在Rust中处理列表+分隔符的最佳代码模式

yhxst69z  于 2023-05-07  发布在  其他
关注(0)|答案(6)|浏览(215)

一个微不足道的Rust自行车着色问题,但我一直看到它以次优(冗长)的方式实现,我想知道stdlib是否应该有所帮助,或者是否有一个好的模式?
目标:

  • 每个元素处理代码应该只写一次
  • 有一个简单的方法来编写“分隔符”处理代码,而无需额外的状态(即,没有bool标志)

假设理想的代码--是否值得从Rust stdlib请求它,或者至少将它添加到itertools(参见issue)?

fn print_functional(items: &[&str]) {
    items.foreach_with_separator(
        |elem| {
            // some arbitrary complex item handling, not printing
            print!("{elem}");
        },
        || {
            // some arbitrary complex "separator" handling
            print!(", "),
        }
    );
}

我主要看到这两种方法。由于格式化代码重复或冗长的上下文状态保持,它们不太理想。我的目标是只使用stdlib,而不创建任何复杂的helper函数。

fn main() {
    let items = vec!["foo", "bar", "baz"];
    print_with_var(&items);
    print_split_last(&items);
}

fn print_with_var(items: &[&str]) {
    let mut first_printed = false;
    for elem in elems {
        if first_printed {
            print!(", ");
        } else {
            first_printed = true;
        }
        // this represents some multi-line processing,
        // but for this example I'm keeping it simple
        let formatted_value = elem.to_string();
        print!("{formatted_value}");
    }
}

fn print_split_last(items: &[&str]) {
    if let Some((last, elems)) = items.split_last() {
        for elem in elems {
            // this is sub-optimal because elem formatting is duplicated
            let formatted_value = elem.to_string();
            print!("{formatted_value}, ");
        }
        let formatted_value = last.to_string();
        print!("{formatted_value}");
    }
}
gfttwv5a

gfttwv5a1#

一个 DRY 解决方案是使用peekable iterators

pub fn print_comma_separated(elements: Vec<&str>)  {
    let mut it = elements.iter().peekable();
    loop {
        if let Some(x) = it.next() {
            print!("{x}"); // only one occurance of 'stringification'
        } else {
            break;
        }
        if it.peek().is_some() {
            print!(", ");
        }
    }
}

pub fn main() {
    print_comma_separated(vec!["a", "b", "c"]);
}

https://godbolt.org/z/zMaP5oExa

0ejtzxu1

0ejtzxu12#

另一种itertools方法,通过使用intersperse避免了join中的字符串分配:

use itertools::Itertools;

pub fn print_comma_separated(elements: &Vec<&str>)  {
    for x in Itertools::intersperse(elements.iter().map(|x| Some(x)), None) {
        if let Some(x) = x {
            print!("{x}")
        } else {
            print!(", ")
        }
    }
}

pub fn main() {
    print_comma_separated(&vec!["a", "b", "c"]);
}

Demo
Rust nightly甚至将intersperse添加到std本身,这将更加出色。

zynd9foi

zynd9foi3#

你不需要一个布尔标志。你可以使用底层的迭代器,然后在抓取第一个元素后,将其余的元素传递给for循环(或者.map它以获得更多的函数式风格):

pub fn foo(ls: &[&str]) -> Option<()> {
    // We get an iterator over the items in a slice
    let mut iter = ls.iter();

    // Get the first item. Note that it's
    // an Option, the slice may be empty.
    // If it is this will essentially
    // return early
    let first = iter.next()?;

    // Do something special with the first element
    println!("first: {first}");

    // Loop over the rest
    for item in iter {
        println!("item: {item}");
    }

    // Return an empty option to satisfy the
    // return type
    Some(())
}   

fn main() {
    foo(&vec!["a", "b", "c"]);
}

你可以看到playground没有解释性注解,它非常简洁。

cpjpxq1n

cpjpxq1n4#

创建另一个函数是最简单的方法。

fn print_split_last(items: &[&str]) {
    if let Some((last, elems)) = items.split_last() {
        for elem in elems {
            let formatted_value = elem_to_string(elem);
            print!("{formatted_value}, ");
        }
        let formatted_value = elem_to_string(last);
        print!("{formatted_value}");
    }
}

fn elem_to_string(elem: &str) -> String {
    elem.to_string()
}

如果涉及到很多状态,可以将其设为闭包并捕获状态。

fn print_split_last(items: &[&str]) {
    let mut state = 0;
    let elem_to_string = |elem| elem.with_state(&mut state);
    if let Some((last, elems)) = items.split_last() {
        for elem in elems {
            let formatted_value = elem_to_string(elem);
            print!("{formatted_value}, ");
        }
        let formatted_value = elem_to_string(last);
        print!("{formatted_value}");
    }
}

你甚至可以把闭包作为一个参数。为了避免创建临时字符串,您应该转换为实现Display的类型。

use std::fmt::Display;

fn print_split_last<T, F, D>(items: &[T], mut f: F)
where
    F: FnMut(&T) -> D,
    D: Display,
{
    if let Some((last, elems)) = items.split_last() {
        for elem in elems {
            let formatted_value = f(elem);
            print!("{formatted_value}, ");
        }
        let formatted_value = f(last);
        print!("{formatted_value}");
    }
}
mkshixfv

mkshixfv5#

使用Itertools或不介意依赖关系的人有itertools::join

use itertools::join;

fn main() {
    println!("{}", join(vec!["a", "b", "c"], ", "))
}

Demo
或更多定制:

use itertools::join;

fn main() {
    let list = vec!["a", "b", "c"];
    println!("{}", join(list.iter().map(|x| {
        format!("{x}") //whatever formatting is desired
    }), ", "))
}

Demo

xcitsw88

xcitsw886#

最简单的,不使用板条箱的方法是:

pub fn print_comma_separated(elements: Vec<&str>)  {
    let mut it = elements.iter();
    let Some(first) = it.next() else {
        return
    }
    print!("{first}");
    for x in it {
        print!(", {x}")
    }
}

很清楚如何将其扩展到,以外的分隔符。

相关问题