Декартово произведение без дубликатов


Я использую декартову функцию произведения, которая с учетом [1], [1,2,3], [1,2,3] возвращает 9 комбинаций:

[ [ 1, 1, 1 ],
  [ 1, 2, 1 ],
  [ 1, 3, 1 ],
  [ 1, 1, 2 ],
  [ 1, 2, 2 ],
  [ 1, 3, 2 ],
  [ 1, 1, 3 ],
  [ 1, 2, 3 ],
  [ 1, 3, 3 ] ]
Но мне нужно удалить те, у которых одни и те же элементы, независимо от порядка, поэтому [ 1, 3, 1 ] и [ 1, 1, 3 ] для меня одинаковы. Результат должен содержать 6 пунктов:
[ [ 1, 1, 1 ],
  [ 1, 2, 1 ],
  [ 1, 3, 1 ],
  [ 1, 2, 2 ],
  [ 1, 3, 2 ],
  [ 1, 3, 3 ] ]
Я могу написать функцию, которая сравнивает все возможные пары с _.xor, но для больших чисел это, вероятно, будет очень неэффективно. Есть ли хороший способ в Javascript сделать это? Эффективный способ сравнить все возможные пары или алгоритм декартова произведения без дубликатов?
3 3

3 ответа:

Сортировать каждый массив декартова произведения

[ 1, 2, 1 ] -> [1 , 1 , 2]
[ 1, 1, 2 ] -> [1 , 1 , 2]

Затем соберите эти отсортированные массивы в набор, который удалит дубликаты.

Конечно, вы можете сделать это во время построения декартова произведения, а не после него.

JavaScript имеетSet иMap , однако они сравнивают объекты и массивы по ссылке, а не по значению, поэтому вы не можете воспользоваться этим напрямую. Идея состоит в том, чтобы использовать ключевую функцию, которая сортирует и кодирует элементы json, прежде чем поместить их в набор.

Чистый ES5:

function product(sets) {
  if (sets.length > 0) {
    var head = sets[0];
    var tail = product(sets.slice(1));
    var result = [];
    head.forEach(function(x) {
      tail.forEach(function(xs) {
        var item = xs.slice(0);
        item.unshift(x);
        result.push(item);
      });
    });
    return result;
  } else {
    return [[]];
  }
}

function myKeyFn(item) {
  return JSON.stringify(item.slice(0).sort());
}

function uniqBy(items, keyFn) {
  var hasOwn = Object.prototype.hasOwnProperty, keyset = {};
  return items.filter(function(item) {
    var key = keyFn(item);
    if (hasOwn.call(keyset, key)) {
      return false;
    } else {
      keyset[key] = 1;
      return true;
    }
  });
}

function uniqProduct(sets) {
  return uniqBy(product(sets), myKeyFn);
}

function log(x) {
  console.log(x);
  var pre = document.createElement('pre');
  pre.appendChild(document.createTextNode(x));
  document.body.appendChild(pre);
}

log(uniqProduct([[1],[1,2,3],[1,2,3]]).map(JSON.stringify).join("\n"));
<pre></pre>

Лодаш + современный JavaScript :

// Note: This doesn't compile on current babel.io/repl due to a bug

function product(sets) {
  if (sets.length > 0) {
    const [x, ...xs] = sets;
    const products = product(xs);
    return _.flatMap(x, head => products.map(tail => [head, ...tail]));
  } else {
    return [[]];
  }
}

function uniqProduct(sets) {
  return _.uniqBy(product(sets), x => JSON.stringify(x.slice(0).sort()));
}

console.log(uniqProduct([[1],[1,2,3],[1,2,3]]).map(JSON.stringify).join("\n"));

JavaScript имеет структуру данныхset .

Поэтому храните результаты в наборе, где каждый элемент набора представляет собой набор пар чисел из исходных наборов, а также количество раз, когда это число встречается.

Таким образом, ваш результат будет выглядеть примерно так:

[ 
  {1:3},
  {1:2, 2: 1},
  { 1:2, 3:1},
  { 1:1, 2:2},
  { 1:1, 2:1, 3:1},
  { 1:1, 3:2 }  ]

Таким образом, вы не сможете добавить объект во второй раз в набор.