【问题标题】:Unable to create duplicate list from existing list using list comprehension with an if condition无法使用带有 if 条件的列表推导从现有列表创建重复列表
【发布时间】:2018-07-05 11:18:58
【问题描述】:

我有一个带有重复元素的排序列表,例如

>>> randList = [1, 2, 2, 3, 4, 4, 5]
>>> randList
[1, 2, 2, 3, 4, 4, 5]

我需要创建一个删除相邻重复元素的列表。我可以这样做:

>>>> dupList = []
     for num in nums:
       if num not in dupList:
         dupList.append(num)

但我想通过列表理解来做到这一点。我尝试了以下代码:

>>> newList = []
>>> newList = [num for num in randList if num not in newList]

但我得到的结果就像 if 条件不起作用。

>>> newList
[1, 2, 2, 3, 4, 4, 5]

任何帮助将不胜感激。 谢谢!!

编辑 1:鉴于我提供的数据,问题的措辞似乎确实令人困惑。我正在使用的 for 循环将删除 all 重复项,但由于我事先对列表进行了排序,因此在删除相邻的重复项时应该没有问题。

【问题讨论】:

  • 请注意,基于 for 循环的解决方案将删除所有重复项,而不仅仅是相邻的。
  • @brunodesthuilliers 你是对的。但是既然列表反正是排序的,会不会有什么影响呢?
  • 好吧,如果您的列表已排序,则根据定义,重复项确实是相邻的。

标签: python list if-statement list-comprehension


【解决方案1】:

使用itertools.groupby 是删除相邻(且仅相邻)重复项的最简单方法,即使对于未排序的输入也是如此:

>>> from itertools import groupby
>>> [k for k, _ in groupby(randList)]
[1, 2, 3, 4, 5]

使用OrderedDict 可以有效地删除所有重复项,同时保持出现的顺序。这也适用于有序和无序输入:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys(randList))
[1, 2, 3, 4, 5]

【讨论】:

  • 在排序列表中,所有重复项都是相邻的。这点真的没必要再强调了。
  • @jpp 当 OP 明确称它们相邻时,尽管它们的数据已排序,我将随意涵盖一般情况并提及区别。
  • OP 没有定义他想要的非排序列表。我认为这是一个假设,而不是一概而论。
  • @jpp 因此,试图通过指出所有/相邻排序/未排序之间的差异来使事情尽可能清晰。答案涵盖了 OP 的具体用例,同时明确了这两个选项对各种输入的作用。我看不出这样的概括如何损害答案。
  • 我更新了my answer,并比较了评估时间。事实证明,这种方法比设置方法慢。但是,这个解决方案确实更接近原始问题(没有排序限制)。
【解决方案2】:

我需要创建一个删除相邻重复元素的列表

请注意,您的 for 基于循环的解决方案将删除所有重复项,而不仅仅是相邻的。用这个测试它:

rand_list = [1, 2, 2, 3, 4, 4, 2, 5, 1]

根据您的规范,结果应该是:

[1, 2, 3, 4, 2, 5, 1]

但你会得到

[1, 2, 3, 4, 5]

改为。

仅删除 相邻 个重复项的可行解决方案是使用生成器:

def dedup_adjacent(seq):
    prev = seq[0]
    yield prev
    for current in seq[1:]:
        if current == prev:
            continue
        yield current
        prev = current

rand_list = [1, 2, 2, 3, 4, 4, 2, 5, 1]
list(dedup_adjacent(rand_list))

=> [1, 2, 3, 4, 2, 5, 1]

【讨论】:

  • @schwobaseggl,由于列表是按照 OP 排序的,这应该没什么区别。
  • @schwobaseggl 我确实错过了规范的这一部分(但请注意,OP 的代码确实删除了所有重复项)。答案会根据您的观察而改变。
  • Jup,示例数据和代码与问题的措辞并不完全相符。
【解决方案3】:

Python 首先评估列表推导,然后将其分配给 newList,因此您无法在执行列表推导期间引用它。

您可以通过两种方式删除重复项:-
1.使用for循环

rand_list = [1,2,2,3,3,4,5]
new_list=[]
for i in rand_list:
    if i not in new_list:
        new_list.append(i)
  1. 将列表转换为集合,然后再次将集合转换为列表,最后对新列表进行排序。
    由于 set 以任何顺序存储值,因此当我们将 set 转换为列表时,您需要对列表进行排序,以便您按升序获取项目

    rand_list = [1,2,2,3,3,4,5] sets = set(rand_list) new_list = list(sets) new_list.sort()

【讨论】:

    【解决方案4】:

    更新:不同方法的比较

    已经有三种方法可以实现删除排序列表中相邻重复元素的目标,即删除所有重复项:

    • 使用groupby(仅相邻元素,需要初始排序)
    • 使用OrderedDict(删除所有重复项)
    • 使用sorted(list(set(_)))(删除所有重复项,通过排序恢复排序)。

    我比较了不同解决方案的运行时间:

    from timeit import timeit
    
    print('groupby:', timeit('from itertools import groupby; l = [x // 5 for x in range(1000)]; [k for k, _ in groupby(l)]'))
    print('OrderedDict:', timeit('from collections import OrderedDict; l = [x // 5 for x in range(1000)]; list(OrderedDict.fromkeys(l))'))
    print('Set:', timeit('l = [x // 5 for x in range(1000)]; sorted(list(set(l)))'))
    
    > groupby: 78.83623623599942
    > OrderedDict: 94.54144410200024
    > Set: 65.60372123999969
    

    请注意,set 方法是所有替代方法中最快的。

    旧答案

    Python 首先评估列表推导,然后将其分配给newList,因此您无法在执行列表推导期间引用它。为了说明,请考虑以下代码:

    randList = [1, 2, 2, 3, 4, 4, 5]
    
    newList = []
    newList = [num for num in randList if print(newList)]
    
    > []
    > []
    > []
    > …
    

    如果您尝试,这一点会变得更加明显:

    # Do not initialize newList2
    newList2 = [num for num in randList if print(newList2)]
    
    > NameError: name 'newList2' is not defined
    

    您可以通过将 randList 变成一个集合来删除重复项:

    sorted(list(set(randlist)))
    
    > [1, 2, 3, 4, 5]
    

    请注意,这会删除所有重复项(不仅仅是相邻的),并且不会保留顺序。前者也适用于您提出的带有循环的解决方案。

    编辑:添加了一个sorted 子句来指定所需的排序。

    【讨论】:

    • @schwobaseggl,由于列表是按照 OP 排序的,这应该没什么区别。重点是set 是无序的,看来 OP 希望保持有序。
    • @schwobaseggl 我在回答的最后一句话中指出了这一点。此外,OP给出的代码也具有这种效果。还有@jpp:似乎不清楚OP想要什么,因为“工作代码”达到的目标与他描述的不同。
    • @schwobaseggl 我通过比较三种解决方案更新了我的答案
    【解决方案5】:

    newList = [num for num in randList if num not in newList]这一行,首先list会在右侧创建,然后分配给newList。这就是为什么每次检查 if num not in newList 时都会返回 True。因为newList 在分配之前一直是空的。

    你可以试试这个:

    randList = [1, 2, 2, 3, 4, 4, 5]
    new_list=[]
    for i in randList:
        if i not in new_list:
            new_list.append(i)
    
    print(new_list)
    

    【讨论】:

    • 这已经在 OP 的帖子中,对于大型列表(O(N) 查找时间)效率低下,并且会删除所有重复项,而不仅仅是相邻的。
    【解决方案6】:

    您无法在进行时访问列表理解中的项目。列表推导式中的项目只有在推导式完成后才能访问。

    对于大型列表,检查列表中的成员将是昂贵的,尽管内存需求最少。相反,您可以附加到 set:

    randList = [1, 2, 2, 3, 4, 4, 5]
    
    def gen_values(L):
        seen = set()
        for i in L:
            if i not in seen:
                seen.add(i)
                yield i
    
    print(list(gen_values(randList)))
    
    [1, 2, 3, 4, 5]
    

    此算法已在第 3 方 toolz 库中实现。在itertools 文档中也称为unique_everseen 配方:

    from toolz import unique
    
    res = list(unique(randList))
    

    【讨论】:

      【解决方案7】:

      由于您的列表已排序,因此使用set 将是实现目标的最快方式,如下所示:

      >>> randList = [1, 2, 2, 3, 4, 4, 5]
      >>> randList
      [1, 2, 2, 3, 4, 4, 5]
      >>> remove_dup_list = list(set(randList))
      >>> remove_dup_list
      [1, 2, 3, 4, 5]
      >>> 
      

      【讨论】:

      • set 被定义为 unordered 集合(至少在 python 3.7 之前),因此这可能会丢失排序。此外,它将删除所有重复项,而不仅仅是相邻的。
      • 他提到列表是排序的,所以元素总是有序的,对吧?
      • 我重复一遍:set 没有排序(至少在 py3.7 之前),因此列表最初排序的事实是无关紧要的(wrt/ 至少保持原始顺序)。在以"aaaabbbccc" 作为源序列的python 2 或python 3 ['a', 'c', 'b'] (py2.7.6) 或['c', 'b', 'a'] (py3.4.3)。
      猜你喜欢
      • 2021-12-20
      • 1970-01-01
      • 2012-04-24
      • 1970-01-01
      • 2014-01-28
      • 1970-01-01
      • 2021-07-28
      • 2020-06-24
      • 1970-01-01
      相关资源
      最近更新 更多