通过迭代创建列表的最快方法

时间:2015-01-09 14:46:40

标签: python list loops optimization numpy

我最近创建了一个脚本来创建Dragon Curve,并设法优化代码。 基本上,我首先生成一个规则列表,看起来像[1, 1, -1, 1, 1, -1, -1],其中1代表右转,-1代表左转。 numpy数组的速度非常快。

有关龙曲线的更多信息:http://en.wikipedia.org/wiki/Dragon_curve

但是,现在我想使用此列表在平面中创建一条曲线。基本上:选择一个点(x,y)和一个方向(东),走一步,然后向右或向左转90度,具体取决于列表中的当前元素,我们将其循环。我们最后还要采取额外措施,但这不应该与问题有关。

让我们说我们的起始位置是(100,100),我们开始往东走,列表是[1, 1, -1]。然后我们应该得到[(100, 100), (101, 100), (101, 101), (101, 101), (100, 101), (100, 102)],这给了我们龙曲线的第二次迭代。

目前,我使用以下代码生成点序列:

pos = [100, 100]
ang = math.pi/2
for i in dragon + [0]:
    pos.extend([pos[-2]+math.cos(ang), pos[-1]+math.sin(ang)])
    ang += i*math.pi/2

其中dragon是先前生成的列表,例如[1, 1, -1, 1, 1, -1, -1]。我添加[0]以在最后采取额外步骤。对于19次迭代,我的脚本输出:

array of length 1048575 completed in 0.00567 seconds
dragon created in 2.82039 seconds
dragon drawn in 0.01462 seconds
image saved in 0.01229 seconds

我们可以清楚地看到上面的代码是最耗时的。 是否有更快的方法从我们之前生成的列表中生成所有这些点?

5 个答案:

答案 0 :(得分:3)

由于sincos的结果为零,一个或减一,以及循环,您可以查找它们,模4:

pos = [100, 100]
direction = 0

east_west_lookup = [0, -1, 0, 1]
north_south_lookup = [1, 0, -1, 0]

for i in dragon + [0]:
    east_west_step = east_west_lookup[direction % 4]
    north_south_step = north_south_lookup[direction % 4]
    pos.extend([pos[-2] + east_west_step,
                pos[-1] + north_south_step])
    direction += i

答案 1 :(得分:2)

Numpy也可以做到这一点,非常有效!使用Peter Wood的答案中的查找表,我们可以为x方向编写:

x0 = 100
directions = np.cumsum(2 - dragon) % 4
dx = np.take([0, -1, 0, 1], directions)
x = np.cumsum(np.r_[x0, dx])

2 - dragon是在取模数之前消除负值。

答案 2 :(得分:0)

这是一种使用字典查找的方式,我认为它更适合使用tuple来表示您的位置,并使用list来表示整个路径。

我没有结果,所以请确保face_dict设置正确。

dragon = [1, 1, -1, 1, 1, -1, -1]
# format: old-direction -> (add-to-x, add-to-y, new-direction)
# using a list would be faster, but I don't have time to think about it :)
face_dict = {
    "EAST": {-1: (1, 0, "NORTH"), 1: (1, 0, "SOUTH")},
    "SOUTH": {-1: (0, -1, "EAST"), 1: (0, -1, "WEST")},
    "WEST": {-1: (1, 0, "SOUTH"), 1: (1, 0, "NORTH")},
    "NORTH": {-1: (0, 1, "WEST"), 1: (0, 1, "EAST")}
}

# indicate a position using a tuple, a tuple represents a single location
# while a list is suitable to represent a path
pos = (100, 100)
def make_path(directions, start_pos):
    # create empty path with starting position
    path = [None] * len(dragon)
    path[0] = start_pos

    # this is the current state, which is stored in the list after every step.
    current_pos = start_pos
    # this is where we're facing right now
    face = "EAST"
    # each iteration fills a single step in the semi-empty path
    for index, direction in enumerate(directions):
        move = face_dict[face][direction]
        current_pos = (current_pos[0] + move[0], current_pos[1] + move[1])
        face = move[2]
        path[index] = current_pos
    return path

注意:合并Peter Wood's solution可以优化每次迭代的查找。

答案 3 :(得分:0)

一个小的改进,但是在我的系统上节省了10-15%的时间,每次都不计算math.pi/2。它没有Peter Wood的解决方案快,但如果您决定将dragon列表打开为0,1和-1以外的值,则可能很有用。

pos = [100, 100]
ang = math.pi/2
turn = ang
for i in dragon + [0]:
    pos.extend([pos[-2]+math.cos(ang), pos[-1]+math.sin(ang)])
    ang += i*turn

答案 4 :(得分:-1)

也许尝试预先分配列表中的空格,然后完全删除extend调用。像这样(未经测试):

pos = [100] * len(dragon)*2 + 4
ang = math.pi / 2
for idx, i in enumerate(dragon + [0]):
    start = idx*2
    pos[start+2] = pos[start]+math.cos(ang)
    pos[start+3] = pos[start+1]+math.sin(ang)
    ang += i * math.pi / 2