我正在尝试将元素与集合中的下一个元素进行比较。
例如:
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
}
}
答案 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
}