算法 - 如何有效地删除列表中的重复元素?

时间:2009-11-26 03:59:32

标签: java c++ python algorithm haskell

16 个答案:

答案 0 :(得分:28)

假设订单很重要:

  • 创建一个空集S和一个空列表M.
  • 一次扫描列表L一个元素。
  • 如果元素在集合S中,则跳过它。
  • 否则,将其添加到M和S。
  • 对L中的所有元素重复。
  • 返回M.

在Python中:

>>> L = [2, 1, 4, 3, 5, 1, 2, 1, 1, 6, 5]
>>> S = set()
>>> M = []
>>> for e in L:
...     if e in S:
...         continue
...     S.add(e)
...     M.append(e)
... 
>>> M
[2, 1, 4, 3, 5, 6]

如果订单无关紧要:

M = list(set(L))

答案 1 :(得分:18)

特例:哈希与平等

首先,我们需要确定一些关于假设的东西,即等于和存在函数关系。这是什么意思?我的意思是对于源对象S的集合,给定作为S的元素的任何两个对象x1和x2,存在(哈希)函数F,使得:

if (x1.equals(x2)) then F(x1) == F(x2)

Java有这样的关系。这允许您将重复项检查为近O(1)操作,从而将算法简化为简单的O(n)问题。如果订单不重要,那么它就是一个简单的单线:

List result = new ArrayList(new HashSet(inputList));

如果订单很重要:

List outputList = new ArrayList();
Set set = new HashSet();
for (Object item : inputList) {
  if (!set.contains(item)) {
    outputList.add(item);
    set.add(item);
  }
}

你会注意到我说“靠近O(1)”。这是因为这样的数据结构(如Java HashMap或HashSet)依赖于一种方法,其中一部分哈希码用于在后备存储中找到一个元素(通常称为存储桶)。桶的数量是2的幂。这样,该列表中的索引很容易计算。 hashCode()返回一个int。如果您有16个桶,您可以通过将hashCode与15进行AND运算来找到要使用的桶,从而为您提供0到15之间的数字。

当您尝试在该存储桶中放置某些东西时,它可能已被占用。如果是,那么将对该桶中的所有条目进行线性比较。如果碰撞率太高或者你试图在结构中放置太多元素,那么通常会增加一倍(但总是以2的幂为单位)并且所有项目都放在他们的新桶中(基于新的)面具)。因此,调整这种结构的大小是相对昂贵的。

查找也可能很昂贵。考虑这个课程:

public class A {
  private final int a;

  A(int a) { this.a == a; }

  public boolean equals(Object ob) {
    if (ob.getClass() != getClass()) return false;
    A other = (A)ob;
    return other.a == a;
  }

  public int hashCode() { return 7; }
}

此代码完全合法,它符合equals-hashCode合约。

假设你的集合只包含A个实例,你的插入/搜索现在变成O(n)操作,将整个插入变成O(n 2 )。

显然这是一个极端的例子,但指出这样的机制还依赖于地图或集合使用的值空间中相对较好的散列分布是有用的。

最后,必须说这是一个特例。如果您使用的语言没有这种“哈希快捷方式”,那么这是一个不同的故事。

一般情况:无订购

如果列表中没有排序函数,那么你就会遇到O(n 2 )每个对象与其他对象的强力比较。所以在Java中:

List result = new ArrayList();
for (Object item : inputList) {
  boolean duplicate = false;
  for (Object ob : result) {
    if (ob.equals(item)) {
      duplicate = true;
      break;
    }
  }
  if (!duplicate) {
    result.add(item);
  }
}

一般情况:订购

如果存在排序函数(例如,整数或字符串列表),则对列表进行排序(即O(n log n)),然后将列表中的每个元素与下一个元素进行比较( O(n))所以总算法是O(n log n)。在Java中:

Collections.sort(inputList);
List result = new ArrayList();
Object prev = null;
for (Object item : inputList) {
  if (!item.equals(prev)) {
    result.add(item);
  }
  prev = item;
}

注意:以上示例假设列表中没有空值。

答案 2 :(得分:7)

如果顺序无关紧要,您可能想尝试用Python编写的算法:

>>> array = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6]
>>> unique = set(array)
>>> list(unique)
[1, 2, 3, 4, 5, 6]

答案 3 :(得分:7)

在haskell中

这将由nubnubBy函数

涵盖
nub :: Eq a => [a] -> [a]
nub [] = []
nub (x:xs) = x : nub (filter (/= x) xs)

nubBy :: (a -> a -> Bool) -> [a] -> [a]
nubBy f [] = []
nubBy f (x:xs) = x : nub (filter (not.f x) xs)

nubBy放宽了对Eq类型类的依赖,而是允许你定义自己的相等函数来过滤重复项。

这些函数在一致的任意类型列表上工作(例如,haskell中不允许[1,2,"three"]),并且它们都是保持顺序的。

为了提高效率,可以使用Data.Map(或实现平衡树)将数据收集到一个集合中(key是元素,value是原始列表的索引,以便能够获得原始排序),然后将结果收集回列表并按索引排序。我稍后会尝试实施。


import qualified Data.Map as Map

undup x = go x Map.empty
    where
        go [] _ = []
        go (x:xs) m case Map.lookup x m of
                         Just _  -> go xs m
                         Nothing -> go xs (Map.insert x True m)

这是@FogleBird解决方案的直接翻译。不幸的是,没有导入它就无法工作。


替换Data.Map导入的一个非常基本的尝试是实现一个树,就像这样

data Tree a = Empty
            | Node a (Tree a) (Tree a)
            deriving (Eq, Show, Read)

insert x Empty = Node x Empty Empty
insert x (Node a left right)
    | x < a = Node a (insert x left) right
    | otherwise = Node a left (insert x right)

lookup x Empty = Nothing --returning maybe type to maintain compatibility with Data.Map
lookup x (Node a left right)
    | x == a = Just x
    | x < a = lookup x left
    | otherwise = lookup x right

一项改进是通过维护深度属性使其在插入上自动平衡(使树不会降级为链表)。关于哈希表的这个好处是它只需要你的类型在类型类Ord中,这对于大多数类型来说很容易派生。


我似乎接受了请求。为了回应@Jonno_FTWs,这里有一个解决方案可以完全消除结果中的重复项。它与原版并不完全不同,只需添加一个额外的案例。但是,运行时性能会慢得多,因为您要遍历每个子列表两次,一次是elem,第二次是重复。另请注意,现在它无法在无限列表中使用。

nub [] = []
nub (x:xs) | elem x xs = nub (filter (/=x) xs)
           | otherwise = x : nub xs

有趣的是,你不需要过滤第二个递归情况,因为elem已经检测到没有重复。

答案 4 :(得分:4)

在Python中

>>> L = [2, 1, 4, 3, 5, 1, 2, 1, 1, 6, 5]
>>> a=[]
>>> for i in L:
...   if not i in a:
...     a.append(i)
...
>>> print a
[2, 1, 4, 3, 5, 6]
>>>

答案 5 :(得分:3)

在java中,它是一个单行。

Set set = new LinkedHashSet(list);

将为您提供删除重复项目的集合。

答案 6 :(得分:2)

对于Java可以使用:

private static <T> void removeDuplicates(final List<T> list)
{
    final LinkedHashSet<T> set;

    set = new LinkedHashSet<T>(list); 
    list.clear(); 
    list.addAll(set);
}

答案 7 :(得分:2)

在Python

中删除列表中的重复项

案例:列表中的项目不可清除或可比较

即我们无法使用setdict)或sort

from itertools import islice

def del_dups2(lst):
    """O(n**2) algorithm, O(1) in memory"""
    pos = 0
    for item in lst:
        if all(item != e for e in islice(lst, pos)):
            # we haven't seen `item` yet
            lst[pos] = item
            pos += 1
    del lst[pos:]

案例:项目可以播放

解决方案来自here

def del_dups(seq):
    """O(n) algorithm, O(log(n)) in memory (in theory)."""
    seen = {}
    pos = 0
    for item in seq:
        if item not in seen:
            seen[item] = True
            seq[pos] = item
            pos += 1
    del seq[pos:]

案例:项目具有可比性,但不具备可用性

那就是我们可以使用sort。此解决方案不保留原始订单。

def del_dups3(lst):
    """O(n*log(n)) algorithm, O(1) memory"""
    lst.sort()
    it = iter(lst)
    for prev in it: # get the first element 
        break
    pos = 1 # start from the second element
    for item in it: 
        if item != prev: # we haven't seen `item` yet
            lst[pos] = prev = item
            pos += 1
    del lst[pos:]

答案 8 :(得分:1)

  • 浏览列表并为每个项目分配顺序索引
  • 根据元素的某些比较函数对列表进行排序
  • 删除重复项
  • 根据指定的索引对列表进行排序

为了简单起见,项目的索引可以存储在类似std :: map

的内容中 如果我没有遗漏任何内容,

看起来像是O(n * log n)

答案 9 :(得分:1)

这取决于你的意思“有效”。朴素算法是O(n ^ 2),我假设你实际意味着你想要的东西比那个低。

正如Maxim100所说,您可以通过将列表与一系列数字配对来保留订单,使用您喜欢的任何算法,然后将其余部分恢复到原始顺序。在Haskell中它看起来像这样:

superNub :: (Ord a) => [a] -> [a]
superNub xs = map snd 
              . sortBy (comparing fst) 
              . map head . groupBy ((==) `on` snd) 
              . sortBy (comparing snd) 
              . zip [1..] $ xs

当然你需要导入Data.List(sort),Data.Function(on)和Data.Ord(比较)。我只能背诵这些函数的定义,但重点是什么?

答案 10 :(得分:1)

我已经为字符串编写了一个算法。实际上,你有什么类型并不重要。

static string removeDuplicates(string str)
{
    if (String.IsNullOrEmpty(str) || str.Length < 2) {
        return str;
    }

    char[] arr = str.ToCharArray();
    int len = arr.Length;
    int pos = 1;

    for (int i = 1; i < len; ++i) {

        int j;

        for (j = 0; j < pos; ++j) {
            if (arr[i] == arr[j]) {
                break;
            }
        }

        if (j == pos) {
            arr[pos] = arr[i];
            ++pos;
        }
    }

    string finalStr = String.Empty;
    foreach (char c in arr.Take(pos)) {
        finalStr += c.ToString();
    }

    return finalStr;
}

答案 11 :(得分:0)

Python中的一行解决方案
使用list-comprehension:

>>> L = [2, 1, 4, 3, 5, 1, 2, 1, 1, 6, 5]
>>> M = []
>>> zip(*[(e,M.append(e)) for e in L if not e in M])[0]
(2, 1, 4, 3, 5, 6)

答案 12 :(得分:0)

也许你应该考虑使用关联数组(也就是python中的dict)来避免首先出现重复的元素。

答案 13 :(得分:0)

我的Java代码:

ArrayList<Integer> list = new ArrayList<Integer>();

list.addAll({1,2,1,3,4,5,2,3,4,3});

for (int i=0; i<list.size(); i++)
{
    for (int j=i+1; j<list.size(); j++)
    {
        if (list.get(i) == list.get(j))
        {
            list.remove(i);
            j--;
        }
    }
}

或只是这样做:

SetList<Integer> unique = new SetList<Integer>();

unique.addAll(list);

两种方式都有时间= nk~O(n ^ 2)

其中n是输入列表的大小,

k是输入列表的唯一成员数

答案 14 :(得分:0)

算法delete_duplicates(a [1 .... n])

//从给定数组中删除重复项

//输入参数:a [1:n],n个元素的数组

{

temp[1:n]; // n个元素的数组

 temp[i]=a[i];for i=1 to n

     temp[i].value=a[i]

        temp[i].key=i

* //基于'value'对数组临时排序。 *

//基于'value'删除temp中的重复元素。

//基于'key'排序数组temp.//使用temp。构建一个数组p

p[i]=temp[i].value

return p

使用'key'在输出数组中维护其他元素。考虑密钥长度为O(n),对密钥执行排序所花费的时间和值为O(nlogn)。因此,从数组中删除所有重复项所花费的时间是O(nlogn)。

答案 15 :(得分:0)

接近已接受答案的通用解决方案

k = ['apple', 'orange', 'orange', 'grapes', 'apple', 'apple', 'apple']
m = []


def remove_duplicates(k):
    for i in range(len(k)):
        for j in range(i, len(k)-1):
            if k[i] == k[j+1]:
                m.append(j+1)

    l = list(dict.fromkeys(m))
    l.sort(reverse=True)

    for i in l:
        k.pop(i)

    return k


print(remove_duplicates(k))