在地图上获取下一个值?

时间:2017-02-22 13:19:58

标签: swift functional-programming

我正在尝试将元素与集合中的下一个元素进行比较。

例如:

let array: [(Double, String)]= [(2.3, "ok"),
                                (1.4, "ok"),
                                (5.1, "notOk")]

我需要一个返回的数组,它将汇总字符串相同的元素。所以我的结果将是:

new array = [(3.7, "ok"), (5.1, "notOk")]

如果可能,我需要做它的功能。我试图在地图中获取下一个元素,但无法找到。

这样的事情(这只是为了逻辑,这段代码不起作用。

let newArray = array.map {(element, nextElement) in 
    if element.1 == nextElement.1 {
        return element.0 + nextElement.0 
    }
} 

4 个答案:

答案 0 :(得分:7)

以更具功能性的方式:

let array: [(Double, String)]= [(2.3, "ok"),
                                (1.4, "ok"),
                                (5.1, "notOk")]
let keys = Set(array.map{$0.1})            // find unique keys
let result = keys.map { key -> (Double, String) in   
    let sum = array.filter {$0.1 == key}   // find all entries with the current key
                   .map {$0.0}             // map them to their values
                   .reduce(0, +)           // sum the values
    return (sum, key)
}
print(result)

输出:

  

[(5.0999999999999996,“notOk”),(3.6999999999999997,“ok”)]

或者(由@dfri建议):

let keys = Set(array.map{$0.1})            // find unique keys
let result = keys.map { key -> (Double, String) in   
    let sum = array.reduce(0) { $0 + ($1.1 == key ? $1.0 : 0) }
    return (sum, key)
}

答案 1 :(得分:4)

我喜欢alexburtnik的回答。我基本上是逐字逐句地写了我的第一遍。它简单明了,高效。这是非常好的斯威夫特。

但是函数式编程可以帮助我们更深入地思考问题并创建更好,可重用的工具。让我们从功能上思考。

dfri的解决方案看起来很漂亮,但是是O(m * n)(在最坏的情况下,O(n ^ 2))。它为每个唯一键循环遍历整个数组。这让Alan Perlis回到了古老的格言:“一个Lisp程序员知道一切的价值和无成本。”但函数式编程不一定非常低效。

函数式编程的目的是将复杂问题分解为更简单的问题,使这些更简单的问题变得通用,然后重新组合它们。这不是关于过滤器和flatMaps。

所以让我们分解这个问题。我们希望按键分组,然后对每个键的值求和。如果我们先按键排序,按键分组会更容易:

let result = array
    .sorted(by: { $0.1 < $1.1 })

现在,我们希望我们可以将它们分组:

let result = array
    .sorted(by: { $0.1 < $1.1 })
    .grouped(by: { $0.1 == $1.1 })

我希望我有grouped(by:)Wish fulfillment is the heart of functional programming,让我们写下来。好吧,一个组是一系列元素,对于某些“相等”的值,它们都是“相等的”。我们可以这样建立:

extension Array {
    func grouped(by equal: (Element, Element) -> Bool) -> [[Element]] {
        guard let firstElement = first else { return [] }
        guard let splitIndex = index(where: { !equal($0, firstElement) } ) else { return [self] }
        return [Array(prefix(upTo: splitIndex))] + Array(suffix(from: splitIndex)).grouped(by: equal)
    }

那就是说,我真的不喜欢这段代码。它不是非常Swifty。那[Array(prefix(...)] +很好地说明了斯威夫特多么讨厌我们这样做。由于复制它可能非常昂贵(可能让我们回到O(n ^ 2).Swiftier解决方案将是一个序列:

struct GroupedSequence<Element>: Sequence, IteratorProtocol {
    var elements: [Element]
    let equal: (Element, Element) -> Bool
    private var nextIndex = 0
    init(of elements: [Element], by equal: @escaping (Element, Element) -> Bool) {
        self.elements = elements
        self.equal = equal
    }

    mutating func next() -> ArraySlice<Element>? {
        guard nextIndex < elements.endIndex else { return nil }
        let first = elements[nextIndex]
        let splitIndex = elements[nextIndex..<elements.endIndex].index(where: { !equal($0, first) } ) ?? elements.endIndex
        defer { nextIndex = splitIndex }
        return elements[nextIndex..<splitIndex]
    }
}

extension Array {
    func grouped(by equal: @escaping (Element, Element) -> Bool) -> GroupedSequence<Element> {
        return GroupedSequence(elements: self, equal: equal)
    }
}

是的,它变异,它是一些更多的代码,但它也是懒惰的(这是函数式编程的关键工具),它更好的Swift,并且非常可重用。我喜欢。但如果您愿意,可以使用递归的纯版本。

好的,现在我们有一组等效的数组。我们想要映射这些并将每个元素减少到它的总和。所以我们将在地图内部进行缩减。但这不是O(n ^ 2),因为每个reduce只在一个切片上。我们只会走一次每个元素。为了处理一个不可能的角落案例(一个空组,grouped(by:)永远不会创建),我们将使用flatMap,但它实际上只是一张地图。你可能想跳到这个,但不要这样做:

let result: [(Double, String)] = array
    .sorted(by: { $0.1 < $1.1 })
    .grouped(by: { $0.1 == $1.1 })
    .flatMap { group in
        guard let key = group.first?.1 else { return nil }
        return (group.reduce(0, { $0 + $1.0 }), // Sum of our values
                key)
}

为什么呢?那是非常难以理解的。这就是函数式编程的一个坏名称。最后一块做什么了?不,我们需要功能组合,而不仅仅是功能工具。所以我们提取一个函数:

func sumEach(pairGroup: ArraySlice<(Double, String)>) -> (Double, String)? {
    guard let key = pairGroup.first?.1 else { return nil }
    return (pairGroup.reduce(0, { $0 + $1.0 }), // Sum of our values
        key)
}

现在,我们可以在不牺牲理解的情况下获得良好的功能性方法:

let result = array
    .sorted(by: { $0.1 < $1.1 })
    .grouped(by: { $0.1 == $1.1 })
    .flatMap(sumEach(pairGroup:))

在此过程中,我们创建了一个新工具,分组,我们可以用它来组成其他解决方案。我觉得这很不错。

但我仍然可能会按照alexburtnik的方式去做。

答案 2 :(得分:3)

你可以遍历输入数组中的每个tupple并将一个总和保存在这样的字典中:

let array: [(Double, String)] = [(1.0,"notok"),(2.0,"ok"),(3.0,"ok"),(4.0,"ok"),(5.0,"ok"),(6.0,"ok"), (7.0,"notok")]

var dict = [String: Double]()
for (value, key) in array {
    dict[key] = (dict[key] ?? 0) + value
}
print ("dict: \(dict)")

输出:

  

dict:[&#34; notok&#34;:8.0,&#34; ok&#34;:20.0]

如果你真的需要获得一组元组,请使用:

let result = dict.map { (key, value) in (value, key) }
print ("result: \(result)")

输出:

  

结果:[(8.0,&#34; notok&#34;),(20.0,&#34; ok&#34;)]

答案 3 :(得分:1)

我想一个充分利用Swift功能的解决方案是结合过滤器 reduce

    let array: [(String, Double)] = [("ok", 2.4),
                                     ("ok", 1.3),
                                     ("not ok", 4.4),
                                     ("very not ok", 99.0)]
    let key = "ok"
    let result = array.filter({$0.0 != key}) + [array.filter({ $0.0 == key }).reduce((key, 0.0), { (key, $0.1 + $1.1) })]
    print(result)

然后结果将是

  

[(“not ok”,4.4000000000000004),(“非常不好”,99.0),(“ok”,3.7000000000000002)]

我认为这是你想要实现的目标。

编辑:

要减少所有元组,您只需将解决方案包装在函数内:

    func reduceAllTuples(tupleArray: [(String, Double)]) -> [(String, Double)]{
        var array = tupleArray
        for (key, _) in tupleArray {
            array = array.filter({$0.0 != key}) + [array.filter({ $0.0 == key }).reduce((key, 0.0), { (key, $0.1 + $1.1) })]
        }
        return array
    }