JavaScript:移除数组中的重复项(包含子数组)

8

目前我正在使用JavaScript,需要遍历一个二维数组,并确定是否存在重复的数组,然后删除这些重复的数组。在这种情况下,运行时间很重要,因此我想知道最有效的方法是什么。

在这种情况下,使用哈希表是否可取?其范围是对每个序列进行哈希处理,然后使用哈希来确定该序列是否再次出现。因此,每个序列都是主数组中的一个数组,任何重复项都将是同一数组中的其他数组。此外,所有单个数组的顺序本身都非常重要(即单个数组中的元素必须始终保持其位置)。此外,单个数组中的所有元素都是字符串值。

例如:假设有一个数组A,它的元素依次是以下数组:

A[0] = ["one", "two", "three", "four"]
A[1] = ["two", "one", "three", "four"]
A[2] = ["one", "two", "three", "four"]

在上面的例子中,A[0]和A[2]是重复的,因此该函数应返回A[0]和A[1],以便相同的数组只有一个实例。

4
好问题,但你的尝试在哪里? - Amit Joki
在实施之前,想知道最理想的解决方案是什么,因为时间复杂度至关重要。 - Katia Abela
效率在这里有两个意思:如果编写最快的算法需要一天时间,但你只是检查100个数组,我不确定这是否高效。也许一个双重for循环就足够了。A和A[n]的大小是多少? - Pablo Lozano
效率指算法运行所需的时间,而不是编写算法所需的时间。 - Katia Abela
首先,它们不是重复的。它们是单独的数组文字,恰好序列化为相同的内容,但它们不是“相同”的数组,例如['one'] == ['one']; // false。您可以编写一个使用lodash的_.isEqual函数来比较它们的函数,或者您可以使用普通的JavaScript来完成,但这不是一个两行代码就能搞定的事情。特别是如果真正的数组比字符串列表更复杂。如果只是字符串/基元,则可以与item.join(',')进行比较。 - Dimitar Christoff
以上用例实际上是通过 ES5 方法实现的,足以让您开始使用。http://jsfiddle.net/dimitar/gvn95x9v/ - Dimitar Christoff
3个回答

8
保持一个对象,其中键是每个数组的连接元素。如果找不到键,则将数组添加到输出数组中,并将键添加到对象中。
var hash = {};
var out = [];
for (var i = 0, l = A.length; i < l; i++) {
  var key = A[i].join('|');
  if (!hash[key]) {
    out.push(A[i]);
    hash[key] = 'found';
  }
}

DEMO


2
我建议使用不同的“join”字符。最好是一个在输入数组字符串中找不到的字符。如果它应该适用于任何输入,则可以使用JSON.stringify选项。 - Prusse
1
“['foo', 'bar']”不等同于“['foobar']”,但在您的解决方案中,它们将被视为相等。 - Felix Kling
很好,我选择了 | - Andy

2
首先,让我们看看朴素解决方案的复杂性: 如果有n个数组,每个数组最多有k个条目,则需要进行O(n^2 * k)次比较,因为对于这些n个数组中的每一个,您都必须将其与k个比较相同的n-1其他数组进行比较。空间复杂度为O(n*k) 因此,如果你愿意用空间换取更好的性能,可以采取以下措施: (短免责声明:我假设所有数组都有相等数量的k个元素,这一点在你的问题中被指出但未获得批准。)
逐个遍历数组,选取第一个元素,我们称之为a。 使用哈希映射验证是否以前见过此元素。如果没有,请创建以a为根的树结构,在哈希映射中将其存储在a下,并将其作为您的当前节点。 现在,对于当前数组中的每个后续条目,检查您的当前节点是否有这种类型的子项。因此,如果第二个条目是b,则将b添加为a的子项。
您的树现在如下所示:(从左到右:根到子节点)
a - b
接下来,当第三个元素c出现时,处理方式完全相同:
a - b - c
现在我们跳到一个数组[a, c, d],您首先遇到了元素的树。对于第二个元素,检查c是否已经是a的子项。如果不是,请添加它:
  - b - c
a
  - c

同样适用于下一个条目:
  - b - c
a
  - c - d

让我们看一下对于之前提到的数组[a, b, c]进行检查时会发生什么情况:
首先,我们检查a,发现已经有了一棵树,并从哈希映射表中获取它。接下来,我们注意到a有一个名为b的子节点,所以我们会下降到b。现在,对于最后一个条目,我们发现它已经存在,这告诉我们我们遇到了重复项,可以将其删除。
很抱歉画得不太好,但我希望我能传达这个想法。它只是关于仅通过一次遍历每个数组,以非冗余的方式存储它们。因此,时间复杂度将为O(n*k)。使用的空间增加,但由于最坏情况是没有任何数组共享任何前缀,因此空间复杂度被限制为O(n*k)
希望我没有忽略什么。

感谢您的输入。使用树结构来处理这个问题实际上并没有在我的脑海中浮现过。话虽如此,不是所有的数组都有相等数量的元素。 - Katia Abela
不客气。那么,我的方法不再适用了,因为你无法区分路径的前缀是否已经被遇到过了... - user3363866

1

ONELINER

A.filter((r={},a=>!(r[a]=++r[a]|0)))

我假设您的字符串不包含,字符。如果包含,则将r[a]两次更改为r[a.join('|')](其中|是任意分隔符),或者使用r[a.map(x=>x.length+','+x)]来允许您字符串中的所有字符。这里是工作示例说明r = {}中,我们设置了一次临时对象。在过滤函数a=>...中,仅用于在参数r = {}中声明一次空临时对象。在函数a=>...中,在a中,我们有当前的A元素。JS在r[a]中进行隐式转换a转换为字符串。然后在!(r[a]=++r[a]|0)中,我们增加元素a的出现次数并返回true(作为过滤函数值),如果元素a首次出现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接