Об’єднання масиву без дублікатів


15

Нещодавно я побачив цей код Javascript на StackOverflow для об'єднання двох масивів та видалення дублікатів:

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öm Дивіться мою редакцію. Я мав на увазі, що ви не можете використовувати унікальні функції масиву. Вибачте за незрозумілість.
hkk

Якщо один з масивів містить у собі дублікати, чи видаляємо їх також? Наприклад, чи слід злиття [1, 2, 2, 3]та [2, 3, 4]повернення [1, 2, 2, 3, 4]чи [1, 2, 3, 4]?
OI

1
@OI Так, це зробило б це занадто просто.
hkk

1
Чи можу я запитати: масиви чого ? Чи можемо ми вважати просто цілі числа чи рядки, чи ми також мусимо допускати складніші речі, такі як багаторівневі об'єкти?
jawns317

Відповіді:


8

Perl

27 персонажів

Простий хакер Perl

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

Я впевнений, що хтось міг би здійснити однолінійку ... і таким чином (Спасибі Дом Гастінгс)

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

1
"Не використовуйте стандартні функції масиву бібліотеки для виявлення унікальності (хоча інші речі формують стандартну бібліотеку в порядку)"
Джон Дворак

1
Як я порушую це правило? Я не використовую унікальні функції?
Зак Лейтон

Як тоді це працює? Вибачте, я не можу прочитати perl. Якщо він читає ключі хеш-карти - чи вважається це нормальним з цим правилом? Я не буду голосувати, поки не переконаюсь, що це так.
Іван Дворак

1
Він поєднує масиви, петлює над обома і додає хеш, збільшуючи значення, котрий ключовим є поточне значення в циклі масиву. Потім він бере ключі цього хешу, я використовував це в деяких своїх роботах .. Отже, [1,1,2,3,4,4] стає {1 => 2, 2 => 1, 3 => 1 , 4 => 2}
Зах Лейтон

@ZachLeighton ви можете скоротити код до 27 знаків sub x{$_{$_}++for@_;keys%_}(якщо він зрівняється !) І використовувати як:z((1,2,3,4),(2,3,4,5,6))
Дом Гастінгс

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
}

Я можу так використовувати concatі робити це в 86 символів:

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

Але я не впевнений, чи це все-таки O (N), заснований на цьому JsPerf: http://jsperf.com/unique-array-merging-concat-vs-looping, оскільки концетна версія незначно швидша з меншими масивами, але повільніше з більші масиви (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).
Еміль Вікстрьом

@ EmilVikström Дякую за це, я вважаю, що JavaScript є, але не маю доказів на це. Вибачте за швидкі пальці, уповільнив себе коментарями: P
Джордж Рейт

Це чудовий підхід. Однак ви могли б також надати рішення у стилі "код-гольф" на додаток до вашої добре відформатованої версії? Бачачи, що багато людей вважають це правильним підходом, можливо, це буде рівнозначним O(N).
hkk

@ cloudcoder2000 Добре, я хотів надрукувати повну версію, оскільки версія коду-гольфу, ймовірно, буде менш ефективною на практиці.
Джордж Рейт

1
@ cloudcoder2000 Вони не є повністю незалежними, тому найгірший випадок не використовується O(A*B)(не використовується, Nоскільки це заплутано). Було б те, що якби кожен вхідний масив (кожен A) мав таку ж кількість елементів ( B), як це є насправді O(SUM(B) FOR ALL A), які можна переписати, як O(N)при визначенні Nяк кількість елементів усіх входів масиву.
meiamsome

4

Python 2.7, 38 символів

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

Повинен бути O (N), припускаючи хорошу хеш-функцію.

setРеалізація символів Васі 8 краща, якщо ви не вважаєте, що вона порушує правила.


Приємно! Зрозуміння в Python можуть бути такими витонченими та потужними.
OI

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

Один із способів у Рубі

Щоб дотримуватися правил, викладених вище, я б використовував аналогічну стратегію, як рішення JavaScript, і використовував хеш як посередник.

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

По суті, це кроки, які я проходжу в рядку вище.

  1. Визначте змінну, merged_arrяка буде містити результат
  2. Ініціалізуйте порожній, безіменний хеш як посередника, щоб помістити унікальні елементи
  3. Використовуйте Object#tapдля заповнення хешу (на який посилається як hashу tapблоці) та повертайте його для подальшого ланцюгового методу
  4. Об'єднати arr1і arr2в єдиний масив, необроблений
  5. Для кожного елемента elв каскадному масиві, помістити значення elв , hash[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#|Хоча, швидкий погляд на джерело , але, здається, підтверджує, що використання хешу в якості посередника здається прийнятним рішенням для виконання унікального об'єднання між двома масивами.


"Не використовуйте стандартні функції масиву бібліотеки для виявлення унікальності (хоча інші речі формують стандартну бібліотеку нормально)"
Джон Дворак

Як я порушую це правило у другому прикладі? Пам'ять виконується на хеші. Це теж не дозволено?
OI

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', тоді це не вийде.


Дякую за відповідь. Це вражаюче коротко, однак це лише половину проблеми. Вам також потрібно включити в рішення фактичну об'єднувальну частину (навіть якщо її така ж, як у вихідному прикладі) і об'єднати все це в одну функцію. Також ви могли б надати додатково до цього (як є O(N)) версію "для гольфу" ?
hkk

Це скидає всіх членів на рядки. наприклад 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

пітон, 46

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

Або, використовуючи задану операцію просто

пітон, 8

set(a+b)

1
Вибачте, це не було зрозуміло, використання набору операцій також обман.
hkk

Ваш перший код матиме дублікати, якщо у дублікаті є дублікати або, якщо є дублікати в b, а цей елемент не в a.
Ведант Кандой

2

Perl

23 байти, якщо порахувати лише блок коду всередині підпрограми. Може бути 21, якщо дозволено перезапис глобальних значень (це буде видалено myз коду). Він повертає елементи у випадковому порядку, тому що порядок не має значення. Що стосується складності, то в середньому це 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

Фортран: 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)як я копіюю aв , cа потім перевірити кожен bпроти всіх c. Останній крок - усунення сміття, яке cбуде міститися, оскільки воно не ініціалізоване.


2

Математика 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}

Математика2 43 символи

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

8
Я думаю, це піде в категорію використання стандартних методів масиву бібліотеки.
hkk

Привіт @ cloudcoder2000. Не потрібно закликати якусь конкретну бібліотеку, щоб використовувати Union у Mathematica.
Мурта

5
На мою думку, за допомогою вбудованої функції зробити саме те, що запитує питання - це обман.
Конрад Боровський

добре, добре .. другий код не використовує Union.
Мурта

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
Це ігнорує значення фальси ... m([1,0,0,0,0],[0,1,0])повертає [1].
Джордж Рейт

1
Змінити h[v]=vна h[v]=1.
Джордж Рейт

Добре помічений @GeorgeReith! Ми поїхали з 86 до 84 :)
Бертран

Це все ще 86, я думаю, ви заплуталися, тому що ви видалили з символу для читання 2 символи, а не гольф.
Джордж Рейт

1

JavaScript 60

Я використовую генератор ES6.
Далі можна перевірити використання Google Traceur REPL .

m=(i,j)=>{h={};return[for(x of i.concat(j))if(!h[x])h[x]=x]}

0

Якщо ви шукаєте реалізацію на основі JavaScript, яка спирається на основні об'єкти, що стоять за рамками, для ефективності, я б тільки що використав Set. Зазвичай у реалізації об'єкт Set властиво обробляє унікальні об'єкти під час вставки за допомогою певної індексації двійкового пошуку. Я знаю, що в Java це log(n)пошук, використовуючи двійковий пошук, заснований на тому, що жоден набір не може містити один об'єкт більше одного разу.


Хоча я поняття не маю, чи це справедливо і для Javascript, для 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 (Діалог Unicode) , O (N), 28 байт

Функція анонімного мовчання.

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

Спробуйте в Інтернеті!

, об'єднати аргументи; O (N)

() Застосувати до цього наступну функцію анонімного мовчання; O (1)

   ⍳⍨ індекси selfie (індекси першого появи кожного елемента у всьому масиві); O (N)

  = порівнювати елемент за елементом до; O (N):

   ⍳∘≢ показники довжини масиву; O (N)

(/⍨) використовувати це для фільтрування; O (N):

   немодифікований аргумент; 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! Скажіть, будь ласка, що це за мова та відформатуйте її як код для кращої читабельності. (Це працює за допомогою відступу кодових рядків на чотири пробіли). Також буде вдячне пояснення вашого підходу.
Лайконі

це просто код JavaScript.
deepak_pal

@techdeepak Ви можете додати таку важливу інформацію до своєї публікації, правильно відформатувати її, додати підсвічування синтаксису та написати трохи більше про складність вашого алгоритму, оскільки це найшвидший алгоритм . На сьогоднішній день ця посада досить низької якості.
Джонатан Фрех

-2

PHP - близько 28 символів [не враховуючи приклад змінних масивів та змінну результатів].

$ array1 = масив (1, 2, 3); $ array2 = масив (3, 4, 5);

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


З питання: Не використовуйте стандартні функції масиву бібліотеки для виявлення унікальності або об'єднання наборів / масивів . Крім того, це фактично не видаляє дублікати з масиву
Jo King

Я думаю, що ви не помітили цього важливого рядка з питання: " Не використовуйте стандартні функції масиву бібліотеки для виявлення унікальності або об'єднання наборів / масивів "
Пітер Тейлор

Так. Це правильно. Дякую, хлопці, за вказане. Критики смиренно приймаються.
Ендрі

@jo король. Ви абсолютно праві щодо "Не використовувати стандартні бібліотеки ...". Решта - неправильно. Це видаляє дублікати. php.net/manual/en/function.array-merge.php . Я рекомендую вам повністю ознайомитися з документацією PHP. Я на 100% впевнений, що це робить свою роботу. Вам просто потрібно бути обережним, який з масивів ви вважаєте дублікатами. Ура.
Ендрі

1
Я буквально запустив код у вашому поданні без змін, і у висновку є дублікати. Схоже, ви повинні прочитати документацію, а саме, якщо, однак, масиви містять цифрові клавіші, пізніше значення не замінить початкове значення, а буде додане
Jo King
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.