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

时间:2015-01-12 17:45:41

标签: python list-comprehension

说我在Python 3.X中有一个列表。我使用list comprehension返回该列表的一个子集---是否有一种简单/ Pythonic的方式从原始列表中“弹出”该子集(因此返回后该子集中的元素不再在原始列表中) ?谢谢!

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

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

然后我想要:

  

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

注意:我不希望一次关闭一个值,而是一次性整个子集。 “流行”可能不是最好的术语,但我不知道是什么。

我能想到的最佳方式是:

 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(类似)) )))

4 个答案:

答案 0 :(得分:4)

一线解决方案(请不要使用此功能):

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.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:]

哪种方法更可取取决于您之后计划对列表执行的操作。

答案 1 :(得分:4)

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

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的更新几乎相同,但不管你信不信,我没有作弊: - )

答案 2 :(得分:4)

请记住,这是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 ns,但我们在循环中迭代两次。当然我们不需要这样做?上面几种方法怎么样?让我们从classify开始,这非常简单,只能遍历列表一次:

%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

更好,但它仍然比我们的'天真'/'低效'实施慢。也许有点不同的配方会更好:

%%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中有点“引人注目”,因此对于许多事情来说,它们比你提出的任何事情都要快。

现在,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&amp;效率,在控制台中试用它通常是一个好主意,但通常天真的实现是合理的性能和最容易阅读。快速测试会告诉您,尝试优化是否真的值得;如果你不小心的话,你通常可以降低它的可读性和速度......

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

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&gt;如图2所示(代替x> 50000),第二循环加速到大约11.4ms。但是,这仍然比天真的实施更快。也就是说,它可能是我喜欢的那个 - 它仍然很容易阅读,并且它不会慢(或显着)。随着时间的推移,代码往往变得越来越复杂,所以当你以后添加另一个比较,或者将它更改为函数调用时,它更容易做,并且这个版本的性能将比天真版本更少。

但同样:这并不是说'使用列表理解'(虽然它们通常是一个好主意),但验证你的假设。

答案 3 :(得分:0)

如果你想要弹出&#39;列表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没有按特定顺序排列,则此脚本特别有用。