快速运行总和

时间:2016-02-02 18:13:46

标签: swift

我想在一个数字数组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,除了它构建一个具有所有中间值的结果数组。

更通用的是拥有一个接受任何序列的函数,并提供一个序列,它是输入序列的运行总和。

8 个答案:

答案 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)

Swift 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的类型,因此坚韧不拔{封闭中的{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(_:_:)或a   Array

另请参阅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]

Swift 3:使用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替代方案(带有开销......)在每个元素映射中使用reduceenumerated().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() }
}