从列表中删除相邻的重复元素[重复]

Posted

技术标签:

【中文标题】从列表中删除相邻的重复元素[重复]【英文标题】:Remove adjacent duplicate elements from a list [duplicate] 【发布时间】:2010-08-11 15:40:37 【问题描述】:

Google Python 类 |列表练习 -

给定一个数字列表,返回一个列表,其中 所有相邻的 == 元素都已简化为单个元素, 所以 [1, 2, 2, 3] 返回 [1, 2, 3]。您可以创建一个新列表或 修改传入的列表。

我使用新列表的解决方案是 -

def remove_adjacent(nums):
  a = []
  for item in nums:
    if len(a):
      if a[-1] != item:
        a.append(item)
    else: a.append(item)        
  return a

这个问题甚至暗示可以通过修改传入的列表来完成。但是,python 文档警告不要在使用 for 循环迭代列表时修改元素。

我想知道除了迭代列表之外我还能尝试什么来完成这项工作。我不是在寻找解决方案,而是在寻找可以引导我走向正确方向的提示。

更新

-用建议的改进更新了上面的代码。

- 使用建议的提示在 while 循环中尝试了以下操作 -

def remove_adjacent(nums):
  i = 1
  while i < len(nums):    
    if nums[i] == nums[i-1]:
      nums.pop(i)
      i -= 1  
    i += 1
  return nums

【问题讨论】:

不要使用&lt;&gt;。正确的符号是!=。使用if a,而不是if len(a) &lt;&gt; 0 @Aran-Fey 恕我直言,这个问题和重复的目标都应该作为Removing elements that have consecutive duplicates的重复项关闭 【参考方案1】:

这是传统方式,原位删除相邻的重复项,同时向后遍历列表:

Python 1.5.2 (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)] on win32
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> def dedupe_adjacent(alist):
...     for i in xrange(len(alist) - 1, 0, -1):
...         if alist[i] == alist[i-1]:
...             del alist[i]
...
>>> data = [1,2,2,3,2,2,4]; dedupe_adjacent(data); print data
[1, 2, 3, 2, 4]
>>> data = []; dedupe_adjacent(data); print data
[]
>>> data = [2]; dedupe_adjacent(data); print data
[2]
>>> data = [2,2]; dedupe_adjacent(data); print data
[2]
>>> data = [2,3]; dedupe_adjacent(data); print data
[2, 3]
>>> data = [2,2,2,2,2]; dedupe_adjacent(data); print data
[2]
>>>

更新:如果您想要一个生成器,但(没有 itertools.groupby 或(您可以比阅读它的文档并理解它的默认行为更快地键入)),这里有六个 -完成这项工作的班轮:

Python 2.3.5 (#62, Feb  8 2005, 16:23:02) [MSC v.1200 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def dedupe_adjacent(iterable):
...     prev = object()
...     for item in iterable:
...         if item != prev:
...             prev = item
...             yield item
...
>>> data = [1,2,2,3,2,2,4]; print list(dedupe_adjacent(data))
[1, 2, 3, 2, 4]
>>>

更新 2:关于巴洛克风格的itertools.groupby() 和极简主义的object() ...

要消除 itertools.groupby() 的 dedupe_adjacent 效果,您需要在其周围包裹一个列表推导以丢弃不需要的分组:

>>> [k for k, g in itertools.groupby([1,2,2,3,2,2,4])]
[1, 2, 3, 2, 4]
>>>

... 或与itertools.imap 和/或operators.itemgetter 混在一起,如另一个答案所示。

object 实例的预期行为是,它们中的任何一个都不等于任何类的任何其他实例,包括 object 本身。因此,它们作为哨兵非常有用。

>>> object() == object()
False

值得注意的是,itertools.groupby 的 Python reference code 使用 object() 作为标记:

self.tgtkey = self.currkey = self.currvalue = object()

当您运行该代码时,它会做正确的事情:

>>> data = [object(), object()]
>>> data
[<object object at 0x00BBF098>, <object object at 0x00BBF050>]
>>> [k for k, g in groupby(data)]
[<object object at 0x00BBF098>, <object object at 0x00BBF050>]

更新3:关于正向索引原位操作的说明

OP 修改后的代码:

def remove_adjacent(nums):
  i = 1
  while i < len(nums):    
    if nums[i] == nums[i-1]:
      nums.pop(i)
      i -= 1  
    i += 1
  return nums

最好写成:

def remove_adjacent(seq): # works on any sequence, not just on numbers
  i = 1
  n = len(seq)
  while i < n: # avoid calling len(seq) each time around
    if seq[i] == seq[i-1]:
      del seq[i]
      # value returned by seq.pop(i) is ignored; slower than del seq[i]
      n -= 1
    else:
      i += 1
  #### return seq #### don't do this
  # function acts in situ; should follow convention and return None

【讨论】:

Python 1.5.2。你在 FreeDOS 上运行它吗? @aaronasterling:不,在 Windows XP 上,而且只是非常偶尔,比如当我看到有人使用 itertools.groupby 等来做一些简单的事情时;-) @John Machin。我非常喜欢你的算法。当我的选票再次增加时,一分钟内 +1。 prev = object( ) 有点hacky,希望object( ) 不是迭代中的第一项!执行iterable = iter( iterable )(以确保它是一个迭代器)然后执行prev = next( iterable ) 会更正确,尽管稍微不那么优雅。 @katrielalex:我只希望 object() 实例继续“无特征” ;-) 从 2.2 开始的任何 Python 版本中查看 object() == object()dir(object()) 的结果.【参考方案2】:

使用生成器迭代列表中的元素,yield 仅在它发生变化时生成一个新元素。

itertools.groupby 正是这样做的。

如果你遍历一个副本,你可以修改传入的列表:

for elt in theList[ : ]:
    ...

【讨论】:

在遍历副本时,我不是在修改 copy 而不是实际传入的列表吗? 是的。您必须明确引用原始列表,例如使用元素的索引。 我不同意这一点。如果即使在迭代副本时从原始列表中删除元素,也会犯一些错误。我认为修改列表中元素的最佳方法是迭代是向后迭代列表并按索引修改元素。 答案:list(x.next() for i, x in groupby(my_list)) list(i for i, x in groupby(my_list))【参考方案3】:

这里只是为了展示另一种方式,是另一个没有索引的单班轮版本:

def remove_adjacent(nums):
     return [a for a,b in zip(nums, nums[1:]+[not nums[-1]]) if a != b]

not 部分将最后一个值作为 result 的最后一个值。

【讨论】:

【参考方案4】:

像往常一样,我只是在这里宣传 Python itertools 文档中令人印象深刻的recipes。

你要找的是函数unique_justseen:

from itertools import imap, groupby
from operator import itemgetter

def unique_justseen(iterable, key=None):
    "List unique elements, preserving order. Remember only the element just seen."
    # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
    # unique_justseen('ABBCcAD', str.lower) --> A B C A D
    return imap(next, imap(itemgetter(1), groupby(iterable, key)))

list(unique_justseen([1,2,2,3])) # [1, 2, 3]

【讨论】:

【参考方案5】:

好吧,katrielalex 对itertools 的看法是正确的,但 OP 似乎对学习操作内置数据结构的基础知识更感兴趣(或者应该!)。至于就地操作列表,确实需要考虑,但我的建议是通读this section of the documentation 并尝试一些列表方法(提示:list.pop()、list.remove(),并了解有关切片的所有内容.)

顺便说一下,发布的代码可以简化(但是您应该添加对错误条件的处理):

def remove_adjacent(nums):
  a = nums[:1]
  for item in nums[1:]:
    if item != a[-1]:
      a.append(item)
  return a

【讨论】:

有趣的是,nums[0] 返回一个 int,而 nums[:1] 返回一个包含单个元素的列表!谢谢! 并且通过切片的魔力,如果nums 为空,nums[:1] 将返回一个空列表,从而在提供空列表作为输入的情况下为您提供正确的行为.相比之下,如果 nums 以空列表的形式出现,nums[0] 将引发 KeyError【参考方案6】:

来自 Google 的极其优雅的解决方案(来源:https://developers.google.com/edu/python/exercises/basic):

def remove_adjacent(nums):
    result = []
    for num in nums:
        if len(result) == 0 or num != result[-1]:
            result.append(num)
    return result

【讨论】:

【参考方案7】:

您可以使用列表推导。例如,这样的事情应该可以完成:

def remove_adjacent(L):
  return [elem for i, elem in enumerate(L) if i == 0 or L[i-1] != elem]

或:

def remove_adjacent(L):
  return [L[i] for i in xrange(len(L)) if i == 0 or L[i-1] != L[i]]

【讨论】:

【参考方案8】:

试试这个:

def remove_adjacent(nums):
  result = []
  if len(nums) > 0:
    result = [nums[0]]
    for i in range(len(nums)-1):
        if nums[i] != nums[i+1]:
            result.append(nums[i+1])

  return result

【讨论】:

【参考方案9】:

itertools.groupby更胜一筹,但也有

reduce(lambda x, y: x + [y] if x[-1] != y else x, seq[1:], seq[0:1])

例如

>>> seq = [[1,1], [2,2], [3,3], [3,3], [2,2], [2,2], [1,1]]
>>> print reduce(lambda x, y: x + [y] if x[-1] != y else x, seq[1:], seq[0:1])
[[1, 1], [2, 2], [3, 3], [2, 2], [1, 1]]

如果来自使用fold 完成此类事情的函数式语言,那么使用reduce 通常感觉很自然。

【讨论】:

【参考方案10】:

如果您明确使用索引,您可以修改您正在迭代的列表:

def remove_adjacent(l):
  if len(l)<2:
    return l
  prev,i = l[0],1
  while i < len(l):
    if l[i] == prev:
      del l[i]
    else:
      prev = l[i]
      i += 1

它不适用于迭代器,因为迭代器不“知道”当您删除任意元素时如何修改索引,因此更容易禁止它。一些语言的迭代器具有删除“当前项”的功能。

【讨论】:

(1) 不要使用“l”作为变量名;在某些字体中,它看起来太接近“1” (2) 如果其长度小于 2,您的函数将返回原始列表,否则返回 None ... 有点不一致。 l 是一个非常好的变量名。如果你的代码字体不能区分 l、I、1、0、O,那么你需要更好的字体。【参考方案11】:

@katrielalex 的解决方案更符合 Python 风格,但如果您确实需要在不复制的情况下就地修改列表,则可以使用 while 循环并在捕获 IndexError 时中断。 例如

nums = [1,1,1,2,2,3,3,3,5,5,1,1,1]
def remove_adjacent(nums):
    """Removes adjacent items by modifying "nums" in-place. Returns None!"""
    i = 0
    while True:
        try:
            if nums[i] == nums[i+1]:
                # Letting you figure this part out, 
                # as it's a homework question
        except IndexError:
            break
print nums
remove_adjacent(nums)
print nums

编辑:pastebin of one way to do it here,以防你被卡住并想知道..

【讨论】:

我尝试了提示并更新了我的尝试。【参考方案12】:

def remove_adjacent(nums):

newList=[]

for num in nums:

    if num not in newList:

        newList.append(num)

newList.sort()

return  newList

【讨论】:

这没有回答问题。【参考方案13】:

另一种方法。欢迎评论。

def remove_adjacent(nums):
    '''modifies the list passed in'''
    l, r = 0, 1
    while r < len(nums):
        if nums[l] == nums[r]:
            r += 1
        else:
            l += 1
            nums[l] = nums[r]
            r += 1
    del nums[l+1:]

【讨论】:

【参考方案14】:

看到由 Google 编写的代码真是令人羞愧。这是我想出的:

def remove_adjacent(nums):
   rmvelement = []
   checkedIndex = []
   for num in nums:
      if nums.index(num) not in checkedIndex:
         index = nums.index(num)
         checkedIndex.append(index)
         skip = False
      else:
         skip = True

   if skip == False:
      for x in nums[index+1:]:
         if x == num:
            rmvelement.append(x)
         else:
            break

   [nums.remove(_) for _ in rmvelement]
   return nums

【讨论】:

【参考方案15】:

这应该适用于透明(尽管是环形交叉路口)的解决方案:

def remove_adjacent(nums):

    numstail = [i for i in range(0,len(nums))] 
    nums = nums + numstail

    for i in nums:
        if nums[i] == nums[i-1]:
            del nums[i]

    return nums[:-len(numstail)]

逻辑如下:

创建一个等于原始数字列表长度的尾列表,并将其附加到原始列表的末尾。 运行“for-loop”,检查 nums 的给定元素是否与前一个元素相同。如果是这样,请将其删除。 返回新的 nums 列表,并进行必要的删除,直到列表末尾的 len(numtails) 索引位置。

(定义numstail 是为了避免索引超出任何长度列表的范围)

【讨论】:

【参考方案16】:
def removeDupAdj2(a):
    b=[]
    for i in reversed(range(1,len(a))):
        if(a[i-1] == a[i]):
            del(a[i])
            #print(a)
    return a

a = [int(i) for i in '1 2 3 3 4 4 3 5 4 4 6 6 6 7 8 8 8 9 1 1 0 0'.split(' ')]
a

res = removeDupAdj2(a)
res

【讨论】:

【参考方案17】:

由于您在学习 Python 课程,我猜您是该语言的新手。因此,对于您和任何其他初学者,我编写了一个简单版本的代码来帮助其他人理解逻辑。

original= [1, 2, 2, 3]
newlist=[]

for item in original:
    if item in newlist:
        print "You don't need to add "+str(item)+" again."
    else:
        newlist.append(item)
        print "Added "+str(item)

print newlist

【讨论】:

但它只要求删除相邻的项目,上面的代码不会让任何重复,无论它们的位置如何,所以 [1,2,3,2] 将产生 [1,2,3 ] 因此它应该产生 [1,2,3,2]

以上是关于从列表中删除相邻的重复元素[重复]的主要内容,如果未能解决你的问题,请参考以下文章

从包含特定字符的列表中删除元素[重复]

从列表的副本中删除时删除的列表元素[重复]

如何从字典中的列表中删除元素[重复]

如何使用 Dart / Flutter 中的列表从列表中删除重复元素?

如何从列表中删除一个特定元素[重复]

python中怎么从一个列表中可重复的随机抽取元素构成新列表?