У меня массив массивов на один уровень глубины и нужно рассчитать сумму длин вложенных массивов, т.е. Длину.
Попытка выяснить хороший идиоматический способ сделать это с Рамдой.
Нынешнее решение, которое у меня есть, не кажется достаточно кратким. Наверное, я что-то упускаю.
Вы можете предложить лучше?
const arr = [[1], [2, 3], [4, 5, 6]]
const lengthDeep = R.pipe(
R.map(R.prop('length')),
R.sum
)
console.log(lengthDeep(arr)) // 6
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>
Хотя это очень простой и простой вопрос, я думаю, что это облегчает интересный момент.
До сих пор есть три предложения. Я пропускаю ответ @ftor, так как он игнорирует ваш комментарий об этом, являясь частью обучения Рамде. Но я включаю его комментарий о сворачивании.
Вот решения:
const lengthDeep = R.compose(R.sum, R.map(R.length));
const lengthDeep = R.compose(R.length, R.flatten);
const lengthDeep = R.reduce((total, xs) => total + R.length(xs), 0);
(Обратите внимание, что я переключился с pipe
на compose
. Обычно я использую compose
когда функция подходит для одной строки, но я не думаю о pipe
и compose
как принципиально разные решения.)
Это соответствует разному пониманию проблемы.
Версия A (R.compose(R.sum, R.map(R.length))
) является наиболее простой, и я считаю, что она наиболее точно R.compose(R.sum, R.map(R.length))
первоначальную презентацию проблемы: мы хотим найти "сумму длин" вложенных массивов ". Эта версия является наиболее простой: она находит эти длины и затем объединяет их. (Это ваша версия, дополненная наблюдением @trincot, что R.length
будет делать вместо R.prop('length')
.) Это тот, который я бы выбрал. Это довольно просто, и очевидно, что он делает.
Версия B (R.compose(R.length, R.flatten)
) соответствует совсем другой концепции проблемы. Он отвечает на вопрос: "Если бы я объединил все эти массивы в один, как долго это будет?" Насколько я могу судить, единственным преимуществом этого является то, что это самый простой код. С другой стороны, для запуска, вероятно, требуется больше времени, и определенно требуется гораздо больше места.
Версия C (R.reduce((total, xs) => total + R.length(xs), 0)
) включает еще одно понятие. Лучший способ описать это решение - это рекурсивное описание. Глубокая длина пустого массива массивов равна нулю. Глубинная длина массива массивов, чей первый элемент имеет длину n
равен n
плюс глубокая длина оставшейся части массива массивов. Если вы так думаете об этой проблеме, эта версия может быть для вас. Есть еще один момент, когда вы можете использовать его: хотя я не тестировал, я ожидал бы, что он будет более результативным, поскольку он только один раз перебирает внешний список. Итак, если вы обнаружили, что эта функция является узким местом в вашем коде (вы проверяете производительность, прежде чем вводить какую-либо оптимизацию производительности, верно?), Вы можете переключиться на нее, хотя код существенно сложнее. (Я не знаю, есть ли разумная версия без его очков. Я не вижу простой, и это уже достаточно читаемо, как это.)
Опять же, я бы выбрал версию A, если что-то важное не побудило меня переключиться на версию C. Однако это не кажется вероятным.
Все это, возможно, очень длинный способ не согласиться с комментарием @ftor: "Вы не должны использовать map
когда вы фактически fold
структуру данных". Вместо этого я бы сказал, что вы должны использовать простейший код, соответствующий вашей ментальной модели проблемы. Это должно быть смягчено другими соображениями, такими как производительность, но это должно быть значение по умолчанию. Моя концепция этой проблемы абсолютно совпадает с моделью "взять все длины и добавить их вместе".
Прежде всего, вы можете использовать R.length
вместо R.prop('length')
. Вы также можете рассмотреть возможность сглаживания массива, после чего требуемый результат - это длина:
R.pipe(R.flatten, R.length)
Другой способ, которым вы можете это сделать, используя небольшой помощник, называемый mapReduce
- мы можем реализовать его с использованием curry
Ramda, чтобы он мог поделиться волшебным интерфейсом карри, как и другие члены библиотеки Rambda.
mapReduce
эффективно принимает функцию отображения m
и функцию уменьшения r
и создает новую восстанавливающую функцию. Это полезная универсальная функция, потому что она может использоваться везде, где вы хотите генерировать редукторы
В качестве дополнительного бонуса это решение будет только итерации через входной массив один раз (минимальное требование для вычисления ответа)
// mapReduce :: (a -> b) -> ((c, b) -> c) -> ((c, a) -> c)
const mapReduce = curry ((m, r) =>
(x, y) => r (x, m (y)))
// deepLength :: [[a]] -> Integer
const deepLength = xs =>
reduce (mapReduce (length, add), 0, xs)
// arr :: [[Integer]]
const arr = [[1], [2, 3], [4, 5, 6]]
console.log (deepLength (arr))
// 6
Чтобы продемонстрировать разнообразную полезность mapReduce
, я покажу вам, как он способен обрабатывать вещи, когда они немного сложнее - при сохранении читаемой программы
// mapReduce :: (a -> b) -> ((c, b) -> c) -> ((c, a) -> c)
const mapReduce = curry ((m, r) =>
(x, y) => r (x, m (y)))
// omap :: (a -> b) -> {k : a} -> {k : b}
const omap = curry ((f, o) =>
reduce (mapReduce (k => ({ [k]: f(o[k]) }), Object.assign), {}, keys(o)))
console.log (omap (add(10), {a: 1, b: 2, c: 3}))
// {"a": 11, "b": 12, "c": 13}
mapReduce
или mapper
.