Saya memiliki string multi-baris yang didefinisikan seperti ini:
foo = """
this is
a multi-line string.
"""
String ini kami gunakan sebagai input-tes untuk parser yang saya tulis. Parser-function menerima file
-object sebagai input dan mengulanginya. Itu juga memanggil next()
metode secara langsung untuk melewati baris, jadi saya benar-benar membutuhkan iterator sebagai input, bukan iterable. Saya memerlukan iterator yang mengulangi baris individu dari string itu seperti file
-objek akan melewati baris file teks. Saya tentu saja bisa melakukannya seperti ini:
lineiterator = iter(foo.splitlines())
Apakah ada cara yang lebih langsung untuk melakukan ini? Dalam skenario ini, string harus dilintasi sekali untuk pemisahan, dan kemudian lagi oleh parser. Tidak masalah dalam kasus uji saya, karena stringnya sangat pendek di sana, saya hanya bertanya karena ingin tahu. Python memiliki begitu banyak built-in yang berguna dan efisien untuk hal-hal seperti itu, tetapi saya tidak dapat menemukan apa pun yang sesuai dengan kebutuhan ini.
foo.splitlines()
kan?splitlines()
dan kedua kalinya dengan mengulang hasil dari metode ini.Jawaban:
Berikut tiga kemungkinan:
Menjalankan ini sebagai skrip utama mengonfirmasi bahwa ketiga fungsi tersebut setara. Dengan
timeit
(dan* 100
untukfoo
mendapatkan string substansial untuk pengukuran yang lebih tepat):Perhatikan bahwa kita membutuhkan
list()
panggilan untuk memastikan iterator dilintasi, tidak hanya dibuat.IOW, implementasi naif jauh lebih cepat bahkan tidak lucu: 6 kali lebih cepat daripada upaya saya dengan
find
panggilan, yang pada gilirannya 4 kali lebih cepat daripada pendekatan tingkat yang lebih rendah.Pelajaran yang perlu dipertahankan: pengukuran selalu merupakan hal yang baik (tetapi harus akurat); metode string seperti
splitlines
diimplementasikan dengan cara yang sangat cepat; menempatkan string bersama-sama dengan memprogram pada tingkat yang sangat rendah (khususnya dengan loop dari+=
bagian yang sangat kecil) bisa sangat lambat.Sunting : menambahkan proposal @ Jacob, sedikit dimodifikasi untuk memberikan hasil yang sama dengan yang lain (tanda kosong pada baris disimpan), yaitu:
Pengukuran memberi:
tidak sebagus
.find
pendekatan berbasis - tetap saja, perlu diingat karena mungkin kurang rentan terhadap bug kecil-kecilan (setiap loop di mana Anda melihat kemunculan +1 dan -1, seperti yang saya dif3
atas, akan secara otomatis memicu kecurigaan off-by-one - dan seharusnya banyak loop yang tidak memiliki tweak seperti itu dan seharusnya memilikinya - meskipun saya yakin kode saya juga benar karena saya dapat memeriksa outputnya dengan fungsi lain ').Tetapi pendekatan berbasis terpisah masih berlaku.
Sebuah tambahan: gaya yang mungkin lebih baik untuk
f4
adalah:setidaknya, itu sedikit kurang bertele-tele. Kebutuhan untuk menghapus jejak
\n
sayangnya melarang penggantianwhile
loop yang lebih jelas dan lebih cepat denganreturn iter(stri)
(iter
bagian yang berlebihan dalam versi modern Python, saya percaya sejak 2.3 atau 2.4, tetapi juga tidak berbahaya). Mungkin patut dicoba, juga:atau variasinya - tetapi saya berhenti di sini karena ini adalah latihan teoretis yang
strip
berbasis, paling sederhana dan tercepat.sumber
(line[:-1] for line in cStringIO.StringIO(foo))
cukup cepat; hampir secepat penerapan naif, tetapi tidak cukup.timeit
kebiasaan.list
panggilan untuk benar-benar mengatur waktu semua bagian yang relevan! -).split()
jelas memperdagangkan memori untuk kinerja, memegang salinan dari semua bagian selain struktur daftar.Saya tidak yakin apa yang Anda maksud dengan "kemudian lagi dengan parser". Setelah pemisahan selesai, tidak ada lagi traversal string , hanya traversal daftar string split. Ini mungkin cara tercepat untuk melakukannya, selama ukuran senar Anda tidak terlalu besar. Fakta bahwa python menggunakan string yang tidak dapat diubah berarti Anda harus selalu membuat string baru, jadi ini harus dilakukan di beberapa titik.
Jika string Anda sangat besar, kerugiannya adalah penggunaan memori: Anda akan memiliki string asli dan daftar string terpisah dalam memori pada saat yang sama, menggandakan memori yang diperlukan. Pendekatan iterator dapat menyelamatkan Anda dari hal ini, membuat string sesuai kebutuhan, meskipun tetap membayar penalti "pemisahan". Namun, jika string Anda sebesar itu, Anda biasanya ingin menghindari bahkan string unsplit berada dalam memori. Akan lebih baik jika Anda membaca string dari file, yang sudah memungkinkan Anda untuk mengulanginya sebagai baris.
Namun jika Anda sudah memiliki string yang sangat besar di memori, salah satu pendekatannya adalah menggunakan StringIO, yang menyajikan antarmuka mirip file ke string, termasuk mengizinkan iterasi berdasarkan baris (secara internal menggunakan .find untuk menemukan baris baru berikutnya). Anda kemudian mendapatkan:
sumber
io
paket untuk ini, misalnya gunakanio.StringIO
sebagai penggantiStringIO.StringIO
. Lihat docs.python.org/3/library/io.htmlStringIO
juga merupakan cara yang baik untuk mendapatkan penanganan baris baru universal berkinerja tinggi.Jika saya membacanya
Modules/cStringIO.c
dengan benar, ini seharusnya cukup efisien (meskipun agak bertele-tele):sumber
Pencarian berbasis Regex terkadang lebih cepat daripada pendekatan generator:
sumber
Saya kira Anda bisa menggulung sendiri:
Saya tidak yakin seberapa efisien penerapan ini, tetapi itu hanya akan mengulangi string Anda sekali.
Mmm, generator.
Edit:
Tentu saja Anda juga ingin menambahkan jenis tindakan parsing apa pun yang ingin Anda lakukan, tetapi itu cukup sederhana.
sumber
+=
bagian memilikiO(N squared)
performa terburuk , meskipun beberapa trik penerapan mencoba menurunkannya jika memungkinkan)..join
metode ini sebenarnya terlihat seperti kompleksitas O (N). Karena saya belum dapat menemukan perbandingan khusus yang dibuat pada SO, saya memulai pertanyaan stackoverflow.com/questions/3055477/… (yang secara mengejutkan menerima lebih banyak jawaban daripada jawaban saya sendiri!)Anda dapat mengulang "file", yang menghasilkan baris, termasuk karakter baris baru di belakangnya. Untuk membuat "file virtual" dari string, Anda dapat menggunakan
StringIO
:sumber