Ratakan Iterator String ke Vec <char> (atau bahkan FromIterator <char>)

Oct 06 2020

Sebagai judul mengatakan, saya memiliki Iteratordari Strings dan ingin menghasilkan Vec<char>, hanya dengan menggabungkan semua dari mereka (dan sementara mempertimbangkan setiap Stringsebagai urutan Nilai Unicode skalar, seperti .chars()halnya). (Saya tidak perlu menyisipkan string antara Strings atau semacamnya, meskipun saya mengerti itu tidak akan jauh lebih sulit dengan, misalnya itertools::intersperse, seperti yang tercakup dalam jawaban lain .)

pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
    // ???
}

(Alangkah baiknya, tetapi tidak terlalu perlu, jika saya dapat menerapkan tanda tangan tipe yang sedikit lebih umum :)

pub fn flatten_strings<S>(ss: impl Iterator<Item=String>) -> S where S: FromIterator<char> {
    // ???
}

Bagaimanapun, inilah upaya sederhana:

pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
    ss.flat_map(|s| s.chars()).collect()
}

Sayangnya, ini tidak berhasil, karena charsreturn Chars, yang berisi referensi ke Stringbasisnya; tapi lambda mengambil alih kepemilikan Stringdan akan menjatuhkannya saat kembali.

error[E0515]: cannot return value referencing function parameter `s`
 --> src/main.rs:2:21
  |
2 |     ss.flat_map(|s| s.chars()).collect()
  |                     -^^^^^^^^
  |                     |
  |                     returns a value referencing data owned by the current function
  |                     `s` is borrowed here

Saya dapat memperbaikinya dengan hanya mengumpulkan semua Strings ke dalam vektor perantara, sehingga saya memiliki seseorang yang memiliki semua Strings, dan saya memetakan sebuah iterator dari &Strings bukannya Strings; tetapi harus mengalokasikan vektor perantara Strings tampaknya tidak efisien dan agak mengalahkan tujuan penggunaan abstraksi iterator Rust yang bagus:

pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
    ss.collect::<Vec<String>>().iter().flat_map(|s| s.chars()).collect()
}

Demikian pula, saya bisa menggulirkan satu putaran, tetapi itu juga tampak jelek. Dapatkah saya mengimplementasikan fungsi ini secara efisien tanpa alokasi tambahan, tetapi juga tanpa meninggalkan abstraksi iterator Rust?

Jawaban

4 NovaDenizen Oct 07 2020 at 00:11

Masalah utamanya adalah bahwa s: Stringparameter dalam closure dihapus sebelum siapa pun dapat menggunakan Charsobjek yang bergantung padanya.

Berikut dua cara untuk mengatasinya. Yang pertama lebih bertele-tele daripada kode Anda, tetapi menggunakan jenis tanda tangan yang sama. Yang kedua bergantung pada iterator &stryang hidup lebih lama dari fungsi dan semua Charsobjeknya.

pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
    let mut res = Vec::new();
    for s in ss {
        res.extend(s.chars());
    }
    res
}

pub fn flatten_strings2<'a>(ss: impl Iterator<Item=&'a str>) -> Vec<char> {
    ss.flat_map(|s| s.chars()).collect()
}
2 MichaelAnderson Oct 07 2020 at 13:18

Jika Anda bisa hidup dengan alokasi sementara, Anda bisa mengonversi setiap string menjadi a Vec<char>saat Anda maju melalui daftar dan menyediakannya ke flat_map. Ini akan memungkinkan kasus yang lebih umum berfungsi juga. Anda dapat bertindak sedikit lebih umum (seperti yang disarankan oleh Sven Marnach ) dan hanya mengembalikan impl Iterator<Item = char>tanpa tagihan , dan mengizinkan penelepon mengumpulkan jika mereka mau.

use std::collections::HashSet;
use core::iter::FromIterator;
pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
    ss.flat_map(|s| s.chars().collect::<Vec<_>>()).collect()
}

pub fn flatten_strings2<S>(ss: impl Iterator<Item=String>)  -> S where S: FromIterator<char>  {
    ss.flat_map(|s| s.chars().collect::<Vec<_>>()).collect()
}

pub fn flatten_strings3(ss: impl Iterator<Item=String>)  -> impl Iterator<Item = char>  {
    ss.flat_map(|s| s.chars().collect::<Vec<_>>())
}

fn main() {
    let v = vec!["A string ".to_string(), "another".to_string() ];
    println!("{:?}",flatten_strings(v.clone().into_iter()));
    let h: HashSet<char> = flatten_strings2(v.clone().into_iter());
    println!("{:?}",h);
    let h: HashSet<char> = flatten_strings3(v.clone().into_iter()).collect();
    println!("{:?}",h);
}