Swift: 延迟封装 map, filter, flatMap 链
Swift: Lazily encapsulating chains of map, filter, flatMap
我有一份动物清单:
let animals = ["bear", "dog", "cat"]
以及转换该列表的一些方法:
typealias Transform = (String) -> [String]
let containsA: Transform = { [=12=].contains("a") ? [[=12=]] : [] }
let plural: Transform = { [[=12=] + "s"] }
let double: Transform = { [[=12=], [=12=]] }
顺便说一句,它们分别类似于 filter(输出 0 或 1 个元素)、map(恰好 1 个元素)和 flatmap(多于 1 个元素),但以统一的方式定义,以便可以处理它们始终如一。
我想创建一个惰性迭代器,它将这些转换的数组应用于动物列表:
extension Array where Element == String {
func transform(_ transforms: [Transform]) -> AnySequence<String> {
return AnySequence<String> { () -> AnyIterator<String> in
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
}
}
}
这意味着我可以懒惰地做:
let transformed = animals.transform([containsA, plural, double])
并检查结果:
print(Array(transformed))
我很高兴这很简洁但很清楚:
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
是一个问题,因为它意味着变换函数只能用于 3 个变换的数组。
编辑:
我试过了:
var lazyCollection = self.lazy
for transform in transforms {
lazyCollection = lazyCollection.flatMap(transform) //Error
}
var iterator = lazyCollection.makeIterator()
但在标记的行上出现错误:
无法将类型 'LazyCollection< FlattenCollection< LazyMapCollection< Array< String>, [String]>>>' 的值分配给类型 'LazyCollection< Array< String>>'
这是我理解的,因为每次循环都会添加另一个平面图,所以类型会发生变化。
如何使变换函数与任意数量的变换数组一起工作?
一个用于有限数量转换的 WET 解决方案是(但是 YUK!)
switch transforms.count {
case 1:
var iterator = self
.lazy
.flatMap(transforms[0])
.makeIterator()
return AnyIterator {
return iterator.next()
}
case 2:
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.makeIterator()
return AnyIterator {
return iterator.next()
}
case 3:
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
default:
fatalError(" Too many transforms!")
}
整个代码:
let animals = ["bear", "dog", "cat"]
typealias Transform = (String) -> [String]
let containsA: Transform = { [=19=].contains("a") ? [[=19=]] : [] }
let plural: Transform = { [[=19=] + "s"] }
let double: Transform = { [[=19=], [=19=]] }
extension Array where Element == String {
func transform(_ transforms: [Transform]) -> AnySequence<String> {
return AnySequence<String> { () -> AnyIterator<String> in
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
}
}
}
let transformed = animals.transform([containsA, plural, double])
print(Array(transformed))
如果您在 Sequence
协议(而不是 Array
)上定义方法,则可以递归地应用转换。如果将转换参数定义为 (Element) -> [Element]
.
的数组,则也不需要约束 where Element == String
extension Sequence {
func transform(_ transforms: [(Element) -> [Element]]) -> AnySequence<Element> {
if transforms.isEmpty {
return AnySequence(self)
} else {
return lazy.flatMap(transforms[0]).transform(Array(transforms[1...]))
}
}
}
另一种实现你想要的方法:
Edit: I tried:
var lazyCollection = self.lazy
for transform in transforms {
lazyCollection = lazyCollection.flatMap(transform) //Error
}
var iterator = lazyCollection.makeIterator()
你已经非常接近你的目标了,如果 Error 行中的两种类型都是可分配的,你的代码就可以工作。
稍作修改:
var lazySequence = AnySequence(self.lazy)
for transform in transforms {
lazySequence = AnySequence(lazySequence.flatMap(transform))
}
var iterator = lazySequence.makeIterator()
或者您可以在此处使用 reduce
:
var transformedSequence = transforms.reduce(AnySequence(self.lazy)) {sequence, transform in
AnySequence(sequence.flatMap(transform))
}
var iterator = transformedSequence.makeIterator()
整个代码为:
(编辑 修改为包含 Martin R 的建议。)
let animals = ["bear", "dog", "cat"]
typealias Transform<Element> = (Element) -> [Element]
let containsA: Transform<String> = { [=13=].contains("a") ? [[=13=]] : [] }
let plural: Transform<String> = { [[=13=] + "s"] }
let double: Transform<String> = { [[=13=], [=13=]] }
extension Sequence {
func transform(_ transforms: [Transform<Element>]) -> AnySequence<Element> {
return transforms.reduce(AnySequence(self)) {sequence, transform in
AnySequence(sequence.lazy.flatMap(transform))
}
}
}
let transformed = animals.transform([containsA, plural, double])
print(Array(transformed))
如何将其完全纳入功能世界?例如使用(动态)函数调用链,如 filter(containsA) | map(plural) | flatMap(double)
.
通过一些可重用的通用代码,我们可以实现一些不错的东西。
让我们从将一些序列和惰性序列操作提升为自由函数开始:
func lazy<S: Sequence>(_ arr: S) -> LazySequence<S> {
return arr.lazy
}
func filter<S: Sequence>(_ isIncluded: @escaping (S.Element) throws -> Bool) -> (S) throws -> [S.Element] {
return { try [=10=].filter(isIncluded) }
}
func filter<L: LazySequenceProtocol>(_ isIncluded: @escaping (L.Elements.Element) -> Bool) -> (L) -> LazyFilterSequence<L.Elements> {
return { [=10=].filter(isIncluded) }
}
func map<S: Sequence, T>(_ transform: @escaping (S.Element) throws -> T) -> (S) throws -> [T] {
return { try [=10=].map(transform) }
}
func map<L: LazySequenceProtocol, T>(_ transform: @escaping (L.Elements.Element) -> T) -> (L) -> LazyMapSequence<L.Elements, T> {
return { [=10=].map(transform) }
}
func flatMap<S: Sequence, T: Sequence>(_ transform: @escaping (S.Element) throws -> T) -> (S) throws -> [T.Element] {
return { try [=10=].flatMap(transform) }
}
func flatMap<L: LazySequenceProtocol, S: Sequence>(_ transform: @escaping (L.Elements.Element) -> S) -> (L) -> LazySequence<FlattenSequence<LazyMapSequence<L.Elements, S>>> {
return { [=10=].flatMap(transform) }
}
请注意,惰性序列对应项比常规 Sequence
更冗长,但这是由于 LazySequenceProtocol
方法的冗长。
通过上面我们可以创建接收数组和return数组的泛型函数,这种类型的函数非常适合流水线,所以让我们定义一个流水线运算符:
func |<T, U>(_ arg: T, _ f: (T) -> U) -> U {
return f(arg)
}
现在我们只需要为这些函数提供一些东西,但要实现这一点,我们需要对 Transform
类型进行一些调整:
typealias Transform<T, U> = (T) -> U
let containsA: Transform<String, Bool> = { [=12=].contains("a") }
let plural: Transform<String, String> = { [=12=] + "s" }
let double: Transform<String, [String]> = { [[=12=], [=12=]] }
有了以上所有内容,事情就变得简单明了:
let animals = ["bear", "dog", "cat"]
let newAnimals = lazy(animals) | filter(containsA) | map(plural) | flatMap(double)
print(Array(newAnimals)) // ["bears", "bears", "cats", "cats"]
我有一份动物清单:
let animals = ["bear", "dog", "cat"]
以及转换该列表的一些方法:
typealias Transform = (String) -> [String]
let containsA: Transform = { [=12=].contains("a") ? [[=12=]] : [] }
let plural: Transform = { [[=12=] + "s"] }
let double: Transform = { [[=12=], [=12=]] }
顺便说一句,它们分别类似于 filter(输出 0 或 1 个元素)、map(恰好 1 个元素)和 flatmap(多于 1 个元素),但以统一的方式定义,以便可以处理它们始终如一。
我想创建一个惰性迭代器,它将这些转换的数组应用于动物列表:
extension Array where Element == String {
func transform(_ transforms: [Transform]) -> AnySequence<String> {
return AnySequence<String> { () -> AnyIterator<String> in
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
}
}
}
这意味着我可以懒惰地做:
let transformed = animals.transform([containsA, plural, double])
并检查结果:
print(Array(transformed))
我很高兴这很简洁但很清楚:
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
是一个问题,因为它意味着变换函数只能用于 3 个变换的数组。
编辑: 我试过了:
var lazyCollection = self.lazy
for transform in transforms {
lazyCollection = lazyCollection.flatMap(transform) //Error
}
var iterator = lazyCollection.makeIterator()
但在标记的行上出现错误:
无法将类型 'LazyCollection< FlattenCollection< LazyMapCollection< Array< String>, [String]>>>' 的值分配给类型 'LazyCollection< Array< String>>'
这是我理解的,因为每次循环都会添加另一个平面图,所以类型会发生变化。
如何使变换函数与任意数量的变换数组一起工作?
一个用于有限数量转换的 WET 解决方案是(但是 YUK!)
switch transforms.count {
case 1:
var iterator = self
.lazy
.flatMap(transforms[0])
.makeIterator()
return AnyIterator {
return iterator.next()
}
case 2:
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.makeIterator()
return AnyIterator {
return iterator.next()
}
case 3:
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
default:
fatalError(" Too many transforms!")
}
整个代码:
let animals = ["bear", "dog", "cat"]
typealias Transform = (String) -> [String]
let containsA: Transform = { [=19=].contains("a") ? [[=19=]] : [] }
let plural: Transform = { [[=19=] + "s"] }
let double: Transform = { [[=19=], [=19=]] }
extension Array where Element == String {
func transform(_ transforms: [Transform]) -> AnySequence<String> {
return AnySequence<String> { () -> AnyIterator<String> in
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
}
}
}
let transformed = animals.transform([containsA, plural, double])
print(Array(transformed))
如果您在 Sequence
协议(而不是 Array
)上定义方法,则可以递归地应用转换。如果将转换参数定义为 (Element) -> [Element]
.
where Element == String
extension Sequence {
func transform(_ transforms: [(Element) -> [Element]]) -> AnySequence<Element> {
if transforms.isEmpty {
return AnySequence(self)
} else {
return lazy.flatMap(transforms[0]).transform(Array(transforms[1...]))
}
}
}
另一种实现你想要的方法:
Edit: I tried:
var lazyCollection = self.lazy for transform in transforms { lazyCollection = lazyCollection.flatMap(transform) //Error } var iterator = lazyCollection.makeIterator()
你已经非常接近你的目标了,如果 Error 行中的两种类型都是可分配的,你的代码就可以工作。
稍作修改:
var lazySequence = AnySequence(self.lazy)
for transform in transforms {
lazySequence = AnySequence(lazySequence.flatMap(transform))
}
var iterator = lazySequence.makeIterator()
或者您可以在此处使用 reduce
:
var transformedSequence = transforms.reduce(AnySequence(self.lazy)) {sequence, transform in
AnySequence(sequence.flatMap(transform))
}
var iterator = transformedSequence.makeIterator()
整个代码为:
(编辑 修改为包含 Martin R 的建议。)
let animals = ["bear", "dog", "cat"]
typealias Transform<Element> = (Element) -> [Element]
let containsA: Transform<String> = { [=13=].contains("a") ? [[=13=]] : [] }
let plural: Transform<String> = { [[=13=] + "s"] }
let double: Transform<String> = { [[=13=], [=13=]] }
extension Sequence {
func transform(_ transforms: [Transform<Element>]) -> AnySequence<Element> {
return transforms.reduce(AnySequence(self)) {sequence, transform in
AnySequence(sequence.lazy.flatMap(transform))
}
}
}
let transformed = animals.transform([containsA, plural, double])
print(Array(transformed))
如何将其完全纳入功能世界?例如使用(动态)函数调用链,如 filter(containsA) | map(plural) | flatMap(double)
.
通过一些可重用的通用代码,我们可以实现一些不错的东西。
让我们从将一些序列和惰性序列操作提升为自由函数开始:
func lazy<S: Sequence>(_ arr: S) -> LazySequence<S> {
return arr.lazy
}
func filter<S: Sequence>(_ isIncluded: @escaping (S.Element) throws -> Bool) -> (S) throws -> [S.Element] {
return { try [=10=].filter(isIncluded) }
}
func filter<L: LazySequenceProtocol>(_ isIncluded: @escaping (L.Elements.Element) -> Bool) -> (L) -> LazyFilterSequence<L.Elements> {
return { [=10=].filter(isIncluded) }
}
func map<S: Sequence, T>(_ transform: @escaping (S.Element) throws -> T) -> (S) throws -> [T] {
return { try [=10=].map(transform) }
}
func map<L: LazySequenceProtocol, T>(_ transform: @escaping (L.Elements.Element) -> T) -> (L) -> LazyMapSequence<L.Elements, T> {
return { [=10=].map(transform) }
}
func flatMap<S: Sequence, T: Sequence>(_ transform: @escaping (S.Element) throws -> T) -> (S) throws -> [T.Element] {
return { try [=10=].flatMap(transform) }
}
func flatMap<L: LazySequenceProtocol, S: Sequence>(_ transform: @escaping (L.Elements.Element) -> S) -> (L) -> LazySequence<FlattenSequence<LazyMapSequence<L.Elements, S>>> {
return { [=10=].flatMap(transform) }
}
请注意,惰性序列对应项比常规 Sequence
更冗长,但这是由于 LazySequenceProtocol
方法的冗长。
通过上面我们可以创建接收数组和return数组的泛型函数,这种类型的函数非常适合流水线,所以让我们定义一个流水线运算符:
func |<T, U>(_ arg: T, _ f: (T) -> U) -> U {
return f(arg)
}
现在我们只需要为这些函数提供一些东西,但要实现这一点,我们需要对 Transform
类型进行一些调整:
typealias Transform<T, U> = (T) -> U
let containsA: Transform<String, Bool> = { [=12=].contains("a") }
let plural: Transform<String, String> = { [=12=] + "s" }
let double: Transform<String, [String]> = { [[=12=], [=12=]] }
有了以上所有内容,事情就变得简单明了:
let animals = ["bear", "dog", "cat"]
let newAnimals = lazy(animals) | filter(containsA) | map(plural) | flatMap(double)
print(Array(newAnimals)) // ["bears", "bears", "cats", "cats"]