Saya menemukan cara saya sendiri untuk mempelajari Rust. Setelah tembok 'mustahil' awal, ada masalah lain, ketika Anda terjebak di tengah-tengah ekspresi dan tidak dapat membuat nilai menjadi tipe yang sesuai.

Ini adalah salah satu kasusnya, dan saya akan melatih apa yang telah saya pelajari.

Tugas

Ada string masukan, dan suatu fungsi perlu menghasilkan string keluaran, di mana setiap . (titik) diganti dengan [.] (titik dalam tanda kurung siku). Kode harus berupa iterator pada input sting dengan .collect() di akhir menghasilkan string output. Tidak ada String.replace atau kecurangan lainnya!

Tanda tangan:

fn myfunc(input: String) -> String;
assert_eq!("abc".to_string(), myfunc("abc".to_string());
assert_eq!("ab[.]c".to_string(), myfunc("ab.c".to_string());
assert_eq!("ab[.][.]c".to_string(), myfunc("ab..c".to_string());
assert_eq!("[.]".to_string(), myfunc(".".to_string());

Kerangka fungsinya:

intput.chars().map??(|c|{
     match c{
       '.' => {?something with "[.]"},
       c => {?just a value of c}
     }
}).collect()

kita tidak tahu apa itu fungsi 'peta??', dan {?} adalah misteri total.

Hal-hal yang tidak mungkin

Kedua lengan match harus mengembalikan tipe yang sama (atau tipe bawah, yang tidak cocok di sini). Di sini masalah pertama: c adalah Char, dan “sesuatu dengan [.]” tidak boleh berupa karakter, melainkan rangkaian karakter. Tidak ada 'generik' di sini.

Masalah dengan mengembalikan “[.]” sangat mendasar di sini, karena ini adalah urutan tidak peduli bagaimana Anda memutarnya. Jadi, untuk membuat lengan kedua (dengan 'c') agar cocok dengan tipenya, kita perlu membuatnya berurutan juga.

Konvensi: Saya lelah mengetik [.] dan c, jadi saya akan menyebutnya 'dot-arm' dan 'c-arm'. Bersabarlah.

Bisakah kita menggunakan std::iter::once? Itu menciptakan iterator dengan satu nilai… Jika kita melakukannya, kita perlu mengubah dot-arm menjadi semacam iterator juga.

Oke, ayo kita lakukan:

'.' => "[.]".chars(),
c => std::iter::once(c)

Tapi tentu saja itu tidak akan berhasil. Karakter iterator mengembalikan tipe Chars, dan sekali iterator mengembalikan Once. Meskipun keduanya adalah iterator dan menerapkan sifat Iterator, keduanya jelas berbeda, dan Rust tidak mengizinkan cabang pertandingan yang berbeda menjadi berbeda. Bahkan jika kita menambahkan beberapa iterator tambahan di akhir (seperti .take(3)), tipe kembaliannya masing-masing akan berbeda.

Oh, juga, jika kita menggunakan iterator sebagai senjata, kita perlu menggunakan fungsi flat_map, bukan hanya map.

Hal-hal yang “mungkin” terjadi

Secara teknis, kita dapat membuat string di kedua lengan, dan mengembalikannya. Ini akan berhasil, tetapi kami akan melakukan alokasi dalam jumlah besar. Setiap karakter (c-arm) akan digabungkan menjadi String, artinya, alokasi baru pada setiap karakter, dan kemudian, selama flat_map, diciutkan kembali menjadi satu string. Tidak, tolong jangan ada string mikro.

Bagaimana dengan iterator?

intput.chars().flat_map(|c|{
     match c{
       '.' => ['[','.',']'].iter(),
       c => [c].iter()
     }
}).collect()

Kedengarannya masuk akal, bukan? Setiap lengan adalah iterator pada array, dan mengembalikan iterator pada dirinya sendiri. Masalahnya adalah kita membuat array dari karakter c, dan array ini bukan milik apa pun, jadi array tersebut langsung dibatalkan alokasinya, dan kita tidak bisa meneruskan iterator ke atasnya. Karat tidak akan mengizinkan hal itu.

Bagaimana jika kita mengembalikan array 'sebagaimana adanya'? (Lupakan hal collect). Tidak bisa, karena [char;1] dan [char;3] berbeda tipe.

Berpikir lebih keras

Kami ingin mengembalikan satu atau lebih karakter. Kita dapat memasukkannya ke dalam Vec, tetapi Vec hampir sama tidak efisiennya dengan String untuk itu. Banyak alokasi.

Tapi, mungkin kita bisa menggunakan irisan. Bagaimana cara membuat irisan dari str? … Sepotong apa?

Ini adalah titik pemikiran. str bukan larik dari Char. Tipe apa yang harus kita gunakan? Sepertinya Chars, karena kita mengulangi karakter (.chars iterator!) Tunggu. Mengapa kita menggunakan chars ? Bisakah kita mengulangi str dengan memiliki str dengan satu karakter dalam satu waktu?

Saya mencari sesuatu dan tidak ada fungsi khusus untuk iterator tersebut. Saya bertanya di Reddit dan mendapat jawabannya:

str.split_inclusive(|_| true)

yang menghasilkan irisan str, di mana setiap irisan hanya memiliki satu karakter. Ini perubahan yang tidak terduga, tetapi berhasil.

Dengan iterator ini kita dapat menulis solusi kerja pertama:

fn myfunc(input: String) -> String {
  input
      .split_inclusive(|_| true)
      .map(|c| match c {
           "." => "[.]",
           c => c,
       })
       .collect()
}

Kami menghasilkan &str dengan panjang berbeda dan dikumpulkan menjadi String .

Cara lain

People on the Discourse menunjukkan kemungkinan solusi lain, jadi mari kita lihat:

pub fn myfunc(input: &str) -> String {
    let bytes: Vec<u8> = input
        .as_bytes()
        .into_iter()
        .flat_map(|c| match c {
            b'.' => b"[.]",
            c => std::slice::from_ref(c),
        })
        .copied()
        .collect();
    String::from_utf8(bytes).unwrap()
}

String telah didekonstruksi menjadi byte dan diulangi. Untuk setiap karakter, ada str, atau fungsi yang belum pernah saya dengar: std::slice::from_ref

Yang menurut saya sangat berguna, karena memungkinkan konversi referensi apa pun untuk dipotong.

Catatan tambahan:

  1. Saya perlu lebih memperhatikan jenis irisan. Menurutku, ini lebih penting daripada kelihatannya.
  2. Saya melewatkan notasi-b. Ini memungkinkan untuk membuat tipe u8 (x=b’.’) atau array u8 (b"hello" sama dengan [b'h', b'e', b'l', b'o']).

Ada juga catatan bagus tentang perpustakaan segmentasi unicode yang menunjuk ke artikel ini: https://manishearth.github.io/blog/2017/01/14/stop-ascribing-meaning-to-unicode-code-points/ … Saya menemukan bahwa Unicode bahkan lebih menyimpang dari yang saya kira… (0xFDFD, ya ampun)…

Kesimpulan

  1. Gunakan String.replace dan jangan menimbulkan pengadukan.

split_inclusive adalah sesuatu yang tidak saya harapkan untuk digunakan, namun sangat menarik, dan dapat digunakan untuk banyak jenis pemisahan lossless. std::slice::from_ref adalah cara yang sangat mudah untuk memasukkan sesuatu ke dalam iterator. b notasinya bagus!

Lebih penting lagi, saya memiliki pemahaman yang cukup kuat tentang 'tidak ada jenis sihir di match'. Anda memerlukan kedua lengan untuk memiliki tipe yang sama, dan tidak ada jalan lain. Selain itu, collect::<String>() dengan flat_map di atas irisan adalah cara menarik untuk memproses string…