Javascript

Удаление повторяющихся объектов из массива (трудно).

Допустим, у нас есть массив таких объектов, как:

const books = [
    {
        name: "My Sister the Serial Killer",  
        author: "Oyinkan Braithwaite" 
    },
    {
        name: "Educated",  
        author: "Tara Westover" 
    },
    {
        name: "My Sister the Serial Killer",  
        author: "Oyinkan Braithwaite" 
    }
];

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

Удаление повторяющихся плоских элементов из массива (легко)

Допустим, у нас есть массив строк, например:

const strings = [
    "My Sister the Serial Killer", 
    "Educated", 
    "My Sister the Serial Killer"
];

Если бы мы хотели удалить любые дубликаты из этого массива, мы могли бы использовать метод filter () вместе с методом indexOf (), чтобы проверить, не является ли какой-либо данный элемент дубликатом.

const filteredStrings = strings.filter((item, index) => {

    // Return to new array if the index of the current item is the same 
    // as the first occurence of the item
    return strings.indexOf(item) === index;

});

Поскольку strings.indexOf (item) всегда будет возвращать индекс первого вхождения элемента, мы можем определить, является ли текущий элемент в цикле фильтрации дубликатом. Если это так, мы не возвращаем его в новый массив, созданный методом filter ().

Объекты работают иначе

Причина, по которой этот же метод не работает с объектами, заключается в том, что любые 2 объекта с идентичными свойствами и значениями фактически не считаются идентичными.

const a = {
    name: "My Sister the Serial Killer",  
    author: "Oyinkan Braithwaite" 
};
const b = {
    name: "My Sister the Serial Killer",  
    author: "Oyinkan Braithwaite" 
};

a === b // false

Это связано с тем, что объекты сравниваются на основе ссылки, а не структуры. Тот факт, что два объекта имеют одинаковые свойства и значения, не учитывается при сравнении двух объектов. Следовательно, indexOf (объект) в массиве объектов всегда будет возвращать индекс переданного объекта, даже если существует другой объект с точно такими же свойствами и значениями.

Решение:

Учитывая эту информацию, единственный способ проверить, имеют ли два объекта одинаковые свойства и значения, — это проверить свойства и значения каждого объекта. Решение, которое я придумал, заключалось в выполнении этой ручной проверки, но с некоторыми улучшениями для повышения производительности и сокращения ненужных вложенных циклов. В частности, я сделал 3 вещи:

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

Вот последняя функция:

function removeDuplicates(arr) {

    const result = [];
    const duplicatesIndices = [];

    // Loop through each item in the original array
    arr.forEach((current, index) => {
    
        if (duplicatesIndices.includes(index)) return;
    
        result.push(current);
    
        // Loop through each other item on array after the current one
        for (let comparisonIndex = index + 1; comparisonIndex < arr.length; comparisonIndex++) {
        
            const comparison = arr[comparisonIndex];
            const currentKeys = Object.keys(current);
            const comparisonKeys = Object.keys(comparison);
            
            // Check number of keys in objects
            if (currentKeys.length !== comparisonKeys.length) continue;
            
            // Check key names
            const currentKeysString = currentKeys.sort().join("").toLowerCase();
            const comparisonKeysString = comparisonKeys.sort().join("").toLowerCase();
            if (currentKeysString !== comparisonKeysString) continue;
            
            // Check values
            let valuesEqual = true;
            for (let i = 0; i < currentKeys.length; i++) {
                const key = currentKeys[i];
                if ( current[key] !== comparison[key] ) {
                    valuesEqual = false;
                    break;
                }
            }
            if (valuesEqual) duplicatesIndices.push(comparisonIndex);
            
        } // end for loop

    }); // end arr.forEach()
  
    return result;
}