Swift: лениво инкапсулирующие цепочки map, filter, flatMap - PullRequest
0 голосов
/ 27 января 2019

У меня есть список животных:

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] }

В некотором смысле, они аналогичны фильтру (выходы 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))

IЯ доволен, насколько это кратко, но ясно:

        .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 ,[String] >>> «набрать» LazyCollection > '

, которыйЯ понимаю, потому что каждый раз вокруг цикла добавляется еще одна плоская карта, поэтому тип меняется.

Как заставить функцию преобразования работать с массивом любого числа преобразований?

Одно решение 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 = { $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))

Ответы [ 3 ]

0 голосов
/ 27 января 2019

Другой подход к достижению того, что вы хотите:

Редактировать : Я пытался:

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> = { $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))
0 голосов
/ 28 января 2019

Как насчет того, чтобы полностью перенести это в функциональный мир?Например, используя (динамические) цепочки вызовов функций, такие как 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 $0.filter(isIncluded) }
}

func filter<L: LazySequenceProtocol>(_ isIncluded: @escaping (L.Elements.Element) -> Bool) -> (L) -> LazyFilterSequence<L.Elements> {
    return { $0.filter(isIncluded) }
}

func map<S: Sequence, T>(_ transform: @escaping (S.Element) throws -> T) -> (S) throws -> [T] {
    return { try $0.map(transform) }
}

func map<L: LazySequenceProtocol, T>(_ transform: @escaping (L.Elements.Element) -> T) -> (L) -> LazyMapSequence<L.Elements, T> {
    return { $0.map(transform) }
}

func flatMap<S: Sequence, T: Sequence>(_ transform: @escaping (S.Element) throws -> T) -> (S) throws -> [T.Element] {
    return { try $0.flatMap(transform) }
}

func flatMap<L: LazySequenceProtocol, S: Sequence>(_ transform: @escaping (L.Elements.Element) -> S) -> (L) -> LazySequence<FlattenSequence<LazyMapSequence<L.Elements, S>>> {
    return { $0.flatMap(transform) }
}

Обратите внимание, что аналоги ленивых последовательностей более многословны, чем обычные Sequence, но это связано ск многословию LazySequenceProtocol методов.

С помощью вышеизложенного мы можем создавать универсальные функции, которые получают массивы и возвращают массивы, и этот тип функций чрезвычайно приспособлен для конвейерной обработки, поэтому давайте определим оператор конвейера:

func |<T, U>(_ arg: T, _ f: (T) -> U) -> U {
    return f(arg)
}

Теперь все, что нам нужно, это передать что-то этим функциям, но для достижения этого нам нужно немного настроить тип Transform:

typealias Transform<T, U> = (T) -> U

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

Со всемиС учетом вышесказанного все становится просто и понятно:

let animals = ["bear", "dog", "cat"]
let newAnimals = lazy(animals) | filter(containsA) | map(plural) | flatMap(double)
print(Array(newAnimals)) // ["bears", "bears", "cats", "cats"]
0 голосов
/ 27 января 2019

Вы можете применить преобразования рекурсивно, если определите метод в протоколе Sequence (вместо Array).Также ограничение where Element == String не требуется, если параметр преобразований определен как массив (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...]))
        }
    }
}
...