【问题标题】:Efficient way to use python's lambda, map使用python的lambda,map的有效方法
【发布时间】:2009-09-01 16:27:08
【问题描述】:

我需要在 Bigtable(db) 中存储大量整数。为了提高效率,我将它们存储为 2 个连续项目之间的差异。

例如:

 original_list = [1005, 1004, 1003, 1004, 1006] 

将上述列表(实际上包含超过 1000k 项)存储为 开始 = 1005 diff = [-1, -1, 1, 2]

我能做到的最接近的是,

ltp = [开始] 地图(lambda x:ltp.append(ltp[-1] + x),打勾)

我正在寻找一种将其转换回原始列表的有效方法。

【问题讨论】:

    标签: python performance list lambda map-function


    【解决方案1】:

    对于如此大的数据结构,numpy 可以很好地工作。对于这个例子,它快了 200 倍以上(见下文),并且更容易编码,基本上只是

    add.accumulate(diff)
    

    numpy 和直接列表操作的比较:

    import numpy as nx
    import timeit
    
    N = 10000
    
    diff_nx = nx.zeros(N, dtype=nx.int)
    diff_py = list(diff_nx)
    
    start = 1005
    
    def f0():
        orig = [start]
        for x in diff_py: 
            orig.append(orig[-1] + x)
    
    def f1():
        diff_nx[0] = start
        nx.add.accumulate(diff_nx)
    
    t = timeit.Timer("f0()", "from __main__ import f0, f1, diff_nx, diff_py, nx, start")
    print t.timeit(number=1000)
    t = timeit.Timer("f1()", "from __main__ import f0, f1, diff_nx, diff_py, nx, start")
    print t.timeit(number=1000)
    

    给予

    13.4044158459     # for list looping
    0.0474112033844   # for numpy accumulate
    

    不过,确实,重用已建立的压缩算法似乎更好,就像使用 PyTables 轻松完成一样,而不是像您在这里所做的那样滚动您自己的算法。

    另外,在这里,我建议您读入数据,为前置开始词留出空间,而不是用前置词重新构建列表,这样您就不必进行复制。

    【讨论】:

    【解决方案2】:

    以下对我有用:

    orig = [start]
    for x in diff:
        orig.append(orig[-1] + x)
    

    使用map 将创建一个大小相同的新数组,并用None 填充。我还发现一个简单的for 循环更具可读性,并且在这种情况下尽可能快。

    【讨论】:

    • 我也有这个想法。但是认为 map 和 lambda 比循环更有效,所以放弃了它。可能是我错了:(
    • @sudhakar:在 Python 中,lambda 效率非常低——函数调用很慢,对匿名函数的调用更慢。
    • @fsanches:列表解析在某些情况下更快,但在这种情况下(使用以前的值构建列表)会慢得多,因为它需要创建中间列表。
    • 在速度方面,根据 Mark Lutz 的书Learning Python: list comprehension > map > for loop
    • @sudhakar map 比 for 循环更快,但正如 John 所说,lambda 不是。
    【解决方案3】:

    非常适合生成器:

    def diff2abs( diffs, start ):
        yield start
        for diff in diffs:
            start += diff
            yield start
    
    start = 1005
    diffs = [-1, -1, 1, 2]
    original_list = list( diff2abs( diffs, start ))
    

    【讨论】:

      【解决方案4】:

      其他几位受访者对您要求的算法有合理的实现,但我不清楚您真正想要解决的问题到底是什么。

      除非存储的数字非常大(即溢出整数并需要 bignums),否则您的差异列表不会为您带来任何效率——整数是 Python 运行时 POV 中的整数,所以您的示例“ [-1, -1, 1, 2] 的差异”列表将消耗与原始列表 [1005, 1004, 1003, 1004, 1006] 一样多的内存。

      【讨论】:

      • 他注意到他将值存储在数据库中。我假设他在数据库中使用小数据类型(例如字节)来存储大整数的大列表,这更有效。
      • 他使用的是 array 类型,大概是 8 位或 16 位值。
      • 是的,我使用的是 8 位无符号字符的数组类型 tickvalue = ArrayProperty('b', default=None)
      • 我使用 diff 的主要原因是,appengine 有 1 mb 的限制。使用原始列表,我可以在一条记录中存储多达 65k*13 个项目,b4 我达到了 1 mb 的限制
      【解决方案5】:
      class runningtotal:
          def __init__(self, start = 0):
              self.total = start
          def __call__(self, value):
              self.total += value
              return self.total
      

      现在试试:

      >>> map(runningtotal(start), [0,]+diff)
      [1005, 1004, 1003, 1004, 1006]
      

      【讨论】:

        【解决方案6】:

        正如 mshsayem 建议的那样,使用列表推导式 - 它们通常比 for 循环或 map/lambdas 更快(根据 Mark Lutz 的《Learning Python》一书)。

        如果您真的想使用更多 FP-ish 解决方案,正确的功能是“扫描”,[我相信]没有在 Python 中实现,因此您必须自己实现它(这并不难任务)。

        “scan”基本上是一个reduce,但它不是将列表缩减为单个值,而是将每个“迭代”的结果存储在一个新列表中。

        如果你实现了它,你可以这样做:

        scan(lambda x,y: x+y, [start]++diff)
        

        【讨论】:

        • 我非常喜欢这种扫描方法。让我看看我是否能想出一个好的实现。 +1 不同的方法
        • 列表推导并不总是更快 - 特别是如果(如您的示例)为每次迭代创建一个新列表。在这种情况下,运行时间会上升到 n^2。
        • @usualyuseless:我的示例不是列表理解,而是仅创建一个列表。
        • 正确,我的错。但是,关键点仍然有效。您为每一步创建一个新列表。
        • 不,如果你使用可变列表,它只会创建一个列表(你没有理由不在这里使用它们)。
        【解决方案7】:

        虽然我不明白为什么这应该更有效,但我很确定 for 循环会提供最佳性能:

        l = [start]
        for i in diff:
            l.append(l[-1] + i)
        

        【讨论】:

        • 股票市场相关应用。我需要按刻度数据存储至少 65k 项刻度。经过过去 3 个月的大量试验后,我选择了这个数据结构,我将在其中将它们存储为 python 数组(而不是列表)并作为 Blob 保存在 appengines Bigtable 中。
        • 列表推导通常比 for 循环更快
        • 想详细说明您将如何构建此列表理解?我想不出一个优雅且不会每次都创建新列表的。
        • mshsayem 做了一个简单的,但似乎他删除了他的答案。这是我的看法(不如他的): [start.append(start[-1]+x) for x in diff] 无论如何,如果你不这样做,map 也比 for 循环快(再次根据 Lutz 的书)不要使用 lambdas。
        • 地图理解的速度更快,如果你使用它们构建一个列表。在这里,您正在使用它们进行一些函数调用(并构造一个包含 None 的列表作为副作用)。
        【解决方案8】:

        我不知道您将整数存储为差异的原因 - rcoder 给出了一个很好的答案,说明为什么这通常不比存储整数本身更有效 - 但如果您不需要访问一次完整的列表,使用生成器在内存方面更有效。由于您说这是一个“大列表”,因此您可以通过这种方式节省大量内存,而不是一次分配整个列表。这是一个生成器理解,可以让你的列表恢复:

        start = 1005
        def mod_start(x):
            global start
            start += x
            return start
        int_generator = (mod_start(i) for i in diffs)
        

        然后您可以像对任何列表一样迭代 int_generator,而无需一次将整个列表放在内存中。但是请注意,您不能对生成器进行下标或切片,但可以在许多有用的情况下使用它。

        您可以清理示例,以便 start 变量不需要是全局的。它不能是 mod_start 函数的本地。

        编辑:您不必使用生成器理解来获取生成器。您还可以使用带有 yield 表达式的生成器函数,就像 THC4k 所做的那样。这避免了开始变量范围的问题,并且可能更干净一些。您还可以随时从生成器中获取列表,方法是将其传递给 list() 内置函数。

        【讨论】:

          【解决方案9】:

          对此的性能不做评论,但你可以在这里使用reduce。

          start = 1005
          diffs = [-1,-1,1,2]
          reduce(lambda undiffed_list, diff: undiffed_list + [undiffed_list[-1] + diff],diffs,[start])
          

          得到你想要的。

          【讨论】:

            猜你喜欢
            • 2014-12-20
            • 2018-06-27
            • 2023-03-31
            • 1970-01-01
            • 1970-01-01
            • 2018-12-23
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多