2017-02-07 3 views
8

Моя первоначальная цель - получить список слов, по одному на каждую строку, и поместить их в HashSet, отбросив строки комментариев и правильно подняв ошибки ввода-вывода. Учитывая файл «stopwords.txt»:Почему строки в моем итераторе конкатенированы?

a 
# this is actually a comment 
of 
the 
this 

мне удалось сделать код компиляции, как это:

fn stopword_set() -> io::Result<HashSet<String>> { 
    let words = Result::from_iter(
     BufReader::new(File::open("stopwords.txt")?) 
       .lines() 
       .filter(|r| match r { 
        &Ok(ref l) => !l.starts_with('#'), 
        _ => true 
       })); 
    Ok(HashSet::from_iter(words)) 
} 

fn main() { 
    let set = stopword_set().unwrap(); 
    println!("{:?}", set); 
    assert_eq!(set.len(), 4); 
} 

Вот playground, что также создает файл выше.

Я ожидаю, что в конце программы будет набор из 4 строк. К моему удивлению, функция фактически возвращает набор, содержащий одну строку со всеми словами каскадного:

{"aofthethis"} 
thread 'main' panicked at 'assertion failed: `(left == right)` (left: `1`, right: `4`)' 

во главе с кусочком советом в документации для FromIterator, я избавилась от всех вызовов from_iter и используется collect вместо (Playground), который действительно решил проблему.

fn stopword_set() -> io::Result<HashSet<String>> { 
    BufReader::new(File::open("stopwords.txt")?) 
      .lines() 
      .filter(|r| match r { 
       &Ok(ref l) => !l.starts_with('#'), 
       _ => true 
      }).collect() 
} 

Почему предыдущие вызовы from_iter, ведущие к неожиданным выводам, в то время как collect() работает точно так же, как предполагалось?

ответ

8

Более простое воспроизводство:

use std::collections::HashSet; 
use std::iter::FromIterator; 

fn stopword_set() -> Result<HashSet<String>, u8> { 
    let input: Vec<Result<_, u8>> = vec![Ok("foo".to_string()), Ok("bar".to_string())]; 
    let words = Result::from_iter(input.into_iter()); 
    Ok(HashSet::from_iter(words)) 
} 

fn main() { 
    let set = stopword_set().unwrap(); 
    println!("{:?}", set); 
    assert_eq!(set.len(), 2); 
} 

Проблема заключается в том, что здесь, мы собираем из итератора дважды. Тип words: Result<_, u8>. Однако Resultтакже сам реализует Iterator, поэтому, когда мы вызываем from_iter, то в конце компилятор видит, что тип Ok должен быть String из-за подписи метода. Работая назад, вы можете построить String из итератора Strings, так что это то, что выбирает компилятор.

Удаление второго from_iter будет решить:

fn stopword_set() -> Result<HashSet<String>, u8> { 
    let input: Vec<Result<_, u8>> = vec![Ok("foo".to_string()), Ok("bar".to_string())]; 
    Result::from_iter(input.into_iter()) 
} 

Или оригинал:

fn stopword_set() -> io::Result<HashSet<String>> { 
    Result::from_iter(
     BufReader::new(File::open("stopwords.txt")?) 
       .lines() 
       .filter(|r| match r { 
        &Ok(ref l) => !l.starts_with('#'), 
        _ => true 
       })) 
} 

Конечно, я обычно рекомендую использовать collect вместо этого, как я предпочитаю цепочки:

fn stopword_set() -> io::Result<HashSet<String>> { 
    BufReader::new(File::open("stopwords.txt")?) 
     .lines() 
     .filter(|r| match r { 
      &Ok(ref l) => !l.starts_with('#'), 
      _ => true, 
     }) 
     .collect() 
} 
+2

Черт, получилось около 2/3 пути, написав мой. –

+1

@ DK. возможно, у вас есть лучшее/другое/более понятное объяснение? – Shepmaster

+0

Нет, это было более или менее одно и то же, написанное в обратном порядке. –

Смежные вопросы