【问题标题】:Python - Splitting an array into two using an optimized for loopPython - 使用优化的 for 循环将数组分成两部分
【发布时间】:2013-11-26 19:45:48
【问题描述】:

这是我在here 发布的问题的后续问题,但这是一个非常不同的问题,所以我想我会单独发布。

我有一个 Python 脚本,它读取一个非常大的数组,我需要优化每个元素的操作(请参阅引用的 SO 问题)。我现在需要将输出数组拆分为两个单独的数组。

我有代码:

output = [True if (len(element_in_array) % 2) else False for element_in_array in master_list]

根据element_in_array 的长度是奇数还是偶数,输出一个长度为len(master_list) 的数组,由True 或False 组成。我的问题是我需要将master_list 拆分为两个数组:一个数组包含element_in_array 对应于output 中的True 元素,另一个包含element_in_array 对应于@987654331 output中的@元素。

这显然可以使用传统的数组运算符(例如append)来完成,但我需要尽可能优化和尽可能快。我的master_list 中有数百万个元素,那么有没有办法在不直接循环master_list 并使用append 创建两个新数组的情况下完成此操作。

任何建议将不胜感激。 谢谢!

【问题讨论】:

  • 因此,您将所有真值(也就是偶数)附加到第一个列表中。应该是一个for循环,也就是O(n),这里真的不能比线性时间循环快。
  • 如果你有一个非常大的数组,你能用NumPy数组代替纯Py​​thon列表吗?如果是这样,您可能可以用更简单的代码来完成它,运行时间大约是 1/10,并且使用大约 1/4 的存储空间。
  • 附带说明,True if foo else Falsebool(foo) 更简单(而且通常更快)。

标签: python optimization for-loop numpy iterator


【解决方案1】:

你可以使用itertools.compress:

>>> from itertools import compress, imap
>>> import operator
>>> lis = range(10)
>>> output = [random.choice([True, False]) for _ in xrange(10)]
>>> output
[True, True, False, False, False, False, False, False, False, False]
>>> truthy = list(compress(lis, output))
>>> truthy
[0, 1]
>>> falsy = list(compress(lis, imap(operator.not_,output)))
>>> falsy
[2, 3, 4, 5, 6, 7, 8, 9]

如果您想要更快的解决方案,请选择NumPy,此外它还允许我们基于布尔数组进行数组过滤:

>>> import numpy as np
>>> a = np.random.random(10)*10
>>> a
array([ 2.94518349,  0.09536957,  8.74605883,  4.05063779,  2.11192606,
        2.24215582,  7.02203768,  2.1267423 ,  7.6526713 ,  3.81429322])
>>> output = np.array([True, True, False, False, False, False, False, False, False, False])
>>> a[output]
array([ 2.94518349,  0.09536957])
>>> a[~output]
array([ 8.74605883,  4.05063779,  2.11192606,  2.24215582,  7.02203768,
        2.1267423 ,  7.6526713 ,  3.81429322])

时间对比:

>>> lis = range(1000)
>>> output = [random.choice([True, False]) for _ in xrange(1000)]
>>> a = np.random.random(1000)*100
>>> output_n = np.array(output)
>>> %timeit list(compress(lis, output))
10000 loops, best of 3: 44.9 us per loop
>>> %timeit a[output_n]
10000 loops, best of 3: 20.9 us per loop
>>> %timeit list(compress(lis, imap(operator.not_,output)))
1000 loops, best of 3: 150 us per loop
>>> %timeit a[~output_n]
10000 loops, best of 3: 28.7 us per loop

【讨论】:

  • 这仍然需要两次通过output,因此需要进行基准测试以查看这是否比将元素附加到适当列表的单次通过更快。
  • 这实际上需要 3 次遍历 -- 一次确定每个元素在哪个列表中,一次生成第一个列表,一次生成第二个列表。
  • 我一直在看 numpy.看起来像要走的路。
【解决方案2】:

如果你可以使用NumPy,这会简单很多。而且,作为奖励,它也会更快,并且它会使用更少的内存来存储你的巨型数组。例如:

>>> import numpy as np
>>> import random
>>> # create an array of 1000 arrays of length 1-1000
>>> a = np.array([np.random.random(random.randint(1, 1000))
                  for _ in range(1000)])
>>> lengths = np.vectorize(len)(a)
>>> even_flags = lengths % 2 == 0
>>> evens, odds = a[even_flags], a[~even_flags]
>>> len(evens), len(odds)
(502, 498)

【讨论】:

    【解决方案3】:

    您可以尝试在itertools 中使用groupby 函数。关键函数将是确定元素长度是否为偶数的函数。 groupby 返回的迭代器由键值元组组成,其中 key 是键函数返回的值(这里是 TrueFalse),值是所有共享的项的序列 同一把钥匙。创建一个字典,将键函数返回的值映射到列表,您可以使用初始迭代器中的一组值扩展适当的列表。

    trues = []
    falses = []
    d = { True: trues, False: falses }
    def has_even_length(element_in_array):
        return len(element_in_array) % 2 == 0
    
    for k, v in itertools.groupby(master_list, has_even_length):
       d[k].extend(v)
    

    groupby 的文档说您通常希望确保列表按 key 函数返回的相同键排序。在这种情况下,可以不排序;你只会得到比groupby返回的迭代器返回的更多的东西,因为序列中可能有许多交替的真/假集合。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-15
      • 1970-01-01
      • 2018-10-15
      • 2020-11-07
      • 1970-01-01
      相关资源
      最近更新 更多