Mengapa Python tidak memiliki fungsi "meratakan" untuk daftar?

39

Erlang dan Ruby keduanya dilengkapi dengan fungsi untuk meratakan array. Sepertinya alat yang sederhana dan bermanfaat untuk ditambahkan ke bahasa. Orang bisa melakukan ini:

>>> mess = [[1, [2]], 3, [[[4, 5]], 6]]
>>> mess.flatten()
[1, 2, 3, 4, 5, 6]

Atau bahkan:

>>> import itertools
>>> mess = [[1, [2]], 3, [[[4, 5]], 6]]
>>> list(itertools.flatten(mess))
[1, 2, 3, 4, 5, 6]

Sebaliknya, dalam Python, kita harus melalui kesulitan menulis fungsi untuk meratakan array dari awal. Ini tampak konyol bagi saya, meratakan array adalah hal yang biasa dilakukan. Ini seperti harus menulis fungsi khusus untuk menggabungkan dua array.

Saya telah mencari Google tanpa hasil, jadi saya bertanya di sini; adakah alasan khusus mengapa bahasa dewasa seperti Python 3, yang dilengkapi dengan ratusan ribu baterai, tidak menyediakan metode sederhana untuk meratakan susunan? Apakah gagasan untuk memasukkan fungsi semacam itu telah dibahas dan ditolak di beberapa titik?

Hubro
sumber
2
@detly: Saya kebetulan meratakan akhir-akhir ini ketika menggunakan beberapa pertanyaan untuk mengambil data dari berbagai sumber. Setiap kueri mengembalikan daftar kamus, jadi pada akhirnya saya memiliki daftar daftar kamus yang akan diubah menjadi daftar kamus. Saya menggunakan loop + extendtetapi rata akan jauh lebih elegan. Namun, saya luka jika pola ini cukup umum untuk membenarkan memiliki rata di perpustakaan standar.
Giorgio
4
"Maksud saya, bayangkan jika Anda memasukkan bug ke dalam kode Anda yang secara tidak sengaja mengubah struktur data Anda. Ratakan akan tetap berfungsi, tetapi menghasilkan hasil yang sepenuhnya salah.": Ini adalah salah satu alasan mengapa saya menyukai bahasa yang diketik secara statis. ;-)
Giorgio
2
@BryanOakley Lihat komentar sebelumnya juga (meskipun tidak untuk daftar multi-level, perataan pada umumnya adalah umum)
Izkata
3
Ini built-in di Mathemaica, dan saya menggunakannya secara luas.
Per Alexandersson

Jawaban:

34

Proposal untuk flattenfungsi yang akan ditambahkan ke pustaka standar muncul dari waktu ke waktu pada milis python-dev dan python-ideas . Pengembang Python biasanya merespons dengan hal-hal berikut:

  1. Ratakan satu tingkat (mengubah iterable dari iterables menjadi iterable tunggal) adalah ekspresi satu-baris yang sepele (x for y in z for x in y)dan dalam hal apa pun sudah ada di perpustakaan standar di bawah namanya itertools.chain.from_iterable.

  2. Apa kasus penggunaan untuk perataan multi-level untuk keperluan umum? Apakah ini benar-benar cukup menarik untuk ditambahkan fungsi ke perpustakaan standar?

  3. Bagaimana alat multi-level untuk keperluan umum akan memutuskan kapan harus merata dan kapan pergi sendiri? Anda mungkin berpikir bahwa aturan seperti "meratakan apa pun yang mendukung antarmuka iterable" akan berfungsi, tetapi itu akan menyebabkan loop tak terbatas untuk flatten('a').

Lihat misalnya Raymond Hettinger :

Sudah dibahas ad nauseam di comp.lang.python. Orang-orang tampaknya lebih suka menulis versi ratakan mereka sendiri daripada menemukan kasus penggunaan yang sah yang belum memiliki solusi sepele.

Tujuan umum perataan perlu beberapa cara untuk mengetahui apa itu atomik dan apa yang bisa dibagi lagi. Juga, tidak jelas bagaimana algoritma harus diperluas untuk mencakup input dengan struktur data seperti pohon dengan data di node serta daun (preorder, postorder, inorder traversal, dll.)

Gareth Rees
sumber
Hanya untuk menjadi eksplisit, ini berarti bahwa fungsi satu tingkat flattendapat didefinisikan sebagai lambda z: [x for y in z for x in y].
Christopher Martin
1
"Perata keperluan umum perlu beberapa cara untuk mengetahui apa itu atomik dan apa yang dapat dibagi lagi.": Ini kedengarannya seperti masalah yang dapat diselesaikan dengan menggunakan OOP: setiap objek dapat memiliki flattenmetode. Implementasi metode ini harus memanggil flattensubkomponennya secara rekursif , jika objeknya adalah komposit. Sayangnya, AFAIK tidak setiap nilai adalah objek dengan Python. Di Ruby itu seharusnya bekerja.
Giorgio
1
seorang penolong yang rata untuk satu tingkat rata daripada yang melanjutkan "untuk masuk" sudah merupakan kasus IMO yang cukup baik. mudah dibaca
dtc
2
@Giorgio Python menghindar dari metode seperti itu. Protokol lebih disukai, dan saya menemukan mereka jauh lebih halus untuk bekerja daripada desain OOP karena Anda bahkan sering tidak perlu menerapkan sangat banyak sama sekali.
jpmc26
8

Itu memang datang dengan metode seperti itu tetapi tidak menyebutnya rata. Ini disebut " rantai ". Ini mengembalikan iterator yang kemudian Anda harus menggunakan fungsi daftar () untuk mengubahnya kembali menjadi daftar. Jika Anda tidak ingin menggunakan *, Anda dapat menggunakan versi "from_iterator" kedua. Ia bekerja sama di Python 3. Ini akan gagal jika input daftar bukan daftar daftar.

[[1], [2, 3], [3, 4, 5]] #yes
[1, 2, [5, 6]] #no

Ada suatu waktu metode rata dalam modul compiler.ast tapi ini tidak digunakan lagi di 2.6 dan kemudian dihapus di 3.0. Rekursi kedalaman sewenang-wenang, yang diperlukan untuk daftar yang tersarang secara arbitrer tidak berfungsi dengan baik dengan kedalaman rekursi maksimum konservatif Python. Alasan penghapusan kompiler sebagian besar karena itu menjadi berantakan . Kompiler berubah menjadi ast tetapi pipih tertinggal.

Kedalaman sewenang-wenang dapat dicapai dengan susunan numpy dan pustaka yang rata.

Insinyur Dunia
sumber
The chain.from_iteratorfungsi, seperti yang Anda katakan, hanya dapat digunakan untuk meratakan daftar dua dimensi. Sebuah sebenarnya fungsi meratakan, yang menerima setiap jumlah daftar bersarang dan kembali daftar satu dimensi, masih akan besar-besaran berguna dalam banyak kasus (setidaknya menurut saya)
Hubro
2
@Ubro: "dalam banyak kasus" - dapatkah Anda menyebutkan enam?
Gareth Rees
1
@ GarethRees: Saya memberikan beberapa contoh di sini: programmers.stackexchange.com/questions/254279/…
Hubro
Saya juga akan berpendapat bahwa jika bahasa lain ini memang menyediakan fitur untuk meratakan daftar dengan cara yang sangat sederhana yang dijelaskan, itu adalah salah satu argumen yang paling meyakinkan untuk mendukung penambahan kemampuan sederhana itu ke Python.
Bobort
Apakah itu mengembalikan iterator atau generator?
jpmc26
-1

... mungkin karena tidak sulit untuk menulisnya sendiri

def flatten(l): return flatten(l[0]) + (flatten(l[1:]) if len(l) > 1 else []) if type(l) is list else [l]

... dan kemudian ratakan semua yang Anda inginkan :)

>>> flatten([1,[2,3],4])
[1, 2, 3, 4]
>>> flatten([1, [2, 3], 4, [5, [6, {'name': 'some_name', 'age':30}, 7]], [8, 9, [10, [11, [12, [13, {'some', 'set'}, 14, [15, 'some_string'], 16], 17, 18], 19], 20], 21, 22, [23, 24], 25], 26, 27, 28, 29, 30])
[1, 2, 3, 4, 5, 6, {'age': 30, 'name': 'some_name'}, 7, 8, 9, 10, 11, 12, 13, set(['set', 'some']), 14, 15, 'some_string', 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
>>> 
Shreyas
sumber
8
Penanya mengetahui hal itu: "dengan Python, seseorang harus melalui kesulitan menulis fungsi untuk meratakan array dari awal". Ini bahkan tidak berusaha menjawab pertanyaan yang diajukan, "Ini konyol bagi saya, meratakan array adalah hal yang biasa dilakukan. Ini seperti harus menulis fungsi khusus untuk menggabungkan dua array."
nyamuk
1
Diluar topik ... Tapi super keren :-) !!
SeF
jawaban ini seperti mengatakan pada OP bahwa dia bukan pengembang yang baik karena dia sendiri tidak tahu cara membuat kode fungsi sendiri. Saya sarankan Anda memodifikasi awal jawaban Anda karena ini adalah kode yang berguna bagi mereka yang menemukan pertanyaan, bahkan di luar topik
Federico Bonelli