Дисплей для чтения человека список вещей


Просто для удовольствия, я создал эту маленькую функцию, которая печатает итератор в строку:

use std::fmt::{Display, Write};

fn enumerate<T: Display, I: IntoIterator<Item = T>>(it: I) -> String {
    let mut it = it.into_iter().peekable();
    let mut result = String::new();

    if let Some(first) = it.next() {
        write!(result, "{}", first).unwrap();
        if it.peek().is_some() {
            while let Some(item) = it.next() {
                match it.peek() {
                    Some(_) => write!(result, ", {}", item),
                    None => write!(result, " and {}", item),
                }.unwrap();
            }
        }
    }

    result
}

fn main() {
    assert_eq!(enumerate(Vec::<i32>::new()), "");
    assert_eq!(enumerate(vec![1]), "1");
    assert_eq!(enumerate(vec![1, 2]), "1 and 2");
    assert_eq!(enumerate(vec![1, 2, 3]), "1, 2 and 3");
    assert_eq!(enumerate(vec![1, 2, 3, 4]), "1, 2, 3 and 4");
}

Но я не очень доволен. Это не очень читабельно, и я переключение между next и peek во вложенных блоках. Я интересно, если это возможно, чтобы написать функцию в более функциональном порядке?



121
4
задан 16 февраля 2018 в 02:02 Источник Поделиться
Комментарии
1 ответ


  1. Это часто более читабельным, чтобы написать черту границы (напр. T: Display) в where п., а не непосредственно рядом с декларацией. Кроме того, существует несколько типов границ, что не может быть записано в угловых скобках и должен быть написан в where п. (примеры ниже). Когда у вас есть where п., поместить все в рамки; не ставьте границы в обоих местах, потому что тогда это просто для кого-то, читающего код, чтобы пропустить границы в угловых скобках.

  2. Вам не нужно T тип параметра функции; вы можете написать привязан I::Item: Display чтобы получить тот же эффект.

  3. В if it.peek().is_some() ненужно; если это выражение возвращает falseпрограмма не введите while let петля в любом случае, и этот цикл является только заявление в if.

  4. С помощью DoubleEndedIterator::next_backмы можем извлечь последний элемент из итератора. Это позволяет нам снять выглядывала, чтобы определить, следует ли вам использовать запятую или "и" в качестве разделителя. Однако, это ограничивает типы итераторов, что функция принимает, потому что итератор должен теперь реализовать DoubleEndedIterator. Для того, чтобы написать это требование, мы должны написать связанный I::IntoIter: DoubleEndedIterator в where п.; Мы не можем написать это в угловых скобках.

fn enumerate<I>(it: I) -> String
where
I: IntoIterator,
I::Item: Display,
I::IntoIter: DoubleEndedIterator,
{
let mut it = it.into_iter();
let mut result = String::new();

if let Some(first) = it.next() {
write!(result, "{}", first).unwrap();

if let Some(last) = it.next_back() {
while let Some(item) = it.next() {
write!(result, ", {}", item).unwrap();
}

write!(result, " and {}", last).unwrap();
}
}

result
}


Я интересно, если это возможно, чтобы написать функцию в более функциональном порядке?

Опираясь на приведенный выше код, мы можем заменить while let петли с fold.

fn enumerate<I>(it: I) -> String
where
I: IntoIterator,
I::Item: Display,
I::IntoIter: DoubleEndedIterator,
{
let mut it = it.into_iter();
let mut result = String::new();

if let Some(first) = it.next() {
write!(result, "{}", first).unwrap();

if let Some(last) = it.next_back() {
result = it.fold(result, |mut result, item| {
write!(result, ", {}", item).unwrap();
result
});

write!(result, " and {}", last).unwrap();
}
}

result
}

write! не "чувствую" функциональная, хотя бы потому, что он видоизменяет ее операнд, а не возвращает новое значение. Поэтому в данном случае, я не думаю, что версия с fold действительно легче, чем версия с while let.

С помощью itertoolsсторонний ящик, опубликованные на ящики.Ио, мы можем упростить код и сделать его более функциональным чувствовать себя, используя его join адаптер. Его реализация является более или менее то, что мы делали раньше.

extern crate itertools;

use std::fmt::{Display, Write};
use std::iter;

use itertools::Itertools;

fn enumerate<I>(it: I) -> String
where
I: IntoIterator,
I::Item: Display,
I::IntoIter: DoubleEndedIterator,
{
let mut it = it.into_iter();

if let Some(first) = it.next() {
if let Some(last) = it.next_back() {
let mut result = iter::once(first)
.chain(it)
.join(", ");

write!(result, " and {}", last).unwrap();
result
} else {
format!("{}", first)
}
} else {
String::new()
}
}

4
ответ дан 16 февраля 2018 в 03:02 Источник Поделиться