Python切片赋值的Swift等价物

用户

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

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

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

^{pr2}$

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

这很容易测试和编码。在

第二个问题是杀手:它在swift中非常慢。考虑使用以下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)

它的工作原理是保持hi/lo部分求和,使msum([.1]*10)产生{},而不是{}。msum的C等价物是Python中math library的一部分。在

我试图在swift中复制:

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 native msum需要2.2秒(大约快4倍),库fsum函数需要0.09秒(几乎快90倍)

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

问题:

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


已被浏览了4866次
更新日期: 2020-10-28 20:05:44
2 个回答
fefe Tyson

正如@MartinR指出的,replaceRange比片分配快。在

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

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

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

评论 - 2020年9月11日 20:34
fefe Tyson

首先(正如在评论中已经说过的那样),有一个巨大的 Swift中非优化代码与优化代码的区别 (“-Onone”对“-O”编译器选项,或Debug vs.Release配置),因此对于性能测试,请确保“Release”配置 已选定。(“Release”也是默认配置,如果 用仪器分析代码)。在

使用半开范围有一些优点:

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

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

^{pr2}$

所以

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])

然而,这并不是一种性能改进。看来 更换切片速度很慢。截断数组并 在新元素中附加

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

将测试代码的时间从1.25秒缩短到0.75秒 计算机。在

评论 - 2020年9月11日 20:34

最新Python问答

推荐Python问答