关于python:从列表中删除相邻的重复元素

Remove adjacent duplicate elements from a list

google python类列表练习-

Given a list of numbers, return a list where
all adjacent == elements have been reduced to a single element,
so [1, 2, 2, 3] returns [1, 2, 3]. You may create a new list or
modify the passed in list.

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

1
2
3
4
5
6
7
8
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循环-

1
2
3
4
5
6
7
8
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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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或(您可以输入比阅读文档更快的速度,并了解其默认行为),这里有一个六行程序来完成此任务:

1
2
3
4
5
6
7
8
9
10
11
12
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()中的重复数据消除相邻效应消失,您需要在它周围包装一个列表理解,以丢弃不需要的分组:

1
2
3
>>> [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本身)相比。因此,它们作为哨兵非常有用。

1
2
>>> object() == object()
False

值得注意的是,itertools.groupby的python引用代码使用object()作为哨兵:

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

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

1
2
3
4
5
>>> 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:关于前向指数原位操作的备注

操作规程修订代码:

1
2
3
4
5
6
7
8
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

最好写为:

1
2
3
4
5
6
7
8
9
10
11
12
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


使用生成器迭代列表中的元素,只有当列表发生更改时,才会使用yield一个新的元素。

itertools.groupby就是这样做的。

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

1
2
for elt in theList[ : ]:
    ...


这里还有一个没有索引的单行程序版本:

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

Not部分将最后一个值放入result,因为只有a的结果。


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

您要查找的是函数unique_justseen

1
2
3
4
5
6
7
8
9
10
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]


好吧,凯特里·亚历克斯对埃多克斯的看法是正确的,但是手术似乎更感兴趣(或者应该是!)学习操作内置数据结构的基础知识。至于就地操作一个列表,它确实需要考虑,但是我的建议是阅读文档的这一部分,并尝试一些列表方法(提示:list.pop(),list.remove(),并了解有关切片的所有信息)。

通过这种方式,可以简化已发布的代码(但是您应该添加对错误条件的处理):

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


试试这个:

1
2
3
4
5
6
7
8
9
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


itertools.groupby比较好,但也有

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

例如

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


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

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


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

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

或:

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


1
2
3
4
5
6
7
8
9
10
11
12
13
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


另一种方法。欢迎评论。

1
2
3
4
5
6
7
8
9
10
11
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:]


看到谷歌写的代码是一个令人谦卑的LOL。这就是我想到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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


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

1
2
3
4
5
6
7
8
9
10
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循环",检查nums的给定元素是否与前一个元素相同。如果是,删除它。
  • 返回新的nums列表,并进行必要的删除,从列表末尾最多返回len(numtails)个索引位置。

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


如果显式使用索引,则可以修改正在迭代的列表:

1
2
3
4
5
6
7
8
9
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

它不适用于迭代器,因为迭代器不知道在删除任意元素时如何修改索引,所以更容易禁止它。有些语言有迭代器,它具有删除"当前项"的函数。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

编辑:在这里粘贴一种方法,以防你卡住并想知道。


def移除相邻(nums):

1
2
3
4
5
6
7
8
9
10
11
newList=[]

for num in nums:

    if num not in newList:

        newList.append(num)

newList.sort()

return  newList


既然您在一个Python类中,我猜您对该语言还是个新手。因此,对于您和其他初学者来说,我编写了一个简单的代码版本来帮助其他人理解逻辑。

1
2
3
4
5
6
7
8
9
10
11
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