Pythonでの2次元配列の回転


122

私が書いているプログラムでは、2次元配列を回転させる必要が生じました。最適な解決策を探して、私は仕事をするこの印象的なワンライナーを見つけました:

rotated = zip(*original[::-1])

私は今それを自分のプログラムで使用しており、想定どおりに機能します。しかし、私の問題は、それがどのように機能するのか理解していないということです。

関係するさまざまな機能がどのようにして望ましい結果を達成するかを誰かが説明していただければ幸いです。


7
確かに。私はそれをこの SOの質問で見つけました。
paldepind 2011

回答:


96

次の2次元リストを考えてみます。

original = [[1, 2],
            [3, 4]]

それを段階的に分解してみましょう:

>>> original[::-1]   # elements of original are reversed
[[3, 4], [1, 2]]

このリストは引数unpackingzip()使用して渡されるため、呼び出しは最終的にこれzipと同等になります。

zip([3, 4],
    [1, 2])
#    ^  ^----column 2
#    |-------column 1
# returns [(3, 1), (4, 2)], which is a original rotated clockwise

うまくいけば、コメントで何zipが行われるかが明確になり、インデックスに基づいて反復可能な各入力の要素がグループ化されます。つまり、列がグループ化されます。


2
近いもの。しかし、きちんとしたASCIIアートのおかげで私はあなたのものを選びました;)
paldepind

1
とアスタリスク?
ジョンktejik

@johnktejik-これは答えの「引数の解凍」部分です。詳細についてはリンクをクリックしてください
JRハード

1
明確にするために、これによりマトリックスが時計回りに回転し、元のリストがタプルに変換されることを指摘する必要があります。
Everett

1
完全に一周する(タプルではなくリストのリストを取得する)私はこれを行いました:rotated = [list(r) for r in zip(*original[::-1])]
matt

94

それは賢いビットです。

まず、コメントで述べたように、Python 3 zip()ではイテレーターを返すlist()ため、実際のリストを取り戻すには全体を囲む必要があります。2020年の時点では、実際には次のようになります。

list(zip(*original[::-1]))

内訳は次のとおりです。

  • [::-1]-元のリストの浅いコピーを逆の順序で作成します。も使用できますreversed()実際にリストをコピーするのではなく、リストに対してリバースイテレータを生成 whichをすることます(よりメモリ効率が良い)。
  • * -元のリストの各サブリストを別の引数にします zip()(つまり、リストをアンパックします)
  • zip()-各引数から1つの項目を取り、それらからリスト(まあ、タプル)を作成し、すべてのサブリストがなくなるまで繰り返します。これが実際に移調が発生する場所です。
  • list() の出力を変換します zip()リストに。

だからあなたがこれを持っていると仮定します:

[ [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9] ]

最初にこれを取得します(浅い、反転コピー):

[ [7, 8, 9],
  [4, 5, 6],
  [1, 2, 3] ]

次に、各サブリストが引数としてに渡されますzip

zip([7, 8, 9], [4, 5, 6], [1, 2, 3])

zip() は、各引数の先頭から1つのアイテムを繰り返し消費し、アイテムがなくなるまでタプルを作成し、(リストに変換された後)結果として次のようになります。

[(7, 4, 1), 
 (8, 5, 2), 
 (9, 6, 3)]

そしてボブはあなたの叔父です。

@IkeMiguelの反対方向への回転についてのコメントでの質問に答えるには、それは非常に簡単ですzip。入るシーケンスと結果の両方を逆にするだけです。1つ目はを削除する[::-1]ことで実現でき、2つ目reversed()は全体をスローすることで実現できます。以来reversed()、リストの上に返すイテレータ、我々は配置する必要がありますlist()周りのこと、それを変換します。list()イテレータを実際のリストに変換するための追加の呼び出しがいくつかあります。そう:

rotated = list(reversed(list(zip(*original))))

「火星のスマイリー」スライスを使用することで、これを少し簡略化できreversed()ます... では、外側は必要ありませんlist()

rotated = list(zip(*original))[::-1]

もちろん、リストを時計回りに3回回転させることもできます。:-)


2
反時計回りに回転させることは可能ですか?
Miguel Ike、

@MiguelIkeはい、zip(* matrix)を実行します[::-1]
RYS 2018

3
^結果をzipPython 3.xのリストにキャストする必要があることに注意してください!
RYS 2018

17

これには3つの部分があります。

  1. original [::-1]は元の配列を逆にします。この表記はPythonリストスライスです。これにより、[start:end:step]で記述された元のリストの「サブリスト」が得られます。startは最初の要素、endはサブリストで使用される最後の要素です。ステップは、最初から最後まですべてのステップの要素を取ると言います。startとendを省略すると、スライスはリスト全体になります。負のステップは、要素を逆に取得することを意味します。したがって、たとえば、オリジナルが[x、y、z]の場合、結果は[z、y、x]になります。
  2. 関数呼び出しの引数リストでリスト/タプルの前にある*は、リスト/タプル自体ではなく、各要素が関数の個別の引数になるようにリスト/タプルを「展開」することを意味します。したがって、たとえば、args = [1,2,3]の場合、zip(args)はzip([1,2,3])と同じですが、zip(* args)はzip(1、 2,3)。
  3. zipは、長さがmであるn個の引数を取り、長さがmのリストを生成する関数です。たとえば、zip([1,2]、[a、b]、[x、y])は[[1、a、x]、[2、b、y]]です。Pythonのドキュメントもご覧ください

+1あなたはおそらく最初のステップを説明している唯一の人です。
paldepind 2011

8

ただの観察です。入力はリストのリストですが、非常に優れたソリューションの回転:zip(* original [::-1])からの出力は、タプルのリストを返します。

これは問題である場合とそうでない場合があります。

ただし、簡単に修正できます。

original = [[1, 2, 3],
            [4, 5, 6],
            [7, 8, 9]
            ]


def rotated(array_2d):
    list_of_tuples = zip(*array_2d[::-1])
    return [list(elem) for elem in list_of_tuples]
    # return map(list, list_of_tuples)

print(list(rotated(original)))

# [[7, 4, 1], [8, 5, 2], [9, 6, 3]]

リストcompまたはマップはどちらも、内部タプルをリストに変換します。


2
def ruota_orario(matrix):
   ruota=list(zip(*reversed(matrix)))
   return[list(elemento) for elemento in ruota]
def ruota_antiorario(matrix):
   ruota=list(zip(*reversed(matrix)))
   return[list(elemento)[::-1] for elemento in ruota][::-1]

4
他の人がそれをよりよく理解できるように、あなたの解決策を説明してください。
HelloSpeakman 2018

もちろん、最初の関数(ruota_antiorario)は反時計回りに回転し、2番目の関数(ruota_orario)は時計回りに回転します
user9402118

1

私自身もこの問題を抱えており、このテーマに関するすばらしいWikipediaページを見つけました(「一般的なローテーション」の段落:https :
//en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

次に、何が起こっているのかを明確に理解するために、次のコードを非常に詳細に記述しました。

あなたが投稿した非常に美しくて巧妙なワンライナーをさらに掘り下げることが役立つことを願っています。

すばやくテストするには、ここにコピーして貼り付けます:http :
//www.codeskulptor.org/

triangle = [[0,0],[5,0],[5,2]]
coordinates_a = triangle[0]
coordinates_b = triangle[1]
coordinates_c = triangle[2]

def rotate90ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1]
# Here we apply the matrix coming from Wikipedia
# for 90 ccw it looks like:
# 0,-1
# 1,0
# What does this mean?
#
# Basically this is how the calculation of the new_x and new_y is happening:
# new_x = (0)(old_x)+(-1)(old_y)
# new_y = (1)(old_x)+(0)(old_y)
#
# If you check the lonely numbers between parenthesis the Wikipedia matrix's numbers
# finally start making sense.
# All the rest is standard formula, the same behaviour will apply to other rotations, just
# remember to use the other rotation matrix values available on Wiki for 180ccw and 170ccw
    new_x = -old_y
    new_y = old_x
    print "End coordinates:"
    print [new_x, new_y]

def rotate180ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1] 
    new_x = -old_x
    new_y = -old_y
    print "End coordinates:"
    print [new_x, new_y]

def rotate270ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1]  
    new_x = -old_x
    new_y = -old_y
    print "End coordinates:"
    print [new_x, new_y]

print "Let's rotate point A 90 degrees ccw:"
rotate90ccw(coordinates_a)
print "Let's rotate point B 90 degrees ccw:"
rotate90ccw(coordinates_b)
print "Let's rotate point C 90 degrees ccw:"
rotate90ccw(coordinates_c)
print "=== === === === === === === === === "
print "Let's rotate point A 180 degrees ccw:"
rotate180ccw(coordinates_a)
print "Let's rotate point B 180 degrees ccw:"
rotate180ccw(coordinates_b)
print "Let's rotate point C 180 degrees ccw:"
rotate180ccw(coordinates_c)
print "=== === === === === === === === === "
print "Let's rotate point A 270 degrees ccw:"
rotate270ccw(coordinates_a)
print "Let's rotate point B 270 degrees ccw:"
rotate270ccw(coordinates_b)
print "Let's rotate point C 270 degrees ccw:"
rotate270ccw(coordinates_c)
print "=== === === === === === === === === "

-1

反時計回りに回転(標準の列から行へのピボット)リストおよび辞書として

rows = [
  ['A', 'B', 'C', 'D'],
  [1,2,3,4],
  [1,2,3],
  [1,2],
  [1],
]

pivot = []

for row in rows:
  for column, cell in enumerate(row):
    if len(pivot) == column: pivot.append([])
    pivot[column].append(cell)

print(rows)
print(pivot)
print(dict([(row[0], row[1:]) for row in pivot]))

生成:

[['A', 'B', 'C', 'D'], [1, 2, 3, 4], [1, 2, 3], [1, 2], [1]]
[['A', 1, 1, 1, 1], ['B', 2, 2, 2], ['C', 3, 3], ['D', 4]]
{'A': [1, 1, 1, 1], 'B': [2, 2, 2], 'C': [3, 3], 'D': [4]}

1
これは、動作の説明を求める質問とは無関係zip(*original[::-1])です。
kaya3
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.