【问题标题】:Swift equivalent of Python slice assignmentSwift 等价于 Python 切片赋值
【发布时间】:2016-03-19 07:54:17
【问题描述】:

在 Python 中,可以有一个列表(类似于 swift 中的数组):

>>> li=[0,1,2,3,4,5]

并对列表的任何/所有执行切片分配:

>>> li[2:]=[99]         # note then end index is not needed if you mean 'to the end'
>>> li
[0, 1, 99]

Swift 有一个相似的 切片分配(这是在swift 交互式shell 中):

  1> var arr=[0,1,2,3,4,5]
arr: [Int] = 6 values {
  [0] = 0
  [1] = 1
  [2] = 2
  [3] = 3
  [4] = 4
  [5] = 5
}
  2> arr[2...arr.endIndex-1]=[99]
  3> arr
$R0: [Int] = 3 values {
  [0] = 0
  [1] = 1
  [2] = 99
}

到目前为止,一切都很好。但是,有几个问题。

首先,swift 不适用于空列表或索引在endIndex 之后。如果切片索引在结束索引之后,Python 会追加:

>>> li=[]             # empty
>>> li[2:]=[6,7,8]
>>> li
[6, 7, 8]
>>> li=[0,1,2]
>>> li[999:]=[999]
>>> li
[0, 1, 2, 999]

swift中的等价物是错误:

  4> var arr=[Int]()
arr: [Int] = 0 values
  5> arr[2...arr.endIndex-1]=[99]
fatal error: Can't form Range with end < start

这很容易测试和编码。

第二个问题是杀手:它真的很慢。考虑这个 Python 代码来执行浮点列表的精确求和:

def msum(iterable):
    "Full precision summation using multiple floats for intermediate values"
    # Rounded x+y stored in hi with the round-off stored in lo.  Together
    # hi+lo are exactly equal to x+y.  The inner loop applies hi/lo summation
    # to each partial so that the list of partial sums remains exact.
    # Depends on IEEE-754 arithmetic guarantees.  See proof of correctness at:
    # www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps

    partials = []               # sorted, non-overlapping partial sums
    for x in iterable:
        i = 0
        for y in partials:
            if abs(x) < abs(y):
                x, y = y, x
            hi = x + y
            lo = y - (hi - x)
            if lo:
                partials[i] = lo
                i += 1
            x = hi
        partials[i:] = [x]
    return sum(partials, 0.0)

它通过保持高/低部分求和来工作,以便msum([.1]*10) 准确生成1.0 而不是0.9999999999999999。 msum 的 C 等效项是 Python 中 math library 的一部分。

我试图快速复制:

func msum(it:[Double])->Double {
    // Full precision summation using multiple floats for intermediate values 
    var partials=[Double]()
    for var x in it {
        var i=0
        for var y in partials{
            if abs(x) < abs(y){
                (x, y)=(y, x)
            }
            let hi=x+y
            let lo=y-(hi-x)
            if abs(lo)>0.0 {
                partials[i]=lo
                i+=1
            }
            x=hi
        }
        // slow part trying to replicate Python's slice assignment partials[i:]=[x]
        if partials.endIndex>i {
            partials[i...partials.endIndex-1]=[x]
        }
        else {
            partials.append(x)
        }    
    } 
    return partials.reduce(0.0, combine: +)
}

测试功能和速度:

import Foundation
var arr=[Double]()
for _ in 1...1000000 {
    arr+=[10, 1e100, 10, -1e100]
    }

print(arr.reduce(0, combine: +))    // will be 0.0
var startTime: CFAbsoluteTime!
startTime = CFAbsoluteTimeGetCurrent()
print(msum(arr), arr.count*5)          // should be arr.count * 5
print(CFAbsoluteTimeGetCurrent() - startTime)

在我的机器上,这需要 7 秒才能完成。 Python 原生 msum 需要 2.2 秒(大约快 4 倍),库 fsum 函数需要 0.09 秒(快大约 90 倍)

我尝试用arr.removeRange(i..&lt;arr.endIndex) 替换partials[i...partials.endIndex-1]=[x],然后追加。快一点但不多。

问题:

  1. 这是惯用的swift:partials[i...partials.endIndex-1]=[x]
  2. 有更快/更好的方法吗?

【问题讨论】:

  • 很抱歉问了一个显而易见的问题:您是否在发布配置中编译了 Swift 代码?在我的 MacBook Pro 上大约需要 1 秒。
  • @MartinR:(拍头)——是的,这有很大的不同。现在 0.80 秒或快 10 倍。我对变化的幅度感到惊讶。
  • Swift 4 现在支持部分范围。 array[i...]、array[..&lt;i] 和 array[...i] 函数类似于 Python 的 array[i:]、array[:i] 和 array[:i+1]

标签: python arrays swift performance swift2


【解决方案1】:

首先(正如 cmets 中已经说过的),有一个巨大的 Swift 中非优化代码和优化代码的区别 (“-Onone”与“-O”编译器选项,或调试与发布配置),因此对于性能测试,请确保“发布”配置 被选中。 (“Release”也是默认配置,如果你 使用 Instruments 分析代码)。

使用半开范围有一些好处:

var arr = [0,1,2,3,4,5]
arr[2 ..< arr.endIndex] = [99]
print(arr) // [0, 1, 99]

事实上,这就是范围在内部存储的方式,它允许您 在数组的末尾插入一个切片(但不像在 Python 中那样超出):

var arr = [Int]()
arr[0 ..< arr.endIndex] = [99]
print(arr) // [99]

所以

if partials.endIndex > i {
    partials[i...partials.endIndex-1]=[x]
}
else {
    partials.append(x)
} 

等价于

 partials[i ..< partials.endIndex] = [x]
 // Or: partials.replaceRange(i ..< partials.endIndex, with: [x])

但是,这并不是性能改进。看起来 在 Swift 中替换切片很慢。截断数组和 将新元素添加到

partials.replaceRange(i ..< partials.endIndex, with: [])
partials.append(x)

将我的测试代码的时间从大约 1.25 秒减少到 0.75 秒 电脑。

【讨论】:

  • 感谢您的耐心和洞察力。帮助我走上学习语言的道路。到目前为止我很喜欢......
  • @dawg:谢谢你——我以前不知道求和方法,所以今天我学到了一些新东西!
【解决方案2】:

正如@MartinR 指出的那样,replaceRange 比切片分配更快。

如果您想要最大速度(根据我的测试),您最好的选择可能是:

partials.replaceRange(i..<partials.endIndex, with: CollectionOfOne(x))

CollectionOfOne 比 [x] 快​​,因为它只是将元素内联存储在结构中,而不是像数组一样分配内存。

【讨论】:

  • 你是对的(一如既往!)——在我的测试中将时间从 0.75 秒减少到 0.68 秒。
  • 很好的答案。谢谢!这确实也提高了速度。
猜你喜欢
  • 2015-11-26
  • 1970-01-01
  • 2020-07-23
  • 2017-11-17
  • 2014-05-24
  • 2021-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多