Swift:Lazily封装了map,filter,flatMap的链

Ada*_*hus 6 functional-programming flatmap swift

我有一份动物名单:

let animals = ["bear", "dog", "cat"]
Run Code Online (Sandbox Code Playgroud)

以及一些转换该列表的方法:

typealias Transform = (String) -> [String]

let containsA: Transform = { $0.contains("a") ? [$0] : [] }
let plural:    Transform = { [$0 + "s"] }
let double:    Transform = { [$0, $0] }
Run Code Online (Sandbox Code Playgroud)

稍微说一下,它们分别类似于过滤器(输出0或1个元素),映射(正好是1个元素)和平面图(多于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()
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这意味着我可以懒惰地做:

let transformed = animals.transform([containsA, plural, double])
Run Code Online (Sandbox Code Playgroud)

并检查结果:

print(Array(transformed))
Run Code Online (Sandbox Code Playgroud)

我很高兴这是多么简洁,但很明显:

        .flatMap(transforms[0])
        .flatMap(transforms[1])
        .flatMap(transforms[2])
Run Code Online (Sandbox Code Playgroud)

是一个问题,因为它意味着转换函数只适用于3个转换的数组.

编辑: 我试过:

  var lazyCollection = self.lazy
  for transform in transforms {
    lazyCollection = lazyCollection.flatMap(transform) //Error
  }
  var iterator = lazyCollection.makeIterator()
Run Code Online (Sandbox Code Playgroud)

但在标记的行上我得到错误:

无法分配类型'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!")
  }
Run Code Online (Sandbox Code Playgroud)

整码:

let animals = ["bear", "dog", "cat"]

typealias Transform = (String) -> [String]

let containsA: Transform = { $0.contains("a") ? [$0] : [] }
let plural:    Transform = { [$0 + "s"] }
let double:    Transform = { [$0, $0] }

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))
Run Code Online (Sandbox Code Playgroud)

Mar*_*n R 7

如果在Sequence协议上定义方法(而不是Array),则可以递归地应用转换.where Element == String如果将transformations参数定义为数组,则不需要约束(Element) -> [Element].

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...]))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


OOP*_*Per 5

另一种实现你想要的方法:

编辑:我尝试过:

var lazyCollection = self.lazy
for transform in transforms {
    lazyCollection = lazyCollection.flatMap(transform) //Error
}
var iterator = lazyCollection.makeIterator()
Run Code Online (Sandbox Code Playgroud)

您非常接近您的目标,如果错误行中的两种类型都是可分配的,您的代码就会起作用。

稍微修改一下:

var lazySequence = AnySequence(self.lazy)
for transform in transforms {
    lazySequence = AnySequence(lazySequence.flatMap(transform))
}
var iterator = lazySequence.makeIterator()
Run Code Online (Sandbox Code Playgroud)

或者您可以在这里使用reduce

var transformedSequence = transforms.reduce(AnySequence(self.lazy)) {sequence, transform in
    AnySequence(sequence.flatMap(transform))
}
var iterator = transformedSequence.makeIterator()
Run Code Online (Sandbox Code Playgroud)

整个代码是:

编辑修改以包括 Martin R 的建议。)

let animals = ["bear", "dog", "cat"]

typealias Transform<Element> = (Element) -> [Element]

let containsA: Transform<String> = { $0.contains("a") ? [$0] : [] }
let plural:    Transform<String> = { [$0 + "s"] }
let double:    Transform<String> = { [$0, $0] }

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))
Run Code Online (Sandbox Code Playgroud)