Memutar array dua dimensi dengan Python


122

Dalam sebuah program saya menulis kebutuhan untuk memutar array dua dimensi muncul. Mencari solusi optimal, saya menemukan satu baris yang mengesankan ini yang melakukan pekerjaan:

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

Saya menggunakannya di program saya sekarang dan berfungsi sebagaimana mestinya. Masalah saya adalah, saya tidak mengerti cara kerjanya.

Saya akan menghargai jika seseorang dapat menjelaskan bagaimana berbagai fungsi yang terlibat mencapai hasil yang diinginkan.


7
Memang. Saya menemukannya dalam pertanyaan SO ini .
paldepind

Jawaban:


96

Perhatikan daftar dua dimensi berikut:

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

Mari kita uraikan langkah demi langkah:

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

Daftar ini dilewatkan ke dalam zip()menggunakan argumen membongkar , sehingga zippanggilan berakhir sampai menjadi setara dengan ini:

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

Mudah-mudahan komentar memperjelas apa zipfungsinya, itu akan mengelompokkan elemen dari setiap input yang dapat di iterable berdasarkan indeks, atau dengan kata lain mengelompokkan kolom.


2
Yang dekat. Tapi saya memilih milik Anda karena seni ASCII yang rapi;)
paldepind

1
dan tanda bintang ??
john ktejik

@johnktejik - itulah bagian "argument unpacking" dari jawaban, klik tautan untuk detailnya
JR Heard

1
Untuk kejelasan, Anda harus menunjukkan bahwa ini memutar matriks searah jarum jam dan daftar dari aslinya diubah menjadi tupel.
Everett

1
untuk pergi lingkaran penuh (mendapatkan kembali daftar daftar dan bukan tupel) Saya melakukan ini:rotated = [list(r) for r in zip(*original[::-1])]
matt

94

Itu sedikit pintar.

Pertama, seperti disebutkan dalam komentar, di Python 3 zip()mengembalikan sebuah iterator, jadi Anda perlu menyertakan semuanya list()untuk mendapatkan daftar yang sebenarnya kembali, jadi pada tahun 2020 itu sebenarnya:

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

Berikut uraiannya:

  • [::-1]- membuat salinan dangkal dari daftar asli dengan urutan terbalik. Bisa juga menggunakan reversed()yang akan menghasilkan iterator terbalik di atas daftar daripada benar-benar menyalin daftar (lebih efisien memori).
  • *- membuat setiap sublist dalam daftar asli menjadi argumen terpisah untuk zip()(yaitu, membongkar daftar)
  • zip()- mengambil satu item dari setiap argumen dan membuat daftar (yah, tuple) dari itu, dan mengulangi sampai semua sublist habis. Di sinilah transposisi sebenarnya terjadi.
  • list()mengonversi keluaran dari zip()menjadi daftar.

Jadi dengan asumsi Anda memiliki ini:

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

Anda pertama kali mendapatkan ini (dangkal, salinan terbalik):

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

Berikutnya setiap sublist diteruskan sebagai argumen ke zip:

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

zip() berulang kali mengkonsumsi satu item dari awal setiap argumennya dan membuat tupel darinya, hingga tidak ada lagi item, menghasilkan (setelah itu diubah menjadi daftar):

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

Dan Bob adalah pamanmu.

Untuk menjawab pertanyaan @ IkeMiguel dalam komentar tentang memutarnya ke arah lain, cukup mudah: Anda hanya perlu membalik urutan yang masuk zipdan hasilnya. Yang pertama dapat dicapai dengan membuang [::-1]yang kedua dan yang kedua dapat dicapai dengan melempar reversed()semuanya. Karena reversed()mengembalikan iterator di atas daftar, kita perlu meletakkannya di list()sekitar itu untuk mengubahnya. Dengan beberapa list()panggilan tambahan untuk mengubah iterator menjadi daftar sebenarnya. Begitu:

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

Kita dapat menyederhanakannya sedikit dengan menggunakan potongan "Smiley Mars" daripada reversed()... maka kita tidak membutuhkan bagian luarnya list():

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

Tentu saja, Anda juga dapat memutar daftar searah jarum jam sebanyak tiga kali. :-)


2
Apakah mungkin memutar berlawanan arah jarum jam ??
Miguel Ike

@MiguelIke ya, lakukan zip (* matrix) [:: - 1]
RYS

3
^ perhatikan bahwa Anda harus memasukkan hasil dari zipke daftar dengan Python 3.x!
RYS

17

Ada tiga bagian untuk ini:

  1. original [:: - 1] membalikkan larik asli. Notasi ini adalah pemotongan daftar Python. Ini memberi Anda "sublist" dari list asli yang dijelaskan oleh [start: end: step], start adalah elemen pertama, end adalah elemen terakhir yang akan digunakan dalam sublist. Langkah mengatakan mengambil setiap elemen langkah dari awal hingga akhir. Awal dan akhir yang dihilangkan berarti potongan akan menjadi keseluruhan daftar, dan langkah negatif berarti Anda akan mendapatkan elemen secara terbalik. Jadi, misalnya, jika aslinya adalah [x, y, z], hasilnya adalah [z, y, x]
  2. The * ketika mendahului list / tuple dalam daftar argumen dari pemanggilan fungsi berarti "perluas" list / tuple sehingga setiap elemennya menjadi argumen terpisah untuk fungsi, bukan daftar / tupel itu sendiri. Sehingga jika, katakanlah, args = [1,2,3], maka zip (args) sama dengan zip ([1,2,3]), tetapi zip (* args) sama dengan zip (1, 2,3).
  3. zip adalah fungsi yang mengambil n argumen yang masing-masing memiliki panjang m dan menghasilkan daftar panjang m, elemen-elemennya memiliki panjang n dan berisi elemen yang sesuai dari masing-masing daftar asli. Misalnya, zip ([1,2], [a, b], [x, y]) adalah [[1, a, x], [2, b, y]]. Lihat juga dokumentasi Python.

+1 karena Anda di mana satu-satunya mungkin menjelaskan langkah pertama.
paldepind

8

Hanya observasi. Inputnya adalah daftar daftar, tetapi output dari solusi yang sangat bagus: rotated = zip (* original [:: - 1]) mengembalikan daftar tupel.

Ini mungkin atau mungkin tidak menjadi masalah.

Namun demikian, mudah diperbaiki:

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 list atau peta keduanya akan mengubah tuple interior kembali ke list.


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
Tolong jelaskan solusi Anda agar orang lain dapat memahaminya dengan lebih baik.
HelloSpeakman

Tentu saja, fungsi pertama (ruota_antiorario) berputar berlawanan arah jarum jam dan fungsi kedua (ruota_orario) searah jarum jam
user9402118

1

Saya sendiri mengalami masalah ini dan saya telah menemukan halaman wikipedia yang bagus tentang subjek tersebut (di paragraf "Rotasi umum":
https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

Kemudian saya menulis kode berikut, super verbose untuk mendapatkan pemahaman yang jelas tentang apa yang sedang terjadi.

Saya harap Anda akan merasakan manfaatnya untuk menggali lebih banyak dalam satu baris yang sangat indah dan cerdas yang Anda posting.

Untuk mengujinya dengan cepat, Anda dapat menyalin / menempelkannya di sini:
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

Berputar Berlawanan Arah Jarum Jam (kolom standar ke poros baris) Sebagai List dan Dict

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]))

Menghasilkan:

[['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
Ini tidak ada hubungannya dengan pertanyaan, yang meminta penjelasan tentang cara zip(*original[::-1])kerja.
kaya3
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.