无重复的阵列合并


15

我最近在StackOverflow上看到了以下Javascript代码,用于合并两个数组,并删除重复项:

Array.prototype.unique = function() {
    var a = this.concat();
    for(var i=0; i<a.length; ++i) {
        for(var j=i+1; j<a.length; ++j) {
            if(a[i] === a[j])
                a.splice(j--, 1);
        }
    }
    return a;
};

var array1 = ["Vijendra","Singh"];
var array2 = ["Singh", "Shakya"];
var array3 = array1.concat(array2).unique(); 

尽管此代码有效,但效率极低(O(n^2))。您的挑战是使算法复杂度降低。

获胜标准是具有最低复杂度的解决方案,但领带会因字符长度最短而中断。

要求

将所有代码打包到一个满足以下“正确性”要求的函数中:

  • 输入:两个数组
  • 输出:一个数组
  • 将两个数组的元素合并在一起-任何一个输入数组中的任何元素都必须在输出数组中。
  • 输出的数组不应有重复项。
  • 顺序并不重要(与原始顺序不同)
  • 任何语言都很重要
  • 不要使用标准库的数组函数来检测唯一性或合并集合/数组(尽管标准库中的其他内容也可以)。让我区分一下数组串联是好的,但是已经完成上述所有操作的函数却不是。

我们应该如何在不使用数组函数的情况下创建或追加到数组?
EmilVikström2014年

@EmilVikström查看我的编辑。我的意思是您不能使用数组唯一性函数。抱歉,不清楚。
2014年

如果其中一个数组中有重复项,我们是否也要删除它们?例如,应合并[1, 2, 2, 3][2, 3, 4]返回[1, 2, 2, 3, 4]还是[1, 2, 3, 4]
OI 2014年

1
@OI是的,那太容易了。
2014年

1
请问:什么数组?我们是否可以仅假设整数或字符串,还是还必须允许使用诸如多级对象之类的更复杂的东西?
颚ns317 2014年

Answers:


8

佩尔

27个字符

简单的Perl Hack

my @vals = ();
push @vals, @arr1, @arr2;
my %out;
map { $out{$_}++ } @vals;
my @unique = keys %out;

我敢肯定有人可以一口气..因此(感谢唐·黑斯廷斯)

sub x{$_{$_}++for@_;keys%_}

1
“不要使用标准库的数组函数来检测唯一性(尽管标准库可以做其他事情)”
John Dvorak 2014年

1
我如何违反该规则?我没有使用独特的功能?
Zach Leighton 2014年

那它是如何运作的呢?抱歉,我看不懂perl。如果它读取哈希映射的键-用该规则算作确定吗?除非我确信是这样,否则我不会投票。
John Dvorak 2014年

1
它组合了数组,在两个数组上循环,并添加了一个哈希值,该值的关键是数组循环中的当前值。然后它使用了哈希的键,在我的一些工作中就使用了它。.[1,1,2,3,4,4]变成{1 => 2,2 => 1,3 => 1 ,4 => 2}
Zach Leighton 2014年

@ZachLeighton可以将代码缩短为27个字符sub x{$_{$_}++for@_;keys%_}(以防打成平局!),并用作:z((1,2,3,4),(2,3,4,5,6))
Dom Hastings 2014年

10

JavaScript的O(N)131 124 116 92(86?)

高尔夫球版:

function m(i,x){h={};n=[];for(a=2;a--;i=x)i.map(function(b){h[b]=h[b]||n.push(b)});return n}

高尔夫版本:

function m(i,x) {
   h = {}
   n = []
   for (a = 2; a--; i=x)
      i.map(function(b){
        h[b] = h[b] || n.push(b)
      })
   return n
}

我可以这样使用concat86个字符:

function m(i,x){h={};n=[];i.concat(x).map(function(b){h[b]=h[b]||n.push(b)});return n}

但是我不确定基于此JsPerf是否仍然为O(N):http ://jsperf.com/unique-array-merging-concat-vs-looping 因为concat版本在较小的数组中速度稍快,但在较慢时更大的阵列(Chrome 31 OSX)。

在实践中这样做(高尔夫球场上充斥着不良做法):

function merge(a1, a2) {
   var hash = {};
   var arr = [];
   for (var i = 0; i < a1.length; i++) {
      if (hash[a1[i]] !== true) {
        hash[a1[i]] = true;
        arr[arr.length] = a1[i];
      }
   }
   for (var i = 0; i < a2.length; i++) {
      if (hash[a2[i]] !== true) {
        hash[a2[i]] = true;
        arr[arr.length] = a2[i];
      }
   }
   return arr;
}
console.log(merge([1,2,3,4,5],[1,2,3,4,5,6]));

我不太擅长计算复杂性,但我相信这是O(N)。如果有人可以澄清将不胜感激。

编辑:这是一个采用任意数量的数组并将其合并的版本。

function merge() {
   var args = arguments;
   var hash = {};
   var arr = [];
   for (var i = 0; i < args.length; i++) {
      for (var j = 0; j < args[i].length; j++) {
        if (hash[args[i][j]] !== true) {
          arr[arr.length] = args[i][j];
          hash[args[i][j]] = true;
        }
      }
    }
   return arr;
}
console.log(merge([1,2,3,4,5],[1,2,3,4,5,6],[1,2,3,4,5,6,7],[1,2,3,4,5,6,7,8]));

这几乎就是我要在几秒钟后发布的内容:-(是的,如果哈希表实现了具有摊销常数的插入和搜索时间,则这是摊销线性时间(这在许多语言中是常​​见的,不清楚关于JS)
埃米尔Vikström

@EmilVikström非常感谢,我相信JavaScript可以,但是没有证据。抱歉,手指快了,发表评论时放慢了自己:P
George Reith 2014年

这是一个很好的方法。但是,除了格式精美的版本之外,您还可以提供“代码高尔夫”样式的解决方案吗?看到很多人都认为这是正确的方法,所以可能会有一个联系O(N)
2014年

@ cloudcoder2000好吧,我想打印一个完整版本,因为代码高尔夫版本在实践中可能效率较低。
George Reith 2014年

1
@ cloudcoder2000它们不是完全独立的,因此最坏的情况不是O(A*B)(不使用,N因为它令人困惑)。如果每个输入数组(每个A)实际具有相同数量的元素(B),则O(SUM(B) FOR ALL A)可以将其重写为O(N)定义N为所有数组输入的元素计数时的形式。
meiamsome 2014年

4

Python 2.7,38个字符

F=lambda x,y:{c:1 for c in x+y}.keys()

假设哈希函数良好,则应为O(N)。

set如果您不认为Wasi的8字符实施违反规则,则更好。


真好!Python的理解是如此优雅和强大。
OI 2014年

3

PHP,69/ 42 68/41个字符

其中函数声明为68个字符:

function m($a,$b){return array_keys(array_flip($a)+array_flip($b));}

不包括函数声明的是41个字符:

array_keys(array_flip($a)+array_flip($b))

3

Ruby中的一种方法

为了保持上述规则,我将使用与JavaScript解决方案类似的策略,并使用哈希作为中介。

merged_arr = {}.tap { |hash| (arr1 + arr2).each { |el| hash[el] ||= el } }.keys

本质上,这些是我在上面一行中要执行的步骤。

  1. 定义一个merged_arr将包含结果的变量
  2. 初始化一个空的未命名哈希作为中介,以将唯一元素放入
  3. 使用Object#tap填充的散列(如引用hashtap块),并返回其用于后续方法链接
  4. 串联arr1arr2合并为一个未处理的数组
  5. 对于el串联数组中的每个元素,如果当前不存在值elhash[el]则将其放入hash[el]。此处的备注(hash[el] ||= el)是确保元素唯一性的原因。
  6. 获取现在填充的哈希的键(或值,因为它们相同)

这应该及时运行O(n)。如果我做的陈述不正确,或者为了提高效率或可读性而可以改善上述答案,请告诉我。

可能的改进

鉴于哈希键将是唯一的且值无关紧要,因此使用备忘录可能是不必要的,因此这足够了:

merged_arr = {}.tap { |hash| (arr1 + arr2).each { |el| hash[el] = 1 } }.keys

我真的很喜欢Object#tap,但是我们可以使用Enumerable#reduce以下方法实现相同的结果:

merged_arr = (arr1 + arr2).reduce({}) { |arr, val| arr[val] = 1; arr }.keys

您甚至可以使用Enumberable#map

merged_arr = Hash[(arr1 + arr2).map { |val| [val, 1] }].keys

我在实践中将如何做

说了这么多,如果我被要求合并两个数组arr1,并arr2使得结果merged_arr具有独特的元素,并可能在我手上使用任何Ruby方法,我会简单地使用其意在解决这一确切的问题并集运算符:

merged_arr = arr1 | arr2

快速浏览一下的来源Array#|似乎可以确认使用散列作为中介似乎是在2个数组之间执行唯一合并的可接受解决方案。


“不要使用标准库的数组函数来检测唯一性(尽管标准库可以做其他事情)”
John Dvorak 2014年

在第二个示例中,我如何违反该规则?正在对哈希执行备忘。也不允许吗?
OI 2014年

2
Array.prototype.unique = function()
{
  var o = {},i = this.length
  while(i--)o[this[i]]=true
  return Object.keys(o)
}

需要n个数组的函数可能如下:

function m()
{
  var o={},a=arguments,c=a.length,i;
  while(c--){i=a[c].length;while(i--)o[a[c][i]] = true} 
  return Object.keys(o);
}

打高尔夫球,我认为这应该工作(117个字符)

function m(){var o={},a=arguments,c=a.length,i;while(c--){i=a[c].length;while(i--)o[a[c][i]]=1}return Object.keys(o)}

更新 如果要保留原始类型,可以

function m()
{
  var o={},a=arguments,c=a.length,f=[],g=[];
  while(c--)g.concat(a[c])
  c = g.length      
  while(c--){if(!o[g[c]]){o[g[c]]=1;f.push(g[c])}}
  return f
}

或打过149:

function m(){var o={},a=arguments,c=a.length,f=[],g=[];while(c--)g.concat(a[c]);c= g.length;while(c--){if(!o[g[c]]){o[g[c]]=1;f.push(g[c])}}return f}

这仍然会引起一些疑问,如果您想区分123'123',那么这将无法工作。


感谢您的回答。它很短,但是只能解决一半问题。您还需要在解决方案中包括实际的合并部分(即使与原始示例中的合并部分相同),并将其全部合并到一个函数中。另外,您还可以提供“ golfed”版本(如原样O(N))吗?
2014年

这将所有成员转换为字符串。如m([1,2,3,4,5],[2,3,4,5,6],[2,3,4,5,6,7])变成["1", "2", "3", "4", "5", "6", "7"]
乔治瑞思

2

python,46

def A(a,b):print[i for i in b if i not in a]+a

或者,仅使用设置操作

python,8

set(a+b)

1
抱歉,尚不清楚,使用set运算也很容易。
2014年

如果a中有重复项,或者b中有重复项并且该元素不在a中,则您的第一个代码将具有重复项。
Vedant Kandoi,

2

佩尔

如果仅计算子例程中的代码块,则为23个字节。如果允许覆盖全局值(可能会my从代码中删除),则可能为21 。它以随机顺序返回元素,因为顺序无关紧要。至于复杂性,平均而言,它是O(N)(取决于散列冲突的数量,但很少见-在最坏的情况下,它可能是O(N 2)(但这不应该发生,因为Perl可以检测到病理性哈希,并在检测到此类行为时更改哈希函数种子))。

use 5.010;
sub unique{
    my%a=map{$_,1}@_;keys%a
}
my @a1 = (1, 2, 3, 4);
my @a2 = (3, 4, 5, 6);
say join " ", unique @a1, @a2;

输出(还显示随机性):

/tmp $ perl unique.pl 
2 3 4 6 1 5
/tmp $ perl unique.pl 
5 4 6 2 1 3

2

FORTRAN:282 252 233 213

高尔夫球版:

function f(a,b,m,n) result(d);integer::m,n,a(m),b(n),c(m+n);integer,allocatable::d(:);j=m+1;c(1:m)=a(1:m);do i=1,n;if(.not.any(b(i)==c(1:m)))then;c(j)=b(i);j=j+1;endif;enddo;allocate(d(j-1));d=c(1:j-1);endfunction

它不仅看起来无限好,而且实际上会以人类可读的形式进行编译(在打高尔夫球的形式中线条太长):

function f(a,b,m,n) result(d)
  integer::m,n,a(m),b(n),c(m+n)
  integer,allocatable::d(:)
  j=m+1;c(1:m)=a(1:m)
  do i=1,n
     if(.not.any(b(i)==c(1:m)))then
        c(j)=b(i);j=j+1
     endif
  enddo
  allocate(d(j-1))
  d=c(1:j-1)
end function

这应该是O(n)我复制ac,然后b针对所有进行检查c。最后一步是消除c由于未初始化而将包含的垃圾。


2

Mathematica 10个字符

Union[a,b]

例:

a={1,2,3,4,5};
b={1,2,3,4,5,6};
Union[a,b]

{1,2,3,4,5,6}

Mathematica2 43个字符

Sort@Join[a, b] //. {a___, b_, b_, c___} :> {a, b, c}

8
我认为这将属于使用标准库数组方法的范畴。
hkk

嗨@ cloudcoder2000。无需调用某些特定的库即可在Mathematica中使用Union。
Murta 2014年

5
在我看来,使用内建函数精确地执行问题所要求的就是作弊。
Konrad Borowski

好的,好的..第二个代码不使用联合。
Murta 2014年

1
我猜Tally[Join[a, b]][[;; , 1]]也可能是作弊;-)顺便说一句,您可以使用单字母变量保存字符。
伊夫·克莱特

1

Javascript 86

高尔夫球版:

function m(a,b){var h={};return a.concat(b).filter(function(v){return h[v]?0:h[v]=1})}

可读版本:

function merge(a, b) {
  var hash = {};
  return a.concat(b).filter(function (val) {
    return hash[val] ? 0 : hash[val] = 1;
  });
}

1
这忽略falsey值... m([1,0,0,0,0],[0,1,0])的回报[1]
乔治·瑞斯

1
更改h[v]=vh[v]=1
乔治·赖斯2014年

好发现@GeorgeReith!我们从86升至84 :)
Bertrand

现在仍然是86,我认为您感到困惑,因为您从可读版本中删除了2个字符,而不是高尔夫版本中的2个字符。
乔治·里斯


0

如果您要寻找一个基于JavaScript的实现,该实现依赖于框架背后的基础对象有效,那么我将使用Set。通常,在一个实现中,Set对象在插入过程中固有地使用某种二进制搜索索引来处理唯一对象。我知道在Java中这是一种log(n)搜索,它使用二进制搜索,基于这样的事实:没有集合可以包含一个以上的对象。


尽管我不知道Java是否同样适用,但只要实现以下代码段,就可以满足以下要求n*log(n)

JavaScript,61字节

var s = new Set(a);      // Complexity O(a.length)
b.forEach(function(e) {  // Complexity O(b.length) * O(s.add())
  s.add(e);
}); 

在线尝试!


如果以上代码段使用a = [1,2,3]b = [1,2,3,4,5,6]则使用s=[1,2,3,4,5,6]

如果您知道的复杂Set.add(Object)的JavaScript函数让我知道,在这个复杂的n + n * f(O)地方f(O)是复杂s.add(O)


0

APL(Dyalog Unicode),O(N),28字节

匿名默认隐式函数。

(⊢(/⍨)⍳∘≢=⍳⍨),

在线尝试!

, 连接参数 上)

(…… ) 对此应用以下匿名默认功能;O(1)

   ⍳⍨ 索引自拍(整个数组中每个元素首次出现的索引);上)

  = 逐个元素地比较;上):

   ⍳∘≢ 数组长度的索引;上)

(/⍨) 用它来过滤; 上):

   未经修改的论点;O(1)

O(N +1 + N + N + N + N +1)= O(N)


-2

JavaScript,131个字符

var array1 = ["Vijendra","Singh"];   
var array2 = ["Singh", "Shakya"];     
result = Array.from(new Set([...array1, ...array2]))

4
欢迎来到PPCG!请告诉我们这是哪种语言,并将其格式化为代码以提高可读性。(这通过将代码行缩进四个空格来起作用)。另外,您的方法的解释将不胜感激。
Laikoni '18

这只是一个JavaScript代码。
deepak_pal

@techdeepak可以将此类重要信息添加到您的帖子中,对其进行适当的格式设置,添加语法突出显示,并更多地介绍算法的复杂性,因为这是最快的算法。就目前而言,该职位的质量相当低。
乔纳森·弗雷希

-2

PHP大约28个字符[省略了示例数组变量和结果变量]。

$ array1 = array(1、2、3); $ array2 = array(3,4,5);

$ result = array_merge($ array1,$ array2);


从问题开始:不要使用标准库的数组函数来检测唯一性或合并集合/数组。此外,这实际上并不从阵列中删除重复项
乔国王

我认为您已经从以下问题中忽略了这一重要观点:“ 不要使用标准库的数组函数来检测唯一性或合并集合/数组
Peter Taylor

是。没错 谢谢你们指出这一点。批评谦卑地接受了。
Endri

@jo King。您绝对对“不要使用标准库的...”是正确的。其余的是错误的。它不会删除重复项。php.net/manual/en/function.array-merge.php。我建议您完全阅读PHP的文档。我100%确信它能胜任。您只需要注意将哪个阵列视为重复阵列。干杯。
Endri '19

1
我按原样运行了您提交的代码,没有任何更改,并且输出重复。看起来应该阅读文档,即,如果数组包含数字键,则后面的值将不会覆盖原始值,而是将其附加
Jo King
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.