Как удалить повторяющиеся элементы массива в JavaScript?
2 года назад·3 мин. на чтение
5 простых способов удаления дубликатов элементов в массиве JavaScript.
Введение
Массивы являются одной из наиболее часто используемых структур данных в JavaScript. Часто массив может содержать дубликаты элементов, которые необходимо удалить для эффективной обработки данных. В этом блоге мы рассмотрим пять способов удаления дубликатов элементов в массиве с помощью JavaScript, ранжированных от наиболее эффективных до наименее эффективных.1. Использование объекта Set
Объект Set
является наиболее эффективным способом удаления дубликатов из массива в JavaScript. Это коллекция уникальных значений, что означает, что она автоматически удаляет дубликаты. Используя spread оператор, мы можем преобразовать объект Set
обратно в массив, в результате чего получается массив, содержащий только уникальные значения. Вот пример:
В этом примере мы сначала определяем массив с повторяющимися значениями. Затем мы создаем новый объектconst arr = [1, 1, 2, 2, 3, 4, 4, 5]; const uniqueArr = [...new Set(arr)]; console.log(uniqueArr); // [1, 2, 3, 4, 5]
Set
с помощью оператора spread (...
). Наконец, мы снова преобразуем объект Set
обратно в массив с помощью оператора spread. Результирующий массив содержит только уникальные значения.
2. Использование метода .filter
Метод .filter
является вторым наиболее эффективным способом удаления дубликатов из массива в JavaScript. Он создает новый массив, который включает только элементы, отвечающие определенному условию, в данном случае элемент появляется в массиве только один раз. Вот пример:
В этом примере мы используем методconst arr = [1, 1, 2, 2, 3, 4, 4, 5]; const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index); console.log(uniqueArr); // [1, 2, 3, 4, 5]
.filter
для создания нового массива uniqueArr
, который включает только элементы, удовлетворяющие условию внутри функции. Условие проверяет, равен ли индекс текущего элемента индексу его первого появления в массиве. Если это так, это означает, что элемент не является дубликатом и может быть включен в новый массив.
3. Использование метода .reduce
Метод .reduce
немного менее эффективен, чем метод .filter
. Он позволяет свести массив к одному значению, применив функцию к каждому элементу массива. В этом случае мы можем использовать .reduce
для создания нового массива, включающего только уникальные значения. Вот пример:
В этом примере мы используем методconst arr = [1, 1, 2, 2, 3, 4, 4, 5]; const uniqueArr = arr.reduce((acc, curr) => { if (!acc.includes(curr)) { acc.push(curr); } return acc; }, []); console.log(uniqueArr); // [1, 2, 3, 4, 5]
.reduce
для итерации по каждому элементу массива и применяем функцию, которая проверяет, находится ли текущий элемент уже в массиве аккумулятора (acc
). Если это не так, мы добавляем его в массив аккумулятора. Метод .reduce
возвращает конечное значение массива аккумулятора, который является массивом, содержащим только уникальные значения.
4. Использование метода .forEach
Метод .forEach
менее эффективен, чем предыдущие методы, но он по-прежнему является жизнеспособным вариантом. Он позволяет перебирать каждый элемент массива и применять к нему функцию. В этом случае мы можем использовать .forEach
для создания нового массива, содержащего только уникальные значения.
Вот пример:
В этом примере мы используем методconst arr = [1, 1, 2, 2, 3, 4, 4, 5]; const uniqueArr = []; arr.forEach((item) => { if (!uniqueArr.includes(item)) { uniqueArr.push(item); } }); console.log(uniqueArr); // [1, 2, 3, 4, 5]
.forEach
для итерации по каждому элементу массива и применения функции, которая проверяет, находится ли уже текущий элемент в массиве uniqueArr
. Если это не так, мы добавляем его в массив uniqueArr
. Результирующий массив содержит только уникальные значения.
5. Использование цикла for
Использование цикла for
является наименее эффективным способом удаления дубликатов из массива. Он требует больше кода и вычислительной мощности, и он не такой лаконичный, как другие методы. Вот пример:
В этом примере мы используем циклconst arr = [1, 1, 2, 2, 3, 4, 4, 5]; const uniqueArr = []; for (let i = 0; i < arr.length; i++) { if (!uniqueArr.includes(arr[i])) { uniqueArr.push(arr[i]); } } console.log(uniqueArr); // [1, 2, 3, 4, 5]
for
для итерации по каждому элементу массива. Проверяем, есть ли текущий элемент в массиве uniqueArr
. Если это не так, мы добавляем его в массив uniqueArr
. Результирующий массив содержит только уникальные значения.
Итоги
Удаление дубликатов из массива является распространенной задачей в JavaScript. В этой статье мы рассмотрели пять различных способов выполнения этой задачи с помощью JavaScript. Каждый метод имеет свои преимущества и недостатки, поэтому вам решать, какой метод наилучшим образом соответствует вашим потребностям. Независимо от того, используется ли объектSet
, метод .filter
, метод .reduce
, метод .forEach
или цикл for
, можно легко удалить дубликаты из массива и более эффективно обрабатывать данные.Композиция функций. Функциональное программирование
2 года назад·5 мин. на чтение
Композиция — это способ построения больших модулей из более мелких. В этой статье подробно рассмотрим композицию на примере JavaScript.
Это серия статей о функциональном программировании:
Пример работы функции
Рассмотрим пример с более конкретными функциями:
Функция
Реализация
Добавим
Добавим для всех параметров, кроме
- Парадигмы программирования
- Композиция (рассматривается в этой статье)
- Функторы
- Каррирование
- Чистые функции
- Функции первого класса
Что такое композиция функций?
Композиция — это процесс объединения небольших единиц в более крупные, которые решают более крупные задачи. При композиции входные данные одной функции приходят из выходных данных предыдущей.Как работает композиция?
Математическая запись определения композиции выглядит следующим образом:В JavaScript композиция, где(f ∘ g)(x) = f(g(x))
f
и g
- это функции, будет выглядеть так:
Функцияconst compose = (f, g) => (x) => f(g(x))
compose
является композицией функций f
и g
. Выходные данные функции g
будут переданы на вход функции f
. По другому эта запись выглядела бы следующим образом:
const compose = (f, g) => { return (x) => { const gResult = g(x) const fResult = f(gResult) return fResult } }
Пример работы функции compose
Рассмотрим пример с более конкретными функциями:
Теперь составим композицию из этих маленьких функций -const getAge = (user) => user.age const isAgeAllowed = (age) => age >= 30
getAge
и isAgeAllowed
:
const user = { name: 'John', age: 35 } const isAllowedUser = compose( isAgeAllowed, getAge ) isAllowedUser(user) // true
compose
выполняет функции справа налево. Мы отправляем объект user
в функцию isAgeAllowed
. Далее user
попадает сначала в getAge
, потом результат этой функции попадает в isAgeAllowed
.
compose
и pipe
Функция pipe
очень похожа на функцию compose
. Они выполняют одну и ту же роль. Обе объединяют функции в цепочки. Однако их реализация и порядок выполнения функций отличается.
Реализация compose
и pipe
compose
реализован следующим образом:
const compose = (...fns) => { return (x) => fns.reduceRight((acc, fn) => fn(acc), x) }
pipe
реализован следующим образом:
Они отличаются лишь в применении функцииconst pipe = (...fns) => { return (x) => fns.reduce((acc, fn) => fn(acc), x) }
reduce
и reduceRight
. Это влияет лишь на порядок выполнения функций.
Порядок выполнения функций
compose
выполняет функции справа налево.
pipe
выполняет функции слева направо.
Пример
Предположим имеется 3 функции:f
, g
и h
.
При использовании compose
:
Порядок выполнения функций будет такимcompose(f, g, h) ← ← ←
h
, g
и далее f
(справа налево). И выходные данные будут передаваться в следующую функцию.
При использовании pipe
:
Порядок выполнения функций будет такимpipe(f, g, h) → → →
f
, g
и далее h
(слева направо). И выходные данные будут передаваться в следующую функцию.
Какую функцию использовать?
compose
и pipe
не сильно отличаются друг от друга. Они решают одну и ту же задачу. compose
ближе к математической нотации (f ∘ g)(x) = f(g(x))
. С pipe
визуально легче воспринимать порядок выполнения функций.
Реальный пример
В следующих примерах будем использоватьpipe
. Предположим, нужно сделать простой калькулятор цен, в котором нужно применить:
- налог (30%, по умолчанию),
- сервисный сбор (10у.е., по умолчанию),
- скидку,
- купон,
- цену доставки на основе веса.
const priceCalculator = ( taxPercentage = 0.3, serviceFees = 10, price, discount, percentCoupon, weight, shippingPricePerKg ) => { // реализация }
Реализация без композиции
Эта функция выполняет свою задачу, но ее сложно читать, тестировать и отлаживать.const priceCalculator = ( taxPercentage = 0.3, serviceFees = 10, price, discount, percentCoupon, weight, shippingPricePerKg ) => { return ( price - (price * percentCoupon) - discount + (weight * shippingPricePerKg) + serviceFees ) * (1 + taxPercentage) }
Реализация с композицией
Эта реализация выглядит более чистой, ее легко тестировать и отлаживать. Все это благодаря модульности.const priceCalculator = ( taxPercentage = 0.3, serviceFees = 10, price, discount, percentCoupon, weight, shippingPricePerKg ) => { const applyTax = (val) => val * (1 + taxPercentage) const applyServiceFees = (val) => val + serviceFees const applyPercentCoupon = (val) => val - val * percentCoupon const applyDiscount = (val) => val - discount const applyShippingCost = (val) => val + weight * shippingPricePerKg return pipe( applyPercentCoupon, applyDiscount, applyShippingCost, applyServiceFees, applyTax )(price) }
Запуск примера
Запустим, передав толькоprice
. Очевидно, получим неверный результат, т.к. передали не все обязательные параметры.
Сначала продебажим. Для дебагаpriceCalculator(10) // NaN
pipe
и compose
можно добавить удобную функцию inspect
. Она просто логирует в консоль входные данные и возвращает эти данные без изменений.
const inspect = (label) => (x) => { console.log(`${label}: ${x}`) return x }
inspect
в цепочку выполнения.
Результат будет примерно таким:const priceCalculator = ( taxPercentage = 0.3, serviceFees = 10, price, discount, percentCoupon, weight, shippingPricePerKg ) => { const applyTax = (val) => val * (1 + taxPercentage) const applyServiceFees = (val) => val + serviceFees const applyPercentCoupon = (val) => val - val * percentCoupon const applyDiscount = (val) => val - discount const applyShippingCost = (val) => val + weight * shippingPricePerKg return pipe( inspect('price'), applyPercentCoupon, inspect('after applyPercentCoupon'), applyDiscount, inspect('after applyDiscount'), applyShippingCost, inspect('after applyShippingCost'), applyServiceFees, inspect('after applyServiceFees'), applyTax )(price) }
Видим,priceCalculator(10) // price: undefined // after applyPercentCoupon: NaN // ...
price
- undefined
. Это произошло потому что price
- третий аргумент, а мы передаем его первым.
Быстрый фикс
Сделаем так, чтобы функция принимала один объект:Далее, используем:const priceCalculator = ({ taxPercentage = 0.3, serviceFees = 10, price, discount, percentCoupon, weight, shippingPricePerKg }) => { // ... }
Все еще получаемpriceCalculator({ price: 10 }) // price: 10 // after applyPercentCoupon: NaN // ...
NaN
, в этот раз потому что не был передан percentCoupon
и он имеет значение undefined
.
price
, значения по умолчанию.
Если запустить заново, получим результат:const priceCalculator = ({ taxPercentage = 0.3, serviceFees = 10, price, discount = 0, percentCoupon = 0, weight = 0, shippingPricePerKg = 0 }) => { // ... }
Это пример того как композиция позволяет нам тестировать и исправлять код проще и быстрее, просто проверяя области, которые вызывают подозрения. Проблема, которую мы отлаживали, была очень простой. Когда мы переходим к более масштабным модулям, все становится еще более трудным для проверки. Большие атомарные функции трудно поддерживать. Разделение функций на более мелкие упрощает отладку, тестирование, поддержку и разработку функций.priceCalculator({ price: 10 }) // 26
Для чего нужна композиция?
Композиция это объединение меньших модулей в более крупные. Если мы думаем оперируем модулями (что обеспечивается композицией), мы улучшаем:- модульное мышление,
- тестируемость,
- возможность отладки,
- поддерживаемость.