Рамда длина глубокая

1

У меня массив массивов на один уровень глубины и нужно рассчитать сумму длин вложенных массивов, т.е. Длину.
Попытка выяснить хороший идиоматический способ сделать это с Рамдой.
Нынешнее решение, которое у меня есть, не кажется достаточно кратким. Наверное, я что-то упускаю.
Вы можете предложить лучше?

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>

PS: Я изучаю Ramda, пытаясь применить его к ежедневному кодированию.
Теги:
functional-programming
lodash
ramda.js

3 ответа

2

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

До сих пор есть три предложения. Я пропускаю ответ @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 структуру данных". Вместо этого я бы сказал, что вы должны использовать простейший код, соответствующий вашей ментальной модели проблемы. Это должно быть смягчено другими соображениями, такими как производительность, но это должно быть значение по умолчанию. Моя концепция этой проблемы абсолютно совпадает с моделью "взять все длины и добавить их вместе".

  • 0
    Привет, Скотт, мой комментарий основан на мнении и не заслуживает правильного ответа. Конечно, ОП может составить свой собственный фолд с промежуточным массивом. Все, что хотелось бы сказать, это то, что «лучший» код не обязательно самый краткий.
  • 1
    @ftor, я не задумывался ответить на ваш комментарий. Только когда я написал большую часть своего ответа, я понял, что он противоречит вашему комментарию. Я, конечно, не думаю, что это в целом плохая идея, и мне нравится, что вы разбили проблему. Я абсолютно согласен, что краткость не является основным достоинством кода.
Показать ещё 2 комментария
2

Прежде всего, вы можете использовать R.length вместо R.prop('length'). Вы также можете рассмотреть возможность сглаживания массива, после чего требуемый результат - это длина:

R.pipe(R.flatten, R.length)
  • 0
    Это выглядит великолепно! Благодарю. Единственным недостатком может быть производительность на больших массивах. Я имею в виду, что это слишком много, чтобы сгладить, просто чтобы получить длину.
1

Другой способ, которым вы можете это сделать, используя небольшой помощник, называемый 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}
  • 0
    Карта преобразователь является правильным инструментом здесь. Поскольку это по сути композиция функций во втором аргументе, мне интересно, есть ли более общее имя, чем mapReduce или mapper .
  • 0
    Мне также любопытно, можем ли мы упростить код, используя правильное сгибание, где аккумулятор является вторым аргументом редуктора, и мы можем просто скомпоновать первый. Это потребует карри решения, конечно. Я сделаю это.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню