我想在一个数字数组a(或任何可添加的东西的有序集合)上有一个函数runningSum
,它返回一个长度相同的数组,其中每个元素i
是所有元素的总和A 中的元素,包括i
。
示例:
runningSum([1,1,1,1,1,1]) -> [1,2,3,4,5,6]
runningSum([2,2,2,2,2,2]) -> [2,4,6,8,10,12]
runningSum([1,0,1,0,1,0]) -> [1,1,2,2,3,3]
runningSum([0,1,0,1,0,1]) -> [0,1,1,2,2,3]
我可以使用for循环或其他任何方式执行此操作。有更多功能选项吗?它有点像reduce,除了它构建一个具有所有中间值的结果数组。
更通用的是拥有一个接受任何序列的函数,并提供一个序列,它是输入序列的运行总和。
答案 0 :(得分:8)
您正在寻找的一般组合子通常称为scan
,并且可以reduce
定义(就像列表上的所有高阶函数一样):
extension Array {
func scan<T>(initial: T, _ f: (T, Element) -> T) -> [T] {
return self.reduce([initial], combine: { (listSoFar: [T], next: Element) -> [T] in
// because we seeded it with a non-empty
// list, it's easy to prove inductively
// that this unwrapping can't fail
let lastElement = listSoFar.last!
return listSoFar + [f(lastElement, next)]
})
}
}
(但我建议这不是一个很好的实现。)
这是一个非常有用的通用功能,遗憾的是它没有包含在标准库中。
然后,您可以通过专门化起始值和操作来生成累积总和:
let cumSum = els.scan(0, +)
你可以简单地省略零长度的情况:
let cumSumTail = els.scan(0, +).dropFirst()
答案 1 :(得分:8)
引用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
的类型,因此坚韧不拔{封闭中的{1}}。
或者:自定义序列累加器
... 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
从Swift 4开始,我们可以使用reduce(into:_:)
将运行总和累积到数组中。
reduce(into:_:)
通过使用let runningSum = arr
.reduce(into: []) { $0.append(($0.last ?? 0) + $1) }
// [2, 4, 6, 8, 10, 12]
,reduce(into:_:)
累加器将不会在后续的reduce迭代中复制;引用Language reference:
此方法优于
[Int]
以提高效率 结果是写时复制类型,例如reduce(_:_:)
或aArray
。
另请参阅implementation of reduce(into:_:)
,注意累加器是作为Dictionary
参数提供给提供的闭包。
但是,每次迭代仍然会在累加器数组上调用append(_:)
;在许多调用中平均分摊inout
,但由于我们知道累加器的最终大小,因此这仍然是一个可以说是不必要的开销。
因为数组使用指数增加其分配的容量 策略,将单个元素附加到数组是
O(1)
操作 平均多次调用O(1)
方法时。当一个数组 有额外的容量,并没有与另一个共享其存储 实例,追加元素是append(_:)
。当一个数组需要时 在追加或共享存储之前重新分配存储 另一个副本,追加是O(1)
,其中n是数组的长度。
因此,知道累加器的最终大小,我们可以使用reserveCapacity(_:)
明确保留这样的容量(例如对the native implementation of map(_:)
所做的)
O(n)
为了它的喜悦,凝聚了:
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]
进行enumerated()
另一个Swift 3替代方案(带有开销......)在每个元素映射中使用reduce
与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
)。
答案 2 :(得分:5)
只是为了好玩:作为一个单行的运行总和:
let arr = [1, 2, 3, 4]
let rs = arr.map({ () -> (Int) -> Int in var s = 0; return { (s += $0, s).1 } }())
print(rs) // [1, 3, 6, 10]
它与JAL's answer中的(更新的)代码相同,特别是 没有生成中间数组。 sum变量在一个立即计算的闭包中捕获,返回转换。
答案 3 :(得分:3)
假设有一个Int
数组,听起来你可以使用map
来操纵输入:
let arr = [0,1,0,1,0,1]
var sum = 0
let val = arr.map { (sum += $0, sum).1 }
print(val) // "[0, 1, 1, 2, 2, 3]\n"
我将继续研究不使用外部变量的解决方案。
答案 4 :(得分:3)
如果您只想让它适用于help('$')
,您可以使用此功能:
Int
如果你希望它在元素类型上是通用的,你必须做很多额外的工作,声明各种数字类型符合提供零元素的自定义协议,并且(如果你希望它在两者上都是通用的)浮点和整数类型)一个加法运算,因为Swift已经没有这样做了。 (未来的Swift版本可以解决这个问题。)
答案 5 :(得分:1)
使用reduce
的一个解决方案:
func runningSum(array: [Int]) -> [Int] {
return array.reduce([], combine: { (result: [Int], item: Int) -> [Int] in
if result.isEmpty {
return [item] //first item, just take the value
}
// otherwise take the previous value and append the new item
return result + [result.last! + item]
})
}
答案 6 :(得分:1)
我认为我很乐意像出色的first answer所建议的那样,使用通用的Sequence
函数扩展scan
。
鉴于此扩展名,您可以像这样获得数组的运行总和:[1,2,3].scan(0, +)
但是您还可以得到其他有趣的东西……
array.scan(1, *)
array.scan(Int.min, max)
array.scan(Int.max, min)
由于实现是Sequence
上的一个函数并返回一个Sequence
,因此可以将其与其他序列函数链接在一起。效率高,具有线性运行时间。
这是扩展名...
extension Sequence {
func scan<Result>(_ initialResult: Result, _ nextPartialResult: @escaping (Result, Self.Element) -> Result) -> ScanSequence<Self, Result> {
return ScanSequence(initialResult: initialResult, underlying: self, combine: nextPartialResult)
}
}
struct ScanSequence<Underlying: Sequence, Result>: Sequence {
let initialResult: Result
let underlying: Underlying
let combine: (Result, Underlying.Element) -> Result
typealias Iterator = ScanIterator<Underlying.Iterator, Result>
func makeIterator() -> Iterator {
return ScanIterator(previousResult: initialResult, underlying: underlying.makeIterator(), combine: combine)
}
var underestimatedCount: Int {
return underlying.underestimatedCount
}
}
struct ScanIterator<Underlying: IteratorProtocol, Result>: IteratorProtocol {
var previousResult: Result
var underlying: Underlying
let combine: (Result, Underlying.Element) -> Result
mutating func next() -> Result? {
guard let nextUnderlying = underlying.next() else {
return nil
}
previousResult = combine(previousResult, nextUnderlying)
return previousResult
}
}
答案 7 :(得分:0)
我参加这个聚会很晚了。其他答案有很好的解释。但是他们都没有以通用的方式提供初始结果。这个实现对我很有用。
public extension Sequence {
/// A sequence of the partial results that `reduce` would employ.
func scan<Result>(
_ initialResult: Result,
_ nextPartialResult: @escaping (Result, Element) -> Result
) -> AnySequence<Result> {
var iterator = makeIterator()
return .init(
sequence(first: initialResult) { partialResult in
iterator.next().map {
nextPartialResult(partialResult, $0)
}
}
)
}
}
extension Sequence where Element: AdditiveArithmetic & ExpressibleByIntegerLiteral {
var runningSum: AnySequence<Element> { scan(0, +).dropFirst() }
}