Python 列表理解- "pop" 来自原始列表的结果?

Python list comprehension- "pop" result from original list?

假设我在 Python 3.X 中有一个列表。我使用列表理解来 return 该列表的一个子集---是否有 easy/Pythonic 方法来 "pop" 原始列表中的那个子集(所以那个子集中的元素不再在 returned 之后的原始列表中)?谢谢!

示例(我需要帮助定义 my_func):

a = [1, 2, 2, 3, 4, 5]
a, b = my_func(a, *kwargs)

然后我想要:

a = [1, 2, 2]
b = [3, 4, 5]

注意:我不想一次弹出一个值,而是一次弹出整个子集。 "Pop" 可能不是最好的术语,但我不知道是什么。

我能想到的最好的方法是:

 temp = [idx for idx, val in enumerate(a) if val > 2]  
 b = [a[i] for i in temp]  
 a = [val for idx,val in enumerate(a) if idx not in temp]  

显然,我更喜欢更优雅、更高效的东西。 我想避免重复查看列表两次。

编辑:我认为这个问题很独特,因为我不仅仅关心拆分列表——我想修改原始列表。拆分并将其中一个拆分分配给原始列表变量是一种可能的解决方案,但我希望有一种方法可以做到这一点而无需明确分配给原始列表变量(类似于 b.append(a.pop()))

一行解决方案(请不要实际使用):

a = [7,4,2, 1, 5 , 11, 2]
[x for x in (a.pop() for i in range(len(a))) if (lambda x: True if x> 2 \
else a.insert(0, x))(x)]

更合理的解决方案

一般来说——如果你在弹出之前检查一个值是什么——那就是在索引处读取,然后弹出(更不用说保持跟踪迭代长度变化的数组的时髦性了),这似乎表明没有两个新列表没有实际意义。

所以我只想添加到两个新列表之一,即

a = [1, 2, 2, 3, 4, 5]
b=[]; c=[];
for i in range(len(a)): 
    if x > 2: b.append(x) 
    else: c.append(x) 

当然,从技术上讲,您可以从 a 中弹出并插入回 a,您会执行 a.insert(0, x) 而不是 c.append(x) - 但操作要循环访问的列表通常不是一个好主意。

另一种选择是排序列表和二分法,即

a = sorted(a) 
ind = bisect.bisect(a, 2) 
#Now both lists are here 
a, b= a[:ind], a[ind:]

哪种方法更可取实际上取决于您打算之后对列表执行的操作。

只需使用列表理解过滤掉不需要的项目:

a = [1, 2, 2, 3, 4, 5]
condition = lambda x: x > 2
b = [x for x in a if condition(x)]      # b == [3, 4, 5] 
a = [x for x in a if not condition(x)]  # a == [1, 2, 2]

更新

如果您担心效率,那么这是另一种只扫描列表一次的方法:

def classify(iterable, condition):
    """ Returns a list that matches the condition and a list that
    does not """
    true_list = []
    false_list = []
    for item in iterable:
        if condition(item):
            true_list.append(item)
        else:
            false_list.append(item)
    return true_list, false_list

a = [1, 2, 2, 3, 4, 5]
b, a = classify(a, lambda x: x > 2)
print(a)  # [1, 2, 2]
print(b)  # [3, 4, 5]

更新 2

我没有意识到我的更新看起来和 user3467349 的几乎一样,但信不信由你,我没有作弊:-)

如果您想 'pop' 列表中的值 a 并将这些值附加到 b 我会遵循这种方法:

    a = [1,2,2,3,4,5]
    b = []

    for i in a[:]:
        if i > 2:
            b.append(i)
            a.remove(i)

    #a = [1,2,2]
    #b = [3,4,5]

如果列表 a 未按特定顺序排列,此脚本特别有用。

请记住,这是 Python,而不是 C,有时它的工作方式不一定直观。这意味着您必须验证您的假设。我使用 IPython 的内置 %%timeit 魔法完成了此操作。

a = [1,2,2,3,4,5]
%timeit b=[x for x in a if x > 2]\
1000000 loops, best of 3: 362 ns per loop
%timeit c=[x for x in a if not (x > 2)]
1000000 loops, best of 3: 371 ns per loop

所以两者都不到 800 纳秒,但我们在循环上迭代了两次。当然我们不需要那样做?上述几种方法怎么样?让我们从分类开始,它非常简单,只遍历列表一次:

%timeit b, a = classify(a, lambda x: x > 2)
1000000 loops, best of 3: 1.89 µs per loop

哇,虽然它只遍历了一次循环,但它花费的时间是上面简单解决方案遍历两次的两倍多。让我们尝试对上面提出的其他解决方案稍作改动:

%%timeit
b, c = [], []
for x in a:
    b.append(x) if x > 2 else a.append(x)
1000000 loops, best of 3: 1.2 µs per loop

更好,但是 仍然 比我们的 'naive'/'inefficient' 实施慢。也许稍微不同的表述会更好:

%%timeit
b, c = [], []
for x in a:
    if x > 2:
        b.append(x)
    else:
        c.append(x)
1000000 loops, best of 3: 983 ns per loop

嗯,好像差不多,就是快了一点。仍然没有击败天真的实现。让我们变得更聪明,也许让它变得更快:

%%timeit
b, c = [], []
for x in a:
    (b, c)[x > 2].append(x)
1000000 loops, best of 3: 1.28 µs per loop

因此,我们看到的是,尽管我们不希望将循环迭代两次,但我们似乎无法在仅进行两次列表推导的情况下有所改进。列表理解有点像 Python 中的 'under the hood',因此对于很多事情来说,它们会比你想出的任何东西都快。

现在,x < 2 比较便宜 - 没有函数调用,简单的数学运算。总有一天它会变得有意义。让我们创建一个更昂贵的比较函数 - 我们将计算阶乘(并且效率低下):

def factorial(x):
    if x in (0,1):
        return 1
    else:
        return x * factorial(x-1)

%timeit b = [x for x in a if factorial(x) > 6]
100000 loops, best of 3: 3.47 µs per loop
%timeit c = [x for x in a if not factorial(x) > 6]
100000 loops, best of 3: 3.53 µs per loop

所以,很明显,时间增加了很多 - 现在一切都在 7uS 左右。

现在让我们尝试一些其他示例:

%timeit b, c = classify(a, lambda x: factorial(x) > 6)
100000 loops, best of 3: 5.05 µs per loop

%%timeit
b, c = [], []
for x in a:
    if factorial(x) > 6:
        b.append(x)
    else:
        c.append(x)
100000 loops, best of 3: 4.01 µs per loop

%%timeit
b, c = [], []
for x in a:
    (b, c)[factorial(x) > 6].append(x)
100000 loops, best of 3: 4.59 µs per loop

所有这一切的教训:在处理 python 和效率时,在控制台中尝试通常是个好主意,但通常朴素的实现性能合理且最容易实现读。快速测试会告诉您尝试优化是否真的值得;如果您不小心,通常会使它的可读性降低和变慢....

附录:有人评论说我们需要更长的列表,因为我们衡量的是函数调用开销而不是性能。他们有一个很好的观点,但时间在我的机器上显示了相同的关系:

In [16]: a = range(100000)

In [17]: random.shuffle(a)

In [18]: %timeit b = [x for x in a if x > 50000]
100 loops, best of 3: 5.2 ms per loop

In [19]: %timeit c = [x for x in m if not x > 50000]
100 loops, best of 3: 5.18 ms per loop

In [20]: %timeit c = [x for x in m if x <= 50000]
100 loops, best of 3: 5.35 ms per loop

In [21]: %%timeit
   ....: b, c = [], []
   ....: for x in a:
   ....:     if x > 50000:
   ....:         b.append(x)
   ....:     else:
   ....:         c.append(x)
   ....:
100 loops, best of 3: 12.7 ms per loop

请注意,如果我将比较更改为 x > 2(而不是 x > 50000),则第二个循环会加速到大约 11.4 毫秒。但是,这仍然比天真的实现快不了多少。也就是说,它可能是我更喜欢的那个——它仍然很容易阅读,而且速度并不慢(或慢很多)。随着时间的推移,代码往往会变得更加复杂,因此当您稍后添加另一个比较,或将其更改为函数调用时,这样做会更容易,而且与原始版本相比,此版本的性能受到的影响更小。

但是再说一次:这并不是说 'use list comprehensions'(尽管它们通常是个好主意),而是要验证您的假设。