У меня есть массив с повторяющимися значениями.

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

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

Нечто подобное работает, но мне интересно, есть ли более прямой подход:

let originalValues = [
  'a',
  'a',
  'a',
  'b',
  'b',
  'c',
  'c',
  'd'
];

let distinct = new Set(originalValues);
/*
distinct -> { 'a', 'b', 'c', 'd' }
*/

// Perhaps originalValues.extract(distinct) ??
for (let val of distinct.values()) {
  const index = originalValues.indexOf(val);
  originalValues.splice(index, 1);
}

/* 
originalValues -> [
  'a',
  'a',
  'b',
  'c'
];
*/
6
Lucas Ricoy 8 Янв 2017 в 17:45

6 ответов

Лучший ответ

Вы можете создать простой цикл Array.prototype.reduce с hash table для подсчета количества вхождений и заполнения результата только в том случае, если это происходит более одного раза.

Смотрите демо ниже:

var originalValues=['a','a','a','a','b','b','b','c','c','d'];

var result = originalValues.reduce(function(hash) {
  return function(p,c) {
    hash[c] = (hash[c] || 0) + 1;
    if(hash[c] > 1)
      p.push(c);
    return p;  
  };     
}(Object.create(null)), []);

console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}
1
kukkuz 8 Янв 2017 в 15:05

Не следует использовать indexOf внутри цикла, потому что он имеет линейную стоимость, а общая стоимость становится квадратичной. Я бы использовал карту для подсчета вхождений каждого элемента в вашем массиве, а затем преобразовал обратно в массив, вычитая одно вхождение.

let originalValues = ['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd'];
let freq = new Map(); // frequency table
for (let item of originalValues)
  if (freq.has(item)) freq.set(item, freq.get(item)+1);
  else freq.set(item, 1);
var arr = [];
for (let [item,count] of freq)
  for (let i=1; i<count; ++i)
    arr.push(item);
console.log(arr);

Если все элементы являются строками, вы можете использовать простой объект вместо карты.

2
Oriol 8 Янв 2017 в 15:03

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

let oV = ["a", "a", "a", "a", "b", "b", "c", "c", "d"]

var o = {}
var distinct = oV.reduce(function(r, e) {
  if (!o[e]) o[e] = 1 && r.push(e) && oV.splice(oV.indexOf(e), 1)
  return r;
}, [])

console.log(distinct)
console.log(oV)
1
Nenad Vracar 8 Янв 2017 в 15:08

Используйте Array#filter в сочетании с Set:

const originalValues = ['a', 'a', 'a', 'b', 'b', 'c',  'c', 'd'];

const remainingValues = originalValues.filter(function(val) {
  if (this.has(val)) { // if the Set has the value
    this.delete(val); // remove it from the Set
    return false; // filter it out
  }

  return true;
}, new Set(originalValues));



console.log(remainingValues);
5
Ori Drori 8 Янв 2017 в 15:45

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

const originalValues = ['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd'];

var r = originalValues.reduce(function(p, c, i, a) {
  var lIndex = a.lastIndexOf(c);
  var index = a.indexOf(c)
  if (lIndex === index || index !== i)
    p.push(c);
  return p
}, [])

console.log(r)

Если дубликатов нет, вы можете напрямую удалить первую итерацию

const originalValues = ['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd'];

var r = originalValues.filter(function(el, i) {
  return originalValues.indexOf(el) !== i
})

console.log(r)
1
Rajesh 8 Янв 2017 в 15:22