Обновление
Я неправильно прочитал спецификации изначально.Ниже этого раздела я подробно опишу несколько решений другой, но связанной проблемы.Вот решение для запрошенного:
const rec = (arr, uniqs = new Set, dups = new Set, [x, ...xs] = arr) =>
arr.length == 0
? [[...uniqs], [...dups]]
: uniqs.has(x) || dups.has(x)
? rec(xs, (uniqs.delete(x), uniqs), dups.add(x))
: rec(xs, uniqs.add(x), dups)
console.log(rec([2, "str", "arr", "str", 2, "obj", "arg"]));
console.log(rec(["ref", "val", "val", "val", "el", "el"]));
Обратите внимание, что по сравнению с окончательным исходным ответом произошли два изменения:
Мы перешли на Набор для dups
какну, с изменениями, необходимыми для этого.
Эта строка теперь удаляется из набора uniqs
, а не просто передает его:
? rec(xs, (uniqs.delete(x), uniqs), dups.add(x))
Это фактически указывает на проблему API с Set
.Это действительно полезно, что add
возвращает набор.Жаль, что delete
тоже так не делает.Хотя иногда может быть полезно возвращение логического значения, это довольно легко получить с помощью has
.Это слишком поздно, чтобы исправить это, но это действительно позор.
Оригинальный ответ
Вот один из возможных подходов.Функции ES6, такие как параметры отдыха и параметры по умолчанию наряду с Symbol
, обеспечивают довольно элегантную реализацию.
const None = Symbol()
const rec = ([x = None, ...xs], uniqs = [], dups = []) =>
x == None
? [uniqs, dups]
: uniqs.includes(x)
? rec(xs, uniqs, dups.concat(x))
: rec(xs, uniqs.concat(x), dups)
console.log(rec([2, "str", "arr", "str", 2, "obj", "arg"]));
console.log(rec(["ref", "val", "val", "val", "el", "el"]));
Одна реальная помощь для рекурсии в современном JS - это возможность использовать параметры по умолчанию, чтобы позволить вам избежать дополнительных вспомогательных функций.
Использование Symbol
Вот интересный способ объединить наш разброс входного массива в голову и хвост и все же позволить нам проверить, если он пуст.Альтернативное средство показано ниже.
const rec = (arr, uniqs = [], dups = [], [x, ...xs] = arr) =>
arr.length == 0
? [uniqs, dups]
: uniqs.includes(x)
? rec(xs, uniqs, dups.concat(x))
: rec(xs, uniqs.concat(x), dups)
console.log(rec([2, "str", "arr", "str", 2, "obj", "arg"]));
console.log(rec(["ref", "val", "val", "val", "el", "el"]));
Эта версия выглядит немного менее чистой, чем оригинал, но не требует определения вспомогательного символа только для проверки на пустоту.
Есть еще что-тонеправильно с этим;это может иметь проблемы с производительностью.Поскольку для каждого элемента мы должны вызывать O(n)
includes
, общее решение выглядит примерно так: O(n^2)
, в зависимости от соотношения уникальных значений для дубликатов.Это может не быть проблемой, и я могу исправить это только в одном из двух сценариев:
- Я протестировал и обнаружил, что этот код является реальным узким местом в моем приложении
- У меня есть более производительная альтернативная версия, которая жертвует небольшой ясностью кода.
В этом случае у меня есть такая версия:
const rec = (arr, uniqs = new Set(), dups = [], [x, ...xs] = arr) =>
arr.length == 0
? [[...uniqs], dups]
: uniqs.has(x)
? rec(xs, uniqs, dups.concat(x))
: rec(xs, uniqs.add(x), dups)
console.log(rec([2, "str", "arr", "str", 2, "obj", "arg"]));
console.log(rec(["ref", "val", "val", "val", "el", "el"]));
(Мы также могли бы использовать альтернативу Symbol
в этой версии.)
Здесь мы переключаемся на использование Set
, которое точнотип данных, предназначенный для работы с коллекцией уникальных элементов.Это требует небольшого развертывания набора с [...uniqs]
- альтернативно Array.from(uniqs)
- но это очень незначительное увеличение сложности.Остальные изменения касаются переключения с массива на набор.