Skip to content

Instantly share code, notes, and snippets.

@Cortado-J
Last active August 10, 2020 04:49
Show Gist options
  • Save Cortado-J/69bbd8a79037832be22e9e4509bcc05f to your computer and use it in GitHub Desktop.
Save Cortado-J/69bbd8a79037832be22e9e4509bcc05f to your computer and use it in GitHub Desktop.
Enables simple refactoring of lazy processing of 'streams' of values.
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
///<<< Support Code for Lazy Streams
///<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
///
/// Enables simple refactoring of lazy processing of 'streams' of values.
/// A stream is taken here ot mean a lazy sequence.
///
/// The code is not generic so if streams of different types are required
/// then extra sctions will be needed.
/// Simple to construct for a new type T though:
/// 1) Make a copy of the "standard code" whose type is nearest to T.
/// 2) Change types in the code from old type to T.
/// 3) If necessary create type conversions. See section below: "Conversion between types of streams"
///<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/// <-<-<-<-<-<-<-<-<-<-<-<
/// Stream of Ints (Standard code)
/// <-<-<-<-<-<-<-<-<-<-<-<
protocol StreamOfInts: LazySequenceProtocol where Element == Int {}
extension LazySequence: StreamOfInts where Element == Int {}
extension LazyMapSequence: StreamOfInts where Element == Int {}
extension LazyFilterSequence: StreamOfInts where Element == Int {}
extension StreamOfInts {
/// Ensure unique Int
/// Uses Store struct - see below.
func unique(in store: Store<Int>) -> some StreamOfInts {
filter{ store.unique($0) }
}
/// Allow debugging as in: .debug{ " Value= \($0)" }
func debug(_ perform: @escaping (Int) -> String) -> some StreamOfInts {
map{
print(perform($0))
return $0
}
}
}
/// <-<-<-<-<-<-<-<-<-<-<-<
/// <-<-<-<-<-<-<-<-<-<-<-<
/// Stream of Strings (Standard code)
/// <-<-<-<-<-<-<-<-<-<-<-<
protocol StreamOfStrings: LazySequenceProtocol where Element == String {}
extension LazySequence: StreamOfStrings where Element == String {}
extension LazyMapSequence: StreamOfStrings where Element == String {}
extension LazyFilterSequence: StreamOfStrings where Element == String {}
extension StreamOfStrings {
/// Ensure unique Int
/// Uses Store struct - see below.
func unique(in store: Store<String>) -> some StreamOfStrings {
filter{ store.unique($0) }
}
/// Allow debugging as in: .debug{ " Value= \($0)" }
func debug(_ perform: @escaping (String) -> String) -> some StreamOfStrings {
map{
print(perform($0))
return $0
}
}
}
/// <-<-<-<-<-<-<-<-<-<-<-<
/// <-<-<-<-<-<-<-<-<-<-<-<
/// Conversion between types of streams
/// <-<-<-<-<-<-<-<-<-<-<-<
/// Convert Int to String
extension StreamOfInts {
func asString() -> some StreamOfStrings {
map{ String($0) }
}
}
/// Convert String to Int
extension StreamOfStrings {
func asInt() -> some StreamOfInts {
compactMap{ Int($0) }
}
}
/// <-<-<-<-<-<-<-<-<-<-<-<
/// <-<-<-<-<-<-<-<-<-<-<-<
/// Uniqueness filtering
/// <-<-<-<-<-<-<-<-<-<-<-<
/// We want to allow checking for uniqueness of values in a stream and allow filtering out
/// any repeated values.
/// In eager environments we can just do for example Array(Set(array)) but here we are staying lazy
/// so we'll need to store elements that have gone before and compare any new elements with the store.
/// Here's the store
class Store<T> where T: Hashable {
var history: Set<T> = []
func reset() {
history = []
}
func unique(_ value: T) -> Bool {
if history.contains(value) { return false }
history.insert(value)
return true
}
}
/// <-<-<-<-<-<-<-<-<-<-<-<
///<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
///<<< End of Support code
///<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
extension StreamOfInts {
func squaredAndCopyIncremented() -> some StreamOfInts {
map{ $0 * $0 }
.flatMap{ [$0, $0+1] }
}
}
extension StreamOfStrings {
var dropFirstAndLast: some StreamOfStrings {
map{ String($0.dropFirst().dropLast()) }
}
}
func testLazySequence() {
let numbers = 1...10
let result: some StreamOfInts
= numbers
.lazy
.debug{ "A: \($0) = Started\( $0 >= 5 ? "" : " dropped in next step:")" }
.filter{ $0 >= 5 }
.debug{ "B: \($0) = Keep >= 5" }
.squaredAndCopyIncremented()
.debug{ "C: \($0) = Squared and Copy Incremented" }
.map{ $0 * 2 }
.debug{ "D: \($0) = Multiplied by 2" }
.squaredAndCopyIncremented()
.debug{ "E: \($0) = Squared and Copy Incremented again! \( $0 % 3 != 0 ? "" : " dropped in next step:")" }
.filter{ $0 % 3 != 0 }
.debug{ "F: \($0) = Keep non-multiples of 3" }
.asString()
.debug{ "G: \($0) = As String" }
.dropFirstAndLast
.debug{ "H: \($0) = Dropped First and Last" }
.unique(in: Store<String>()) /// Filter out any which have previously been found in this stream of Strings
.debug{ "I: \($0) = Unique check" }
.asInt()
.debug{ "J: \($0) = As Int again" }
.unique(in: Store<Int>()) /// Filter out any which have previously been found in this stream of Ints
.debug{ "K: \($0) = Unique check" }
print(Array(result))
}
testLazySequence()
@Cortado-J
Copy link
Author

Cortado-J commented Jan 10, 2020

Question was asked about this on Stack Overflow which led to this Gist: https://stackoverflow.com/questions/59675790/refactoring-lazy-functional-code-in-swift-5/59676583#59676583

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment