├── .gitignore ├── LICENSE.txt ├── LazyCollections.playground ├── Contents.swift ├── Sources │ ├── Array+Box.swift │ ├── Box.swift │ └── Request.swift └── contents.xcplayground └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Göksel Köksal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LazyCollections.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | 5 | var frameIndexes: [Int] = Array(1...6) 6 | 7 | let isFrameIncluded: (Int) -> Bool = { print("filter"); return $0 % 2 == 0 } 8 | let pathTransform: (Int) -> String = { print("mapToName"); return "animation_\($0).jpeg" } 9 | let urlTransform: (String) -> URL? = { print("flatMapToURL"); return URL(string: "https://www.somehost.com/\($0)") } 10 | let requestTransform: (URL) -> Request = { print("mapToRequest"); return Request(url: $0) } 11 | let printBlock: (Request) -> Void = { print("print"); print($0.url.path) } 12 | 13 | print("-------------------------------") 14 | print("Using normal map/flatMap/filter") 15 | print("-------------------------------") 16 | 17 | let imageRequests1 = frameIndexes 18 | .filter(isFrameIncluded) 19 | .map(pathTransform) 20 | .flatMap(urlTransform) 21 | .map(requestTransform) 22 | 23 | print("Total: \(imageRequests1.count) items\n") 24 | 25 | print("-------------------------------") 26 | print("Using lazy map/flatMap/filter") 27 | print("-------------------------------") 28 | 29 | let lazyImageRequests = frameIndexes 30 | .lazy 31 | .filter(isFrameIncluded) 32 | .map(pathTransform) 33 | .flatMap(urlTransform) 34 | .map(requestTransform) 35 | 36 | var imageRequests2: [Request] = [] 37 | for request in lazyImageRequests { 38 | imageRequests2.append(request) 39 | } 40 | 41 | print("Total: \(imageRequests2.count) items\n") 42 | 43 | print("-------------------------------") 44 | print("Using Box") 45 | print("-------------------------------") 46 | 47 | let imageRequests3: [Request] = frameIndexes.manipulate { box -> Box in 48 | return box 49 | .filter(isFrameIncluded) 50 | .map(pathTransform) 51 | .mapSome(urlTransform) 52 | .map(requestTransform) 53 | } 54 | 55 | print("Total: \(imageRequests3.count) items") 56 | -------------------------------------------------------------------------------- /LazyCollections.playground/Sources/Array+Box.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Array { 4 | 5 | public func manipulate(_ transform: (Box) -> Box) -> [U] { 6 | return self.flatMap { element in 7 | let box = Box(element) 8 | let transformedBox = transform(box) 9 | return transformedBox.value 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /LazyCollections.playground/Sources/Box.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum Box { 4 | 5 | case filled(T) 6 | case empty 7 | 8 | public init(_ value: T?) { 9 | switch value { 10 | case .some(let wrapped): 11 | self = Box.filled(wrapped) 12 | case .none: 13 | self = Box.empty 14 | } 15 | } 16 | 17 | public var value: T? { 18 | switch self { 19 | case .filled(let value): 20 | return value 21 | case .empty: 22 | return nil 23 | } 24 | } 25 | 26 | public func map(_ transform: (T) -> U) -> Box { 27 | switch self { 28 | case .filled(let value): 29 | let newValue: U = transform(value) 30 | return Box(newValue) 31 | case .empty: 32 | return Box.empty 33 | } 34 | } 35 | 36 | public func mapSome(_ transform: (T) -> U?) -> Box { 37 | switch self { 38 | case .filled(let value): 39 | if let newValue = transform(value) { 40 | return Box.filled(newValue) 41 | } else { 42 | return Box.empty 43 | } 44 | case .empty: 45 | return Box.empty 46 | } 47 | } 48 | 49 | public func filter(_ isIncluded: (T) -> Bool) -> Box { 50 | switch self { 51 | case .filled(let value): 52 | return isIncluded(value) ? self : Box.empty 53 | case .empty: 54 | return Box.empty // or self 55 | } 56 | } 57 | 58 | public func execute(_ block: (T) -> Void) -> Box { 59 | switch self { 60 | case .filled(let value): 61 | block(value) 62 | return self 63 | case .empty: 64 | return self 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /LazyCollections.playground/Sources/Request.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Request { 4 | 5 | public let url: URL 6 | 7 | public init(url: URL) { 8 | self.url = url 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LazyCollections.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lazy Collections 2 | 3 | Playground to demonstrate how lazy collections work. 4 | 5 | ## `map`, `flatMap` and `filter` on a normal array: 6 | 7 | ```swift 8 | let imageRequests = frameIndexes 9 | .filter(isFrameIncluded) 10 | .map(pathTransform) 11 | .flatMap(urlTransform) 12 | .map(requestTransform) 13 | ``` 14 | 15 | **Result:** 16 | 17 | * We iterate over the original array `n` times. 18 | * We create 3 redundant intermediate arrays. 19 | 20 | ## `map`, `flatMap` and `filter` on a lazy array: 21 | 22 | ```swift 23 | let lazyImageRequests = frameIndexes 24 | .lazy 25 | .filter(isFrameIncluded) 26 | .map(pathTransform) 27 | .flatMap(urlTransform) 28 | .map(requestTransform) 29 | 30 | var imageRequests: [Request] = [] 31 | lazyImageRequests.forEach { imageRequests2.append($0) } 32 | ``` 33 | 34 | **Result:** 35 | 36 | * We iterate only once. 37 | * We get each element calculated through the pipe as we access them. 38 | 39 | 40 | --------------------------------------------------------------------------------