web-dev-qa-db-ja.com

SwiftでArrayでレイジーを使用する理由とタイミング

[1, 2, 3, -1, -2].filter({ $0 > 0 }).count // => 3

[1, 2, 3, -1, -2].lazy.filter({ $0 > 0 }).count // => 3

2番目のステートメントにlazyを追加する利点は何ですか。私の理解によると、lazy変数が使用されると、メモリは使用時にその変数に初期化されます。この文脈ではどのように意味がありますか?

enter image description here

LazySequenceの使用についてもう少し詳しく理解しようとしています。シーケンスでmapreducefilter関数を使用しましたが、lazyシーケンスでは使用していません。これを使用する理由を理解する必要がありますか?

25
Deep Arora

lazyは、配列の処理方法を変更します。 lazyを使用しない場合、filterは配列全体を処理し、結果を新しい配列に格納します。 lazyを使用すると、シーケンスまたはコレクションの値が下流の関数から生成されますオンデマンド。値は配列に格納されません。それらは必要なときに生産されます。

私がreduceの代わりにcountを使用したこの変更された例を考えてください。

lazyを使用しない

この場合、何かがカウントされる前に、すべてのアイテムが最初にフィルタリングされます。

[1, 2, 3, -1, -2].filter({ print("filtered one"); return $0 > 0 })
    .reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
filtered one
filtered one
filtered one
filtered one
counted one
counted one
counted one

lazyの使用

この場合、reduceはカウントするアイテムを要求しており、filterはアイテムが見つかるまで機能し、次にreduceは別のアイテムを要求し、filter別が見つかるまで機能します。

[1, 2, 3, -1, -2].lazy.filter({ print("filtered one"); return $0 > 0 })
    .reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
counted one
filtered one
counted one
filtered one
counted one
filtered one
filtered one

lazyを使用する場合

option-lazyをクリックすると、この説明が表示されます。

pop-up for lazy in Xcode

lazyDiscussionから:

操作をチェーンする場合は、遅延プロパティを使用します。

  1. 中間操作がストレージを割り当てないようにする

    または

  2. 不要な計算を避けるために最終的なコレクションの一部のみが必要な場合

    3番目を追加します。

  3. ダウンストリームプロセスをより早く開始し、アップストリームプロセスがすべての作業を最初に実行するのを待つ必要がない場合

したがって、たとえば、最初の正のlazyを検索する場合は、filterの前にIntを使用することをお勧めします。 filterを使用すると、配列全体をフィルター処理する必要がなくなり、フィルター処理された配列にスペースを割り当てる必要がなくなります。

3番目の点として、filterを使用して1...10_000_000の範囲の素数を表示するプログラムがあるとします。素数を表示する前にすべてを計算するのを待つ必要があるよりも、素数を見つけたときに表示するほうがよいでしょう。

42
vacawama

これまで見たことがないので、探して見つけました。

投稿した構文は遅延コレクションを作成します。遅延コレクションでは、コードの各ステップで一連の中間配列全体を作成する必要がありません。フィルターステートメントしかない場合はそれほど関係ありません。filter.map.map.filter.map、遅延コレクションがないため、各ステップで新しい配列が作成されます。

詳細については、この記事を参照してください。

https://medium.com/developermind/lightning-read-1-lazy-collections-in-Swift-fa997564c1a

編集:

私はいくつかのベンチマークを行いましたが、マップやフィルターなどの一連の高次関数は、「通常の」コレクションよりも遅延コレクションの方が実際には少し遅いです。

レイジーコレクションでは、パフォーマンスが少し低下しますが、メモリフットプリントが小さくなります。

#2を編集:

@discardableResult func timeTest() -> Double {
    let start = Date()
    let array = 1...1000000
    let random = array
        .map { (value) -> UInt32 in
            let random = arc4random_uniform(100)
            //print("Mapping", value, "to random val \(random)")
            return random
    }
    let result = random.lazy  //Remove the .lazy here to compare
        .filter {
            let result = $0 % 100 == 0
            //print("  Testing \($0) < 50", result)
            return result
        }
        .map { (val: UInt32) -> NSNumber in
            //print("    Mapping", val, "to NSNumber")
            return NSNumber(value: val)
        }
        .compactMap { (number) -> String? in
            //print("      Mapping", number, "to String")
            return formatter.string(from: number)
        }
        .sorted { (lhv, rhv) -> Bool in
            //print("        Sorting strings")
            return (lhv.compare(rhv, options: .numeric) == .orderedAscending)
    }

    let elapsed = Date().timeIntervalSince(start)

    print("Completed in", String(format: "%0.3f", elapsed), "seconds. count = \(result.count)")
    return elapsed
}

上記のコードで、行を変更すると

let result = random.lazy  //Remove the .lazy here to compare

let result = random  //Removes the .lazy here

その後、より速く実行されます。 lazyを使用すると、.lazyコレクションを使用すると、直線配列と比較して、約1.5倍の時間がかかります。

5
Duncan C