斯威夫特 4
一般序列案例
引用 OP:
更一般的方法是有一个可以接受任何序列的函数
并提供一个序列,它是输入的总和
顺序。
考虑一些任意序列(符合Sequence),比如说
var seq = 1... // 1, 2, 3, ... (CountablePartialRangeFrom)
要创建另一个序列,它是 seq 上的(惰性)运行总和,您可以使用全局 sequence(state:next:) 函数:
var runningSumSequence =
sequence(state: (sum: 0, it: seq.makeIterator())) { state -> Int? in
if let val = state.it.next() {
defer { state.sum += val }
return val + state.sum
}
else { return nil }
}
// Consume and print accumulated values less than 100
while let accumulatedSum = runningSumSequence.next(),
accumulatedSum < 100 { print(accumulatedSum) }
// 1 3 6 10 15 21 28 36 45 55 66 78 91
// Consume and print next
print(runningSumSequence.next() ?? -1) // 120
// ...
如果我们愿意(为了它的乐趣),我们可以将闭包压缩为上面的sequence(state:next:):
var runningSumSequence =
sequence(state: (sum: 0, it: seq.makeIterator())) {
(state: inout (sum: Int, it: AnyIterator<Int>)) -> Int? in
state.it.next().map { (state.sum + $0, state.sum += $0).0 }
}
但是,对于sequence(state:next:) 的这些单行返回,类型推断往往会中断(也许还有一些未解决的错误?),迫使我们显式指定state 的类型,因此... in 在关闭。
或者:自定义序列累加器
protocol Accumulatable {
static func +(lhs: Self, rhs: Self) -> Self
}
extension Int : Accumulatable {}
struct AccumulateSequence<T: Sequence>: Sequence, IteratorProtocol
where T.Element: Accumulatable {
var iterator: T.Iterator
var accumulatedValue: T.Element?
init(_ sequence: T) {
self.iterator = sequence.makeIterator()
}
mutating func next() -> T.Element? {
if let val = iterator.next() {
if accumulatedValue == nil {
accumulatedValue = val
}
else { defer { accumulatedValue = accumulatedValue! + val } }
return accumulatedValue
}
return nil
}
}
var accumulator = AccumulateSequence(1...)
// Consume and print accumulated values less than 100
while let accumulatedSum = accumulator.next(),
accumulatedSum < 100 { print(accumulatedSum) }
// 1 3 6 10 15 21 28 36 45 55 66 78 91
具体数组情况:使用reduce(into:_:)
从 Swift 4 开始,我们可以使用 reduce(into:_:) 将运行总和累加到一个数组中。
let runningSum = arr
.reduce(into: []) { $0.append(($0.last ?? 0) + $1) }
// [2, 4, 6, 8, 10, 12]
通过使用reduce(into:_:),[Int] 累加器将不会在后续的reduce迭代中被复制;引用Language reference:
此方法优于reduce(_:_:)
结果是写时复制类型,例如 Array 或
Dictionary.
另请参阅implementation of reduce(into:_:),注意累加器作为inout 参数提供给提供的闭包。
但是,每次迭代仍然会导致对累加器数组的append(_:) 调用; amortized O(1) 是多次调用的平均值,但这里仍然可以说是不必要的开销,因为我们知道累加器的最终大小。
因为数组使用指数增加其分配的容量
策略,将单个元素附加到数组是 O(1) 操作
当对append(_:) 方法的多次调用进行平均时。当一个数组
具有额外容量并且不与其他人共享其存储空间
例如,附加一个元素是O(1)。当一个数组需要
在追加之前重新分配存储或其存储共享
另一个副本,追加的是O(n),其中n是数组的长度。
因此,知道了累加器的最终大小,我们可以使用reserveCapacity(_:) 为它显式保留这样的容量(就像the native implementation of map(_:) 所做的那样)
let runningSum = arr
.reduce(into: [Int]()) { (sums, element) in
if let sum = sums.last {
sums.append(sum + element)
}
else {
sums.reserveCapacity(arr.count)
sums.append(element)
}
} // [2, 4, 6, 8, 10, 12]
为了它的喜悦,浓缩:
let runningSum = arr
.reduce(into: []) {
$0.append(($0.last ?? ($0.reserveCapacity(arr.count), 0).1) + $1)
} // [2, 4, 6, 8, 10, 12]
Swift 3:使用enumerated() 进行后续调用reduce
另一个 Swift 3 替代方案(有开销...)是在每个元素映射中结合使用 enumerated().map 和 reduce:
func runningSum(_ arr: [Int]) -> [Int] {
return arr.enumerated().map { arr.prefix($0).reduce($1, +) }
} /* thanks @Hamish for improvement! */
let arr = [2, 2, 2, 2, 2, 2]
print(runningSum(arr)) // [2, 4, 6, 8, 10, 12]
好处是您不必在单个 reduce 中使用数组作为收集器(而不是重复调用 reduce)。