การหมุนอาร์เรย์สองมิติใน Python


122

ในโปรแกรมฉันกำลังเขียนความจำเป็นในการหมุนอาร์เรย์สองมิติขึ้นมา การค้นหาโซลูชันที่ดีที่สุดฉันพบว่าหนึ่งซับที่น่าประทับใจนี้ทำงานได้:

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

ตอนนี้ฉันใช้มันในโปรแกรมของฉันและทำงานได้ตามที่ควร ปัญหาของฉันคือฉันไม่เข้าใจว่ามันทำงานอย่างไร

ฉันจะขอบคุณหากมีใครสามารถอธิบายได้ว่าฟังก์ชันต่างๆที่เกี่ยวข้องจะบรรลุผลลัพธ์ที่ต้องการได้อย่างไร


7
จริง ผมพบว่ามันอยู่ในนี้คำถาม SO
paldepind

คำตอบ:


96

พิจารณารายการสองมิติต่อไปนี้:

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

ให้แบ่งมันทีละขั้นตอน:

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

รายการนี้ถูกส่งผ่านไปยังzip()โดยใช้การคลายแพ็กเกจดังนั้นการ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
และดอกจัน ??
john ktejik

@johnktejik - นั่นคือส่วน "การคลายข้อโต้แย้ง" ของคำตอบคลิกลิงก์เพื่อดูรายละเอียด
JR Heard

1
เพื่อความชัดเจนคุณควรชี้ให้เห็นว่าสิ่งนี้จะหมุนเมทริกซ์ตามเข็มนาฬิกาและรายการจากต้นฉบับจะถูกแปลงเป็นทูเปิล
Everett

1
เพื่อไปแบบเต็มวง (รับรายการกลับมาไม่ใช่สิ่งที่เพิ่มขึ้น) ฉันทำสิ่งนี้:rotated = [list(r) for r in zip(*original[::-1])]
แมตต์

94

นั่นเป็นบิตที่ชาญฉลาด

ก่อนอื่นตามที่ระบุไว้ในความคิดเห็นใน Python 3 zip()จะส่งคืนตัววนซ้ำดังนั้นคุณต้องใส่ข้อมูลทั้งหมดlist()เพื่อดึงรายการจริงกลับออกมาดังนั้นในปี 2020 จึงเป็นจริง:

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

นี่คือรายละเอียด:

  • [::-1]- ทำสำเนารายการต้นฉบับแบบตื้นในลำดับย้อนกลับ นอกจากนี้ยังสามารถใช้reversed()ซึ่งจะสร้างตัววนซ้ำแบบย้อนกลับในรายการแทนที่จะคัดลอกรายการจริง ๆ (หน่วยความจำมีประสิทธิภาพมากขึ้น)
  • *- ทำให้แต่ละรายการย่อยในรายการต้นฉบับเป็นอาร์กิวเมนต์แยกกันzip()(กล่าวคือคลายรายการ)
  • zip()- รับหนึ่งรายการจากแต่ละอาร์กิวเมนต์และสร้างรายการ (เช่นทูเพิล) จากสิ่งเหล่านั้นและทำซ้ำจนกว่ารายการย่อยทั้งหมดจะหมด นี่คือจุดที่การขนย้ายเกิดขึ้นจริง
  • 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() ใช้หนึ่งรายการซ้ำ ๆ จากจุดเริ่มต้นของอาร์กิวเมนต์แต่ละรายการและสร้างทูเปิลจากนั้นจนกว่าจะไม่มีรายการอีกต่อไปส่งผลให้ (หลังจากที่มันถูกแปลงเป็นรายการ):

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

และบ๊อบเป็นลุงของคุณ

ในการตอบคำถามของ @ IkeMiguel ในความคิดเห็นเกี่ยวกับการหมุนไปในทิศทางอื่นมันค่อนข้างตรงไปตรงมา: คุณเพียงแค่ต้องย้อนกลับทั้งลำดับที่เข้าzipและผลลัพธ์ ครั้งแรกสามารถทำได้โดยการลบ[::-1]และครั้งที่สองสามารถทำได้โดยการขว้างปาreversed()สิ่งของทั้งหมด ตั้งแต่reversed()ส่งกลับ iterator กว่ารายการที่เราจะต้องใส่list()รอบที่แปลงเป็น ด้วยการlist()เรียกพิเศษสองสามครั้งเพื่อแปลงตัววนซ้ำเป็นรายการจริง ดังนั้น:

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

เราสามารถทำให้มันง่ายขึ้นเล็กน้อยโดยใช้ชิ้นส่วน "Martian smiley" แทนที่จะเป็นreversed()... จากนั้นเราไม่ต้องการด้านนอกlist():

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

แน่นอนคุณสามารถหมุนรายการตามเข็มนาฬิกาสามครั้งได้ :-)


2
หมุนทวนเข็มนาฬิกาได้ไหม ??
Miguel Ike

@MiguelIke ใช่ทำ zip (* matrix) [:: - 1]
RYS

3
^ โปรดทราบว่าคุณต้องส่งผลลัพธ์zipไปยังรายการใน Python 3.x!
RYS

17

มีสามส่วนดังนี้:

  1. original [:: - 1] ย้อนกลับอาร์เรย์เดิม สัญกรณ์นี้คือการแบ่งส่วนรายการ Python สิ่งนี้ทำให้คุณมี "รายการย่อย" ของรายการเดิมที่อธิบายโดย [start: end: step], start เป็นองค์ประกอบแรก, end เป็นองค์ประกอบสุดท้ายที่จะใช้ในรายการย่อย ขั้นตอนกล่าวว่าใช้ทุกขั้นตอนตั้งแต่แรกถึงสุดท้าย การเริ่มต้นและสิ้นสุดที่ไม่ได้รับหมายความว่าชิ้นส่วนจะเป็นรายการทั้งหมดและขั้นตอนที่เป็นลบหมายความว่าคุณจะได้รับองค์ประกอบกลับกัน ตัวอย่างเช่นหากต้นฉบับคือ [x, y, z] ผลลัพธ์จะเป็น [z, y, x]
  2. * เมื่อนำหน้า list / tuple ในรายการอาร์กิวเมนต์ของการเรียกใช้ฟังก์ชันหมายถึง "ขยาย" รายการ / ทูเพิลเพื่อให้แต่ละองค์ประกอบกลายเป็นอาร์กิวเมนต์แยกต่างหากสำหรับฟังก์ชันแทนที่จะเป็น list / tuple เอง ดังนั้นถ้าพูดว่า args = [1,2,3] ดังนั้น zip (args) จะเหมือนกับ zip ([1,2,3]) แต่ zip (* args) จะเหมือนกับ zip (1, 2,3)
  3. zip เป็นฟังก์ชันที่รับ n อาร์กิวเมนต์ซึ่งแต่ละรายการมีความยาว m และสร้างรายการความยาว m องค์ประกอบของความยาว n และมีองค์ประกอบที่สอดคล้องกันของรายการต้นฉบับแต่ละรายการ เช่น zip ([1,2], [a, b], [x, y]) คือ [[1, a, x], [2, b, y]] ดูเอกสาร Python ด้วย

+1 เนื่องจากคุณที่คนเดียวอาจอธิบายขั้นตอนแรก
paldepind

8

เป็นเพียงข้อสังเกต อินพุตเป็นรายการของรายการ แต่ผลลัพธ์จากโซลูชันที่ดีมาก: rotated = 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]]

คอมพ์รายการหรือแผนที่ทั้งคู่จะแปลงสิ่งที่อยู่ภายในกลับเป็นรายการ


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

แน่นอนว่าฟังก์ชันแรก (ruota_antiorario) จะหมุนทวนเข็มนาฬิกาและฟังก์ชันที่สอง (ruota_orario) ตามเข็มนาฬิกา
user9402118

1

ฉันประสบปัญหานี้ด้วยตัวเองและพบหน้าวิกิพีเดียที่ยอดเยี่ยมในหัวข้อ (ในย่อหน้า "การหมุนเวียนทั่วไป":
https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

จากนั้นฉันก็เขียนโค้ดต่อไปนี้ super verbose เพื่อให้มีความเข้าใจอย่างชัดเจนว่าเกิดอะไรขึ้น

ฉันหวังว่าคุณจะพบว่ามีประโยชน์ในการขุดเพิ่มเติมในซับเดียวที่สวยงามและชาญฉลาดที่คุณโพสต์ไว้

หากต้องการทดสอบอย่างรวดเร็วคุณสามารถคัดลอก / วางได้ที่นี่:
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.