有没有办法在迭代器链中删除基于Result过滤和分区的项目的展开?

时间:2018-06-09 08:36:52

标签: iterator rust

我创建了这个示例代码:

fn main() {
    let books = vec![
        Book {
            data: Ok("type1".to_owned()),
            metadata: "meta1".to_owned(),
        },
        Book {
            data: Err("-".to_owned()),
            metadata: "meta2".to_owned(),
        },
        Book {
            data: Ok("type2".to_owned()),
            metadata: "meta2".to_owned(),
        },
    ];

    // metadata without data being error
    let (book_type_1, book_type_2): &(Vec<_>, Vec<_>) = &books
        .iter()
        .filter(|f| f.data.is_ok())
        .partition(|p| p.data.as_ref().unwrap() == "type1");

    println!("Books {:?}", books);
    println!("Type 1 {:?}", book_type_1); // Should be the original Vec<Book> with Type 1 filtered.
    println!("Type 2 {:?}", book_type_2); // Should be the original Vec<Book> with Type 2 filtered.
}

#[derive(Debug)]
struct Book {
    data: Result<String, String>,
    metadata: String,
}

let (book_type_1, book_type_2)表达式上,我需要使用Book::data两次,但我已经过滤了它,所以我知道它不能是Err。有没有办法重组以删除unwrap在这里的使用?

2 个答案:

答案 0 :(得分:2)

我不确定你的意思,但似乎你想使用Iterator::filter_map()。它允许您过滤Some(T)的值,然后将其作为未打包的T传递。

所以你可以做的是将你的Result转换为OptionResult::Ok(T)Some(T)将成为T,这意味着它通过了过滤为fn main() { let books = vec![ Book { data: Ok("type1".to_owned()), metadata: "meta1".to_owned(), }, Book { data: Err("-".to_owned()), metadata: "meta2".to_owned(), }, Book { data: Ok("type2".to_owned()), metadata: "meta2".to_owned(), }, ]; // metadata without data being error let (book_type_1, book_type_2): (Vec<_>, Vec<_>) = books .iter() .filter_map(|f| { match &f.data { Ok(data) => Some((f, data)), Err(_) => None, } }) .partition(|(book, data)| *data == "type1"); println!("Books {:?}", books); println!("Type 1 {:?}", book_type_1); println!("Type 2 {:?}", book_type_2); } #[derive(Debug)] struct Book { data: Result<String, String>, metadata: String, }

None

Result::ok()

我删除了对返回的分区元组的不必要引用。

另请注意,Option<T>Result<T, E>相关,但您使用public function findByTerms($terms) : array { $alias = "d"; $qb = $this->createQueryBuilder($alias); foreach (explode(" ", $terms) as $i => $term) { $qb ->andWhere($qb->expr()->orX( // nested condition $qb->expr()->like($alias . ".name", ":term" . $i), $qb->expr()->like($alias . ".description", ":term" . $i) )) ->setParameter("term" . $i, "%" . $term . "%") ; } return $qb->getQuery()->getResult(); } 。我想你知道这一点,但只是确定。

答案 1 :(得分:1)

我会按in the other answer所述使用flat_map,但我会将产生的值保留为ResultResult implements IntoIterator,因此您可以直接在flat_map

中使用它

我也使用Result::as_ref而不是明确写出匹配。

然后我使用Itertools::partition_map同时在各种类型之间进行选择并删除额外的属性:

extern crate itertools;
use itertools::{Either, Itertools};

// ...

let (book_type_1, book_type_2): (Vec<_>, Vec<_>) = books
    .iter()
    .flat_map(|b| b.data.as_ref().map(|data| (b, data)))
    .partition_map(|(b, data)| {
        if data == "type1" {
            Either::Left(b)
        } else {
            Either::Right(b)
        }
    });

注意:

  • 没有理由引用结果元组。
  • 这只能起作用,因为您正在迭代对书籍的引用。

如果您需要对所拥有的Book进行操作,我会将比较转移到flat_map来电并传递给它:

let (book_type_1, book_type_2): (Vec<_>, Vec<_>) = books
    .into_iter()
    .flat_map(|book| {
        book.data
            .as_ref()
            .ok()
            .map(|data| data == "type1")
            .map(|is_type1| (is_type1, book))
    })
    .partition_map(|(is_type1, book)| {
        if is_type1 {
            Either::Left(book)
        } else {
            Either::Right(book)
        }
    });

还有一种实用的解决方案,即将所有错误排序到与其中一种类型相同的bin中。既然你知道赢了是任何错误,这都没有效果:

let (book_type_1, book_type_2): (Vec<_>, Vec<_>) = books
    .iter()
    .filter(|f| f.data.is_ok())
    .partition(|p| p.data.as_ref().ok().map_or(false, |d| d == "type1"));

另见: