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

时间:2010-08-11 15:40:37

标签: python

Google Python类|列表练习 -

  

给出一个数字列表,返回一个列表   所有相邻的==元素都被缩减为单个元素,   所以[1,2,3,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

17 个答案:

答案 0 :(得分:17)

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

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()中获取重复数据删除效果,您需要在其周围包含列表推导以丢弃不需要的石斑鱼:

>>> [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 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>]

并且该代码在您运行它时会做正确的事情:

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

更新3:关于前向索引原位操作的备注

OP的修订代码:

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

最好写成:

{{1}}

答案 1 :(得分:9)

使用生成器迭代列表中的元素,yield只有在更改时才会使用新的

itertools.groupby就是这样做的。

如果迭代副本,则可以修改传入列表:

for elt in theList[ : ]:
    ...

答案 2 :(得分:6)

这里再展示一个没有索引的单线版:

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

not part将最后一个值放在结果中,因为只有结果才能结束。

答案 3 :(得分:5)

像往常一样,我只是在这里宣传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]

答案 4 :(得分:3)

嗯,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

答案 5 :(得分:1)

您可以使用列表理解。例如,像这样的事情应该做的工作:

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

答案 6 :(得分:1)

来自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 :(得分:1)

itertools.groupby优越,但也有

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

e.g。

>>> 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通常会感觉很自然。

答案 8 :(得分:1)

试试这个:

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 :(得分:0)

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

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

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

答案 10 :(得分:0)

@ katrielalex的解决方案更加pythonic,但如果您确实需要在不进行复制的情况下就地修改列表,则可以使用while循环并在捕获IndexError时中断。 e.g。

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,万一你遇到困难并想知道..

答案 11 :(得分:0)

def remove_adjacent(nums):

newList=[]

for num in nums:

    if num not in newList:

        newList.append(num)

newList.sort()

return  newList

答案 12 :(得分:0)

另一种方法。欢迎评论。

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

答案 13 :(得分:0)

看到谷歌编写的代码是一件令人羞愧的事。这就是我想出的:

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

答案 14 :(得分:0)

这应该适用于透明(尽管是迂回)的解决方案:

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被定义为避免索引超出任何长度列表的范围)

答案 15 :(得分:0)

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

答案 16 :(得分:-3)

由于你是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