Xoay mảng hai chiều trong Python


122

Trong một chương trình tôi đang viết, xuất hiện nhu cầu xoay một mảng hai chiều. Đang tìm kiếm giải pháp tối ưu, tôi thấy một lớp lót ấn tượng này thực hiện công việc:

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

Tôi đang sử dụng nó trong chương trình của mình và nó hoạt động như mong đợi. Tuy nhiên, vấn đề của tôi là tôi không hiểu nó hoạt động như thế nào.

Tôi đánh giá cao nếu ai đó có thể giải thích cách các chức năng khác nhau liên quan đạt được kết quả mong muốn.


7
Thật. Tôi tìm thấy nó trong câu hỏi SO này .
paldepind

Câu trả lời:


96

Hãy xem xét danh sách hai chiều sau:

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

Hãy chia nhỏ nó từng bước:

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

Danh sách này được chuyển vào zip()bằng cách sử dụng giải nén đối số , vì vậy zipcuộc gọi kết thúc tương đương với điều này:

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

Hy vọng rằng các nhận xét làm rõ ràng những gì zipthực hiện, nó sẽ nhóm các phần tử từ mỗi đầu vào có thể lặp lại dựa trên chỉ mục, hay nói cách khác nó nhóm các cột.


2
Một cái kết. Nhưng tôi đã chọn của bạn do nghệ thuật ASCII gọn gàng;)
paldepind

1
và dấu hoa thị ??
john ktejik

@johnktejik - đó là phần "đối số giải nén" của câu trả lời, hãy nhấp vào liên kết để biết chi tiết
JR Heard

1
Để rõ ràng, bạn nên chỉ ra rằng điều này xoay ma trận theo chiều kim đồng hồ và các danh sách từ bản gốc được chuyển đổi thành các bộ giá trị.
Everett

1
để đi toàn bộ vòng kết nối (lấy lại một danh sách các danh sách chứ không phải các bộ giá trị) Tôi đã làm điều này:rotated = [list(r) for r in zip(*original[::-1])]
matt

94

Đó là một chút thông minh.

Đầu tiên, như đã lưu ý trong một nhận xét, trong Python 3 zip()trả về một trình lặp, vì vậy bạn cần bao gồm toàn bộ nội dung list()để lấy lại danh sách thực tế, vì vậy kể từ năm 2020, nó thực sự:

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

Đây là sự cố:

  • [::-1]- tạo một bản sao ngắn của danh sách gốc theo thứ tự ngược lại. Cũng có thể sử dụng reversed()mà sẽ tạo ra một trình lặp ngược trên danh sách thay vì thực sự sao chép danh sách (hiệu quả hơn về bộ nhớ).
  • * - làm cho mỗi danh sách con trong danh sách ban đầu trở thành một đối số riêng biệt để zip() (tức là giải nén danh sách)
  • zip()- lấy một mục từ mỗi đối số và tạo một danh sách (tốt, một bộ) từ các đối số đó và lặp lại cho đến khi tất cả các danh sách con hết. Đây là nơi mà sự chuyển vị thực sự xảy ra.
  • list()chuyển đổi kết quả đầu ra của zip()một danh sách.

Vì vậy, giả sử bạn có điều này:

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

Đầu tiên bạn nhận được điều này (bản sao nông, đảo ngược):

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

Tiếp theo, mỗi danh sách con được chuyển làm đối số cho zip:

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

zip() liên tục sử dụng một mục từ đầu mỗi đối số của nó và tạo một bộ từ đó, cho đến khi không còn mục nào nữa, dẫn đến (sau khi nó được chuyển đổi thành danh sách):

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

Và Bob là chú của bạn.

Để trả lời câu hỏi của @ IkeMiguel trong một nhận xét về việc xoay nó theo hướng khác, khá đơn giản: bạn chỉ cần đảo ngược cả trình tự đi vào zipvà kết quả. Điều đầu tiên có thể đạt được bằng cách loại bỏ [::-1]và thứ hai có thể đạt được bằng cách ném reversed()xung quanh toàn bộ. Vì reversed()trả về một trình vòng lặp trên danh sách, chúng tôi sẽ cần đặt list()xung quanh đó để chuyển đổi nó. Với một vài list()lệnh gọi bổ sung để chuyển đổi các trình vòng lặp thành một danh sách thực tế. Vì thế:

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

Chúng ta có thể đơn giản hóa điều đó một chút bằng cách sử dụng lát cắt "Mặt cười của sao Hỏa" thay vì reversed()... thì chúng ta không cần lớp vỏ ngoài list():

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

Tất nhiên, bạn cũng có thể chỉ cần xoay danh sách theo chiều kim đồng hồ ba lần. :-)


2
Có thể xoay ngược chiều kim đồng hồ không ??
Miguel Ike

@MiguelIke yes, do zip (* matrix) [:: - 1]
RYS

3
^ lưu ý rằng bạn phải truyền kết quả của zipvào một danh sách trong Python 3.x!
RYS

17

Có ba phần cho điều này:

  1. gốc [:: - 1] đảo ngược mảng ban đầu. Ký hiệu này là cắt danh sách Python. Điều này cung cấp cho bạn một "danh sách con" của danh sách ban đầu được mô tả bởi [start: end: step], bắt đầu là phần tử đầu tiên, kết thúc là phần tử cuối cùng được sử dụng trong danh sách con. bước cho biết thực hiện từng phần tử bước từ đầu tiên đến cuối cùng. Bắt đầu và kết thúc bị bỏ qua có nghĩa là lát cắt sẽ là toàn bộ danh sách và bước phủ định có nghĩa là bạn sẽ nhận được các phần tử ngược lại. Vì vậy, ví dụ: nếu ban đầu là [x, y, z], kết quả sẽ là [z, y, x]
  2. Dấu * khi đứng trước danh sách / tuple trong danh sách đối số của một lệnh gọi hàm có nghĩa là "mở rộng" danh sách / tuple để mỗi phần tử của nó trở thành một đối số riêng biệt cho hàm, thay vì chính danh sách / tuple. Vì vậy, giả sử nếu args = [1,2,3], thì zip (args) giống như zip ([1,2,3]), nhưng zip (* args) giống với zip (1, 2,3).
  3. zip là một hàm nhận n đối số, mỗi đối số có độ dài m và tạo ra một danh sách có độ dài m, các phần tử của có độ dài n và chứa các phần tử tương ứng của mỗi danh sách ban đầu. Ví dụ: zip ([1,2], [a, b], [x, y]) là [[1, a, x], [2, b, y]]. Xem thêm tài liệu Python.

+1 vì bạn là người duy nhất có thể giải thích bước đầu tiên.
paldepind

8

Chỉ là một quan sát. Đầu vào là một danh sách các danh sách, nhưng đầu ra từ giải pháp rất hay: rotated = zip (* original [:: - 1]) trả về một danh sách các bộ giá trị.

Đây có thể là một vấn đề.

Tuy nhiên, nó dễ dàng sửa chữa:

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

Danh sách comp hoặc bản đồ sẽ chuyển đổi các bộ giá trị bên trong trở lại danh sách.


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
Vui lòng giải thích giải pháp của bạn để người khác có thể hiểu rõ hơn.
HelloSpeakman

tất nhiên, chức năng đầu tiên (ruota_antiorario) quay ngược chiều kim đồng hồ và chức năng thứ hai (ruota_orario) theo chiều kim đồng hồ
user9402118 23/02

1

Bản thân tôi đã gặp sự cố này và tôi đã tìm thấy trang wikipedia tuyệt vời về chủ đề này (trong đoạn "Các cách xoay thông thường":
https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

Sau đó, tôi viết đoạn mã sau, siêu dài để hiểu rõ ràng về những gì đang xảy ra.

Tôi hy vọng rằng bạn sẽ thấy hữu ích khi tìm hiểu thêm về một dòng chữ rất đẹp và thông minh mà bạn đã đăng.

Để nhanh chóng kiểm tra nó, bạn có thể sao chép / dán nó tại đây:
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

Xoay ngược chiều kim đồng hồ (từ cột tiêu chuẩn sang tổng hợp hàng) Dưới dạng danh sách và chính tả

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

Sản xuất:

[['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
Điều này không liên quan đến câu hỏi yêu cầu giải thích về cách zip(*original[::-1])hoạt động.
kaya3
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.