有效地比较任意分配标签的列表

时间:2016-01-14 10:59:44

标签: python algorithm list comparison

我有两个项目标签列表(来自群集),它们代表相同的项目,但分配了不同的(任意)标签。一个例子:

labels1 = [1, 1, 2, 2, 3, 3, 3, 1, 1]
labels2 = [0, 0, 1, 1, 4, 4, 4, 0, 0]

每个列表中的结构相同,因此找到的簇与其标签相同。通过按标签首次出现的顺序重命名标签,可以将它们转换为以下列表。

renamed = [0, 0, 1, 1, 2, 2, 2, 0, 0]

我正在寻找的是一种检查此属性的方法,因此问题在于找到一种在relabel函数中执行重新标记的有效方法。

labels1 = [1, 1, 2, 2, 3, 3, 3, 1, 1]
labels2 = [0, 0, 1, 1, 4, 4, 4, 0, 0]

def relabel(labels):
    """Rename list of labels to the order they first appear in the list.
    """
    seen = []
    renamed = []
    for l in labels:
        if l not in seen:
            seen.append(l)
        renamed.append(seen.index(l))
    return renamed

assert relabel(labels1) == relabel(labels2)

我所做的工作,我只是想知道是否有一种更有效的方式来进行这种比较,而我却不知道。例如,如果列表很大,会使用生成器表达式帮助吗?

3 个答案:

答案 0 :(得分:2)

我看到有两件事可以改进。首先,由于您为所看到的标签使用list,因此l not in seenseen.index(l)操作需要O(n)次。您可以使用list

代替dict

然后,正如您自己建议的那样,您可以返回包含yield关键字的生成器,而不是返回列表。

def relabel(labels):
    """
    Rename list of labels to the order they first appear in the list.
    """
    seen = dict()
    for l in labels:
        if l not in seen:
            seen[l] = len(seen)
        yield seen[l]

assert all(x == y for x, y in zip(relabel(labels1), relabel(labels2)))

答案 1 :(得分:2)

您的原始功能不会返回结果,我很惊讶您说它有效。我们可以在这里优化一些事项:

  • 我们将使用字典seen而不是列表,因为list.index对O(n)来说代价高昂
  • seen会将项目映射到新名称,这只是字典的当前长度 - 但len使用O(1)的成本更低。 x in some_dictx in some_list的O(n)相比也是O(1)。
  • 最后,我们将您的函数重写为生成器,并使用allizip检查生成器表达式中两个重新标记的相等性。 all将在第一次不匹配时停止。

以下是代码:

from itertools import izip

def relabel(labels):
    seen = {}
    for l in labels:
        if l not in seen:
            seen[l] = len(seen)
        yield seen[l]

def compare_labels(l1,l2):
    if len(l1) != len(l2):
        return False

    l1 = relabel(l1)
    l2 = relabel(l2)
    return all(x==y for x,y in izip(l1,l2))

编辑:我刚刚意识到只使用izip代替izip_longest并预先检查长度更好。如果确保传递给compare_labels的两个标签的长度始终相同,则可以将此签出保留。

答案 2 :(得分:0)

除了以上答案之外 - 您不需要两次重新标记,也不需要浏览整个列表(您可以在第一次不匹配时停止)。如果目标是验证此属性,则:

 def verify(labels1, labels2):
     seen = {}
     used = {}
     for (x, y) in izip_longest(labels1, labels2):
         if x == None or y == None: return False
         if seen.has_key(x):
             if seen[x] != y: return False
         else:
             if used.has_key(y): return False
             seen[x] = y
             used[y] = True
     return True

此算法适用于O(min(len(labels1),len(labels2))),它使用O(num(labels1)+ num(labels2))内存。

如果标签集是有限的(并且最好是小的),那么您可以通过使用位操作加速查找used集中的值(这不会改变渐近速度,但可能会导致实践中的大量增益)。