简体   繁体   中英

Ramda length deep

I have an array of arrays one level deep and need to calculate the sum of lengths of the nested arrays , ie length deep .
Trying to figure out a good idiomatic way to do it with Ramda.
The current solution I've got doesn't feel terse enough. Probably I'm missing something.
Can you please suggest better?

 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: I'm learning Ramda by trying to apply it to everyday coding.

First of all, you can use R.length instead of R.prop('length') . You could also consider flattening the array, after which the required result is the length of that:

R.pipe(R.flatten, R.length)

Although this is a very straightforward and simple question, I think it brings into relief an interesting point.

There are three suggestions so far. I'm skipping @ftor's answer, as it ignores your comment about this being part of learning Ramda. But I'm including his comment about folding.

Here are the solutions:

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);

(Note that I switched from pipe to compose . I usually use compose when the function fits on a single line but I don't think of pipe and compose as fundamentally different solutions.)

These correspond to different understandings of the issue.

Version A ( R.compose(R.sum, R.map(R.length)) ) is the most straightforward, and I believe it maps most closely to the original presentation of the problem: We want to find "the sum of lengths of the nested arrays". This version is the most straightforward: it finds those lengths and then adds them together. (This is your version enhanced with @trincot's observation that R.length will do in place of R.prop('length') .) This is the one I would choose. It is quite simple, and it's obvious what it does.

Version B ( R.compose(R.length, R.flatten) ) corresponds to a very different conception of the problem. It answers the question, "If I combined all these arrays into one, how long would it be?" As far as I can tell, the only advantage to it is that it is the simplest code. On the downside, it is probably takes longer to run, and definitely requires much more space.

Version C ( R.reduce((total, xs) => total + R.length(xs), 0) ) involves still another notion. The best way to describe this solution is with a recursive description. The deep length of an empty array of arrays is zero. The deep length of an array of arrays whose first element has length n is n plus the deep length of the remainder of the array of arrays. If that is how you think about the problem, this version might be for you. There is one other time you might choose to use it: although I haven't tested, I would expect it to be more performant, since it only loops over the outer list once. So if you found that this function was a bottleneck in your code (you do test for performance before introducing any performance optimization, right?), you might switch to it, even though the code is substantially more complex. (I don't know if there's a reasonable points-free version of it. I can't see a simple one, and this is already readable enough like this.)

Again, I would choose Version A unless something important prompted me to switch to Version C . That doesn't seem likely, though.


All this is perhaps a very long-winded way of disagreeing with @ftor's comment: "You shouldn't use map when you actually fold a data structure." I would say instead that you should use the simplest code that matches your mental model of the problem. This must be tempered by other considerations such as performance, but it should be the default. My conception of this problem absolutely matches the "take all the lengths and add them together" model.

Here's another way you can do it using a little helper called mapReduce – we can implement it using Ramda's curry so that it will share a magical curry interface like other Rambda library members.

mapReduce effectively takes a mapping function m and a reducing function r and creates a new reducing function. This is a useful generic function because it can be used anywhere you wish to generate reducers

As an added bonus, this solution will only iterate thru the input array once (the minimum requirement to compute the answer)

// 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

To demonstrate the diverse utility of mapReduce , I'll show you how it is capable of handling things when they're slightly more complicated – while still maintaining a readable program

// 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}

 const arr = [[1], [2, 3], [4, 5, 6]] const lengthDeep = R.reduce(R.useWith(R.add, [R.identity, R.prop('length')]), 0) console.log(lengthDeep(arr)) // 6 
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM