【问题标题】:Removing duplicate sets of items in a sequence删除序列中的重复项集
【发布时间】:2020-09-30 13:53:37
【问题描述】:

我有一个列表,例如data = [0, 4, 4, 2, 5, 8, 5, 8, 7, 1, 5, 6, 1, 5, 6],我需要从中删除项目集(集k = 3 的最大长度),但前提是这些集彼此跟随。 data 包括三个这样的情况:[4, 4][5, 8, 5, 8][1, 5, 6, 1, 5, 6],因此清理后的列表应该类似于 [0, 4, 2, 5, 8, 7, 1, 5, 6]

我试过这段代码,它可以工作:

data = np.array([0, 4, 4, 2, 5, 8, 5, 8, 7, 1, 5, 6, 1, 5, 6])
for k in range(1, 3):
    kth_difference = data[k:] - data[:-k]
    ids = np.where(kth_difference)
    data = data[ids]

但如果我将输入列表更改为data = [0, 4, 4, 2, 5, 8, 5, 8, 7, 1, 5, 6, 1, 5](打破最后一组),新的输出列表是[0, 4, 2, 5, 8, 7, 1, 5],最后丢失了6

这个任务有什么解决方案?如何使此解决方案适用于任何k

【问题讨论】:

  • [1, 5, 1, 5] 应该是 [5, 8, 5, 8]?
  • 是的,对不起,我的错误)
  • 2, 2, 2, 2 会发生什么?是2, 2 还是2?还是保证永远不会发生?
  • 这应该是2。如果您有兴趣,这是一个简化的子任务,目标是删除图像字幕中出现的重复单词,当字幕由神经网络生成时,其中列表项是字典中单词的索引
  • 2, 2, 1, 2, 2, 1 会发生什么? 2, 2, 12, 12, 1, 2, 1 ?

标签: python list algorithm numpy


【解决方案1】:

您添加了一个 numpy 标签,所以让我们利用它来发挥我们的优势。从数组开始:

data = np.array([0, 4, 4, 2, 5, 8, 5, 8, 7, 1, 5, 6, 1, 5, 6])

很容易制作一个长度不超过n的元素掩码:

mask_1 = data[1:] == data[:-1]
mask_2 = data[2:] == data[:-2]
mask_3 = data[3:] == data[:-3]

第一个掩码在下一个元素相同的每个位置都有一个掩码。第二个掩码将有一个元素与前面两个元素相同的元素,因此您需要一次查找 2 个元素的运行。这同样适用于第三个掩码。掩码的过滤需要考虑到您希望在最后包含部分匹配的可能性。您可以使用 k-1 元素有效地扩展掩码来完成此操作:

delta = np.diff(np.r_[False, mask_3, np.ones(2, dtype=bool), False].view(np.int8))
edges = np.flatnonzero(delta).reshape(-1, 2)
lengths = edges[:, 1] - edges[:, 0]
delta[edges[lengths < 3, :]] = 0
mask = delta[-k:].cumsum(dtype=np.int8).view(bool)

在这种排列中,mask 屏蔽了构成重复组的重复的三个元素。如果复制的部分被截断,它可能包含少于三个元素。这样可以确保您保留部分重复的所有元素。

对于本练习,我假设您在不同级别之间没有奇怪的重叠。即,属于重复段的数组的每一部分恰好属于一个可能的重复段。否则,掩码处理会变得复杂得多。

这是一个将所有这些包装在一起的函数:

def clean_mask(mask, k):
    delta = np.diff(np.r_[False, mask, np.ones(k - 1, bool), False].view(np.int8))
    edges = np.flatnonzero(delta).reshape(-1, 2)
    lengths = edges[:, 1] - edges[:, 0]
    delta[edges[lengths < k, :]] = 0
    return delta[:-k].cumsum(dtype=np.int8).view(bool)

def dedup(data, kmax):
    data = np.asarray(data)
    kmax = min(kmax, data.size // 2)

    remove = np.zeros(data.shape, dtype=np.bool)
    for k in range(kmax, 0, -1):
        remove[k:] |= clean_mask(data[k:] == data[:-k], k)
    return data[~remove]

您在问题中显示的两个测试用例的输出:

>>> dedup([0, 4, 4, 2, 5, 8, 5, 8, 7, 1, 5, 6, 1, 5, 6], 3)
array([0, 4, 2, 5, 8, 7, 1, 5, 6])

>>> dedup([0, 4, 4, 2, 5, 8, 5, 8, 7, 1, 5, 6, 1, 5], 3)
array([0, 4, 2, 5, 8, 7, 1, 5, 6])

时机

快速基准测试表明,numpy 解决方案也比纯 python 快得多:

for n in range(2, 7):
    x = np.random.randint(0, 10, 10**n)
    y = list(x)
    %timeit dedup(x, 3)
    %timeit remdup(y)

结果:

# 100 elements
 dedup: 332 µs ± 5.01 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
remdup: 36.9 ms ± 152 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

# 1000 elements
 dedup: 412 µs ± 5.88 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
remdup: > 1 minute

注意事项

此解决方案不会尝试涵盖极端情况。例如:data = [2, 2, 2, 2, 2, 2, 2] 或类似的,其中多个级别的k 可以重叠。

【讨论】:

  • 谢谢,您的解决方案真的很有趣!
  • @Plaeryin_Bol 谢谢。它也快得多。我正在添加一些时间
【解决方案2】:

这是一个尝试,这也是我第一次使用breakfor/else

def remdup(l):
  while True:
    for (i,j) in ((i,j) for i in range(0,len(l)) for j in range(i+1, len(l)+1)):
      if l[i:j] == l[j:j+(j-i)]:
        l = l[:j] + l[j+j-i:]
        break  # restart
    else:  # if no duplicate was found
      break  # halt
  return l

print(remdup([0, 4, 4, 2, 5, 8, 5, 8, 7, 1, 5, 6, 1, 5, 6]))
# [0, 4, 2, 5, 8, 7, 1, 5, 6]

这是如何工作的:

  • 迭代所有子字符串l[i:j]
    • 如果l[i:j] 与下一个相同长度的子字符串l[j,j+j-i] 重复:
      • 删除l[j,j+j-i]
      • 中断迭代并重新开始,因为列表已更改
    • 如果没有找到重复的,返回列表

我建议避免使用breakfor/else。它们很丑陋,并且会编写欺骗性代码。

【讨论】:

  • 感谢您的帮助!
猜你喜欢
  • 2015-11-24
  • 1970-01-01
  • 2021-02-25
  • 2016-01-19
  • 1970-01-01
  • 2019-03-19
  • 1970-01-01
  • 2015-09-30
  • 1970-01-01
相关资源
最近更新 更多