Как найти дубликат в массиве, используя подстроку

Я пытаюсь отфильтровать массив на наличие повторяющихся значений, разделив/подстроив элементы.

Мне нужно разделить эти данные на «-», найти повторяющуюся строку после «-» и записать элемент с повторяющейся строкой в ​​повторяющийся массив.

Вот над чем я работаю:

  var arr = ['abc-10.10.10.0/22','abc-10.01.21.0/22','abc-10.01.01.0/22','abcd-10.01.01.0/22'];
    var duplicates = [];

arr.forEach(function (value, index, array) {
    if (array.indexOf(value, index + 1) !== -1
        && duplicates.indexOf(value) === -1) {
        duplicates.push(value);
    }
});

console.info("Duplicate values:", duplicates); 
//Desired output 'abc-10.01.01.0/22','abcd-10.01.01.0/22'

🤔 А знаете ли вы, что...
JavaScript может выполняться как на стороне клиента (в браузере), так и на стороне сервера (с использованием Node.js).


4
80
4

Ответы:

Использование Object.groupBy

Самая простая реализация — просто группируйте по подстроке и возвращайте все результаты со счетчиком > 1.

function getDuplicates(xs, selector) {
    const group = Object.groupBy(xs, selector);
    return Object.keys(group)
        .filter(k => group[k].length > 1)
        .map(k => group[k])
        .flat();
}

var arr = ['abc-10.10.10.0/22','abc-10.01.21.0/22','abc-10.01.01.0/22','abcd-10.01.01.0/22'];
getDuplicates(arr, (x) => x.split('-')[1])

Пользовательская реализация

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

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

function getDuplicates(xs, selector) {
    const selectSet = new Set(); // list of all duplicate found selectors
    const selectToXs = {}; // key of selector to return original values
    for(const x of xs) {
        const sub = selector(x);
        if (sub in selectToXs) {
            selectSet.add(sub);
            selectToXs[sub].add(x);
        } else {
            selectToXs[sub] = new Set([x]);
        }
    }
    return [...selectSet].map((s) => [...selectToXs[s]]).flat();
}

var arr = ['abc-10.10.10.0/22','abc-10.01.21.0/22','abc-10.01.01.0/22','abcd-10.01.01.0/22'];
getDuplicates(arr, (x) => x.split('-')[1])

вероятно, не оптимально:

const arr = ['abc-10.10.10.0/22','abc-10.01.21.0/22','abc-10.01.01.0/22','abcd-10.01.01.0/22'];


const afterSet = new Set();
const duplicateSet = new Set();
arr.forEach(function(str){
  const afterString = str.split('-').at(1);
  if (afterString !== undefined &&
     afterSet.size === afterSet.add(afterString).size){
      duplicateSet.add(afterString);
     }
});
const duplicates = arr.filter(
  function(str){
    return duplicateSet.has(str.split('-').at(1))
  }
);

console.info("Duplicate values:", duplicates); 
//Desired output 'abc-10.01.01.0/22','abcd-10.01.01.0/22'

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

var arr = [
  'abc-10.10.10.0/22',
  'abc-10.01.21.0/22',
  'abc-10.01.01.0/22', // Duplicate
  'abcd-10.01.01.0/22' // Duplicate
];

var groups = arr.reduce(function(groups, value, index, array) {
  var tokens = value.split('-');
  var key = tokens.length > 0 ? tokens[1] : value;
  if (groups[key] === undefined) {
    groups[key] = [];
  }
  groups[key].push(value);
  return groups;
}, {});

var duplicates = Object.values(groups)
  .reduce(function(duplicates, values) {
    if (values.length > 1) {
      return duplicates.concat(values);
    }
    return duplicates;
  }, []);


// Expected: abc-10.01.01.0/22 abcd-10.01.01.0/22
console.info("Duplicate values:", ...duplicates);
.as-console-wrapper { top: 0; max-height: 100% !important; }

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

const arr = [
  'abc-10.10.10.0/22',
  'abc-10.01.21.0/22',
  'abc-10.01.01.0/22', // Duplicate
  'abcd-10.01.01.0/22' // Duplicate
];

const duplicates = findDuplicates(arr, (v) => v.split('-').pop());

// Expected: abc-10.01.01.0/22 abcd-10.01.01.0/22
console.info('Duplicates:', ...duplicates);

function findDuplicates(arr, keyFn) {
  const result = [];
  const seen = new Set();
  const staged = new Map();
  for (let item of arr) {
    const key = keyFn?.(item) ?? item;
    if (!seen.has(key)) {
      seen.add(key);
      staged.set(key, item);
    } else {
      if (staged.has(key)) {
        result.push(staged.get(key));
        staged.delete(key);
      }
      result.push(item);
    }
  }
  console.info('Unique: ', ...[...staged.values()]); // DEBUG!
  return result;
}
.as-console-wrapper { top: 0; max-height: 100% !important; }

Решено

Чтобы использовать Array#indexOf, вам сначала нужно будет использовать Array#map и String#split для создания нового массива целевых подстрок.

Но вы также можете добавить внутренний цикл и использовать равенство для сравнения строк следующим образом:

const arr = ['abc-10.10.10.0/22','abc-10.01.21.0/22','abc-10.01.01.0/22','abcd-10.01.01.0/22'];
const duplicates = [];

arr.forEach(function (value, index, array) {
    array.slice(index+1).forEach(function(val, i) {
        if (value.split('-')[1] === val.split('-')[1]
            && duplicates.indexOf(value.split('-')[1]) === -1) {
            duplicates.push(value);
        }
    });
});

console.info("Duplicate values:", duplicates); 
//Desired output 'abc-10.01.01.0/22','abcd-10.01.01.0/22'