53699


下記の様にfor文を使わずに記述したいと考えていますがエラーが出てしまいます。  
正直for_eachとmapの違いがいまいちわからず、  
下記のコードもmapなのかfor_eachなのか悩んでいます。  
どの様に記述するのが良いでしょうか？

[code]

    use scraper::{Selector, Html};
    fn main(){
        let html = r#"
        <!DOCTYPE html>
        <body>
        <div>
        <ul>
            <li>Foo1</li>
            <li>Foo2</li>
            <li>Foo3</li>
        </ul>
        </div>
        <div>
        <ul>
            <li>Foo4</li>
            <li>Foo5</li>
            <li>Foo6</li>
        </ul>
        </div>
        </body>
    "#;
        let document = Html::parse_document(html);
        let selector = Selector::parse("div").unwrap();
        let ul  = Selector::parse("ul").unwrap();
        let li  = Selector::parse("li").unwrap();
    
       let v :Vec<_> = document.select(&selector)
            .for_each(|item| item.select(&ul))
            .for_each(|item| item.select(&li))
            .for_each(|item| item.inner_html())
            .collect();
        println!("{:?}",v);
    
    }
    
[/code]

error

[code]

    error[E0308]: mismatched types
      --> src/main.rs:28:26
       |
    28 |         .for_each(|item| item.select(&ul))
       |                          ^^^^^^^^^^^^^^^^ expected (), found struct `scraper::element_ref::Select`
       |
       = note: expected type `()`
                  found type `scraper::element_ref::Select<'_, '_>`
    
    error[E0599]: no method named `for_each` found for type `()` in the current scope
      --> src/main.rs:29:10
       |
    29 |         .for_each(|item| item.select(&li))
       |          ^^^^^^^^
       |
       = note: the method `for_each` exists but the following trait bounds were not satisfied:
               `&mut () : std::iter::Iterator`
    
[/code]


CC BY-SA 4.0
2019-03-24T05:49:29.607
53687
2019-03-24T14:33:42.990
32285
question
rust
scraperでfor文を使わないで上手くプログラムする方法


## 最終形

最終的にどのようなプログラムになればよいのかが少し分からなかったのですが、おそらくこういうことでしょうか？

[code]

    // ...(省略)...
    let v: Vec<_> = document
        .select(&selector)
        .flat_map(|item| item.select(&ul))
        .flat_map(|item| item.select(&li))
        .map(|item| item.inner_html())
        .collect();
    println!("{:?}", v);
    
[/code]

上のプログラムを実行すると

[code]

    ["Foo1", "Foo2", "Foo3", "Foo4", "Foo5", "Foo6"]
    
[/code]

と表示されます。

## `.map()` と `.for_each()` の違い

さて、本題ですが、 `.map()` と `.for_each()` にはかなりの違いがあります。具体的には:

  * [`.map()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map)
    * 目的 : イテレータの各要素をそれぞれに別のものに変換し、その変換後の要素を生成する新たなイテレータを作ること
    * 戻り値 : 各要素を変換したものを生成するイテレータ
    * 遅延評価 : あり (イテレータをすぐには消費しない。変換は実際に値が必要になったときに処理される)
  * [`.for_each()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.for_each)
    * 目的 : イテレータの各要素に対して順番に処理を実行すること (ほとんど `for ... in` と等価)
    * 戻り値 : Unit 型 `()`
    * 遅延評価 : なし (ただちにイテレータを消費する)

scraper については詳しくないのですが、とりあえず今回の場合、 `document.select()`
によって、マッチした要素を生成するイテレータが得られます。その **マッチした各要素について、さらに`.select()` をかけてしぼりこみたい**
(つまりマッチした各要素にさらに変換をかけたい) という用途ですから、 `.for_each()` より `.map()`
が適切であることが分かります。また、この場合、 `.select()` がまたイテレータを返すことから、マッチした全てに対してさらなる
`.select()` を実行する処理をシンプルに書くために `.flatten()` が利用できます。 `.map()` と `.flatten()`
を融合させた便利メソッドが既に `.flat_map()` として存在するので、これを使います。

## もう少し詳細な説明

### `.map()`

`.map()` は、イテレータの各要素に変換をかける関数です。この関数はその変換内容をクロージャとして渡しますが、 `.map()`
が呼び出された時点でその変換を実行するわけではありません。変換は実際に値が取り出されるときに行われます (遅延評価)
。遅延評価をするのとしないのとでは、特に副作用がない場合は結果に影響はありませんが、副作用があるときは異なる結果になりえます。

[code]

    let range = 1..=3; // range は 1, 2, 3 を生成するイテレータ
    /*
     * twice もイテレータであって、 range の各要素を2倍した値を生成するものである。
     * つまり 2, 4, 6 を生成するイテレータ。
     * なお2倍する直前に元の値を表示する (副作用) 。
     */
    let twice = range.map(|x| {
        println!("twice {}", x);
        x * 2
    });
    for x in twice {
        println!("{}", x);
    }
    
[/code]

出力例

[code]

    twice 1
    2
    twice 2
    4
    twice 3
    6
    
[/code]

もし `.map()` が遅延評価をしないなら、出力は

[code]

    twice 1
    twice 2
    twice 3
    2
    4
    6
    
[/code]

のようになっているはずですが、 `.map()` の遅延評価により **実際に for 文で各要素を取り出す瞬間に`.map()` の変換が適用される**
ため、前者のような出力結果となります。また、実際には取り出されなかった要素については、何の処理もしません。

[code]

    let range = 1..=3; // range は 1, 2, 3 を生成するイテレータ
    
    /*
     * twice もイテレータであって、 range の各要素を2倍した値を生成するものである。
     * つまり 2, 4, 6 を生成するイテレータ。
     * なお2倍する直前に元の値を表示する (副作用) 。
     */
    let twice = range.map(|x| {
        println!("twice {}", x);
        x * 2
    });
    
    for x in twice.take(2) { // 2 つだけ使う
        println!("{}", x);
    }
    
[/code]

出力例

[code]

    twice 1
    2
    twice 2
    4
    
[/code]

`for` 文で `.take(2)` としていることにより要素が 2 つしかとりだされなかったため、要素 `3` については変換すらされていません。

`.map()`
の戻り値はその変換後の値を生成するイテレータですから、それに対してイテレータの各メソッドを呼び出せます。特によく使われるのは質問のコードにもある
`.collect()` ですね。

`.map()` が遅延評価になっていると特に副作用があるときにハマりがちですが、今思い付いたものだけでも、使わなかった値については計算しなくて済むこと、
`.map()` 関数自体は一瞬で終わってくれること、終わりのない無限の長さのイテレータも扱うことができることなど、メリットもあります。

### `.for_each()`

`.for_each()` はイテレータの各要素を純粋に処理する関数です。処理の内容をクロージャとして渡します。 `.map()`
と違って変換することではなく処理することがメインなので、このクロージャの戻り値は `()` でないといけませんし、 `.for_each()`
自体の戻り値も `()` です。

なお、これはほとんど `for` 文と等価です (`break` ができない違いはあります。 `break` できるようにするには
`.try_for_each()` が使えると思います) 。これがなくても `for` 文で事足ります。まあ、 `for .. in` で `in`
以降が長いメソッドチェインになっていて (特に複数行に分けるくらい長いとき)、かつ実行する処理そのものは小さい場合 (表示するだけ)
にスマートに書ける、という効果はあるようです。

[code]

    // ちょっと見にくい
    for x in iter
        .very()
        .very()
        .very()
        .very()
        .very()
        .long()
        .method()
        .chain()
    {
        println!("{}", x);
    }
    
    // v.s.
    
    iter.very()
        .very()
        .very()
        .very()
        .very()
        .long()
        .method()
        .chain()
        .for_each(|x| println!("{}", x));
    
[/code]

余談ですが `.for_each()` でできることは `.map()` でももちろんできます。ただし `.map()`
は先に説明した通り遅延評価になるので、その後に何らかの方法でイテレータを消費してやる必要があります (`.count()` や
`.collect::<()>()` や `.for_each(drop)` など) 。

### `.flatten()`

変換処理によっては、「イテレータを生成するイテレータ」のようなネストしたイテレータが生成されることがあります。このような「イテレータを生成するイテレータ」を得たときに、各イテレータを全てつなぎあわせてネストを一段階解消するのが
`.flatten()` という関数です。(なぜこれが出てきたかというと、最初に提示したコードの `.flat_map()` の説明のためです。)

たとえば次のように使います。

[code]

    let fruits = vec!["apple", "banana", "orange"].into_iter();
    
    let map_fruits = fruits.map(|f| f.chars()); // map_fruits は {'a', 'p', 'p', 'l', 'e'}, ..., {'o', 'r', 'a', 'n', 'g', 'e'} というイテレータ (イテレータを生成するイテレータ)
    let flat = map_fruits.flatten(); // flat は 'a', 'p', 'p', 'l', 'e', ..., 'o', 'r', 'a', 'n', 'g', 'e' というイテレータ (文字を生成するイテレータ)
    
[/code]

この例では、「文字を生成するイテレータを生成するイテレータ `map_fruits`」が `.flatten()`
によって全てつなぎ合わされて「文字を生成するイテレータ」になっています。

### `.flat_map()`

これは上の `.flatten()` の例で示したように `.map()` の変換がイテレータを生成する場合に使える関数です。効果は
`.map().flatten()` というチェーンと同じことになります。


CC BY-SA 4.0
2019-03-24T14:33:42.990
53699
2019-03-24T14:33:42.990
29245
53687
answer