0

I'm trying to learn Ramda, but I'm struggling with seemingly simple stuff. How would I write the filter and sort using Ramda's pipe?

const items = [
  { id: 1, subitems: [{name: 'Foo', price: 1000}]},
  { id: 2, subitems: [{name: 'Bar'}]},
  { id: 3, subitems: [{name: 'Foo', price: 500}]},
  { id: 4, subitems: [{name: 'Qux'}]},
]

const findFoo = value => value.name === 'Foo'

items
  .filter(item => item.subitems.find(findFoo))
  .sort((a, b) => a.subitems.find(findFoo).price > b.subitems.find(findFoo).price ? -1 : 1)

// [{ id: 3, subitems: [...] }, { id: 1, subitems: [...] })

I've tried something like this but it returns an empty array:

R.pipe(
  R.filter(
    R.compose(
      R.path(['subitems']),
      R.propEq('name', 'Foo')
    )
  ),
  // Todo: sorting...
)(items)

  

2 Answers 2

2

Ramda's sortBy may help here. You could just do the following:

const findFoo = pipe (prop ('subitems'), find (propEq ('name', 'Foo')))

const fn = pipe (
  filter (findFoo),
  sortBy (pipe (findFoo, prop ('price')))
)

const items = [{id: 1, subitems: [{name: 'Foo', price: 1000}]}, {id: 2, subitems: [{name: 'Bar'}]}, {id: 3, subitems: [{name: 'Foo', price: 500}]}, {id: 4, subitems: [{name: 'Qux'}]}]

console .log (fn (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {pipe, prop, find, propEq, filter, sortBy} = R                  </script>

Obviously if we tried, we could make this entirely point-free, and address the concerns about double-extracting the Foo subobject. Here's a working version that converts the elements into [fooSubObject, element] pairs (where the former may be nil), then runs a filter to collect the elements where the fooSubObject is not nil, sorts by their price, then unwraps the elements from the pairs.

const fn = pipe (
 map (chain (pair) (pipe (prop ('subitems'), find (propEq ('name', 'Foo'))))),
 pipe (filter (head), sortBy (pipe (head, prop ('price')))),
 map (last)
)

const items = [{id: 1, subitems: [{name: 'Foo', price: 1000}]}, {id: 2, subitems: [{name: 'Bar'}]}, {id: 3, subitems: [{name: 'Foo', price: 500}]}, {id: 4, subitems: [{name: 'Qux'}]}]

console .log (fn (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {pipe, map, chain, pair, prop, find, propEq, filter, last, sortBy, head} = R</script>

But to my eyes, this is a horrible, unreadable mess. We can tame it a bit by extracting a helper taking a function to generate the gloss object we will need for filtering and sorting, and our main process function (that actually does the filtering and sorting) using the gloss function to create the [gloss, element] pairs as above, calling our process and then extracting the second element from each resulting pair. As per Ori Drori's answer, we'll name that function dsu. It might look like this:

const dsu = (gloss, process) => 
  compose (map (last), process, map (chain (pair) (gloss)))

const fn = dsu (
 pipe (prop ('subitems'), find (propEq ('name', 'Foo'))),
 pipe (filter (head), sortBy (pipe (head, prop ('price'))))
)

const items = [{id: 1, subitems: [{name: 'Foo', price: 1000}]}, {id: 2, subitems: [{name: 'Bar'}]}, {id: 3, subitems: [{name: 'Foo', price: 500}]}, {id: 4, subitems: [{name: 'Qux'}]}]

console .log (fn (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {compose, map, last, chain, pair, pipe, prop, find, propEq, filter, head, sortBy} = R</script>     

This is better, and maybe marginally acceptable. But I still prefer the first version above.

Sign up to request clarification or add additional context in comments.

Comments

2

You can use a DSU sort:

  1. Decorate - map the original array, and create a tuple with the price of the found item in the sub-array, or -Infinity if none, and the original object.
  2. Sort by using the price (the 1st item in the tuple).
  3. Undecorate - Map again an extract the original object.

const { pipe, propEq, find, map, applySpec, prop, propOr, identity, sortWith, descend, head, last } = R

const findItemByProp = pipe(propEq, find)

const dsu = (value) => pipe(
  map(applySpec([ // decorate with the value of the item in the sub-array
    pipe(
      prop('subitems'),  // get subitems
      findItemByProp('name', value),  // find an item with the name
      propOr(-Infinity, 'price') // extract the price or use -Infinity as a fallback
    ), 
    identity // get the original item
  ])),
  sortWith([descend(head)]), // sort using the decorative value
  map(last) // get the original item
)

const items = [{"id":1,"subitems":[{"name":"Foo","price":1000}]},{"id":2,"subitems":[{"name":"Bar"}]},{"id":3,"subitems":[{"name":"Foo","price":500}]},{"id":4,"subitems":[{"name":"Qux"}]}]

const result = dsu('Foo')(items)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

3 Comments

Inspired by this, I added a version that creates a higher-order dsu function. I can see many uses, although in the end I would use it over my simpler version only if there were demonstrable performance problems. At some point, Ramda had a function that worked like this, or so I remember. I'll look around and see if I can find it, or find what happened to it.
Nice! The generic DSU is concise. I liked the chain(pair). I started with chain but couldn't find a way to create the tuple.
I've used the technique often enough, but never before thought to extract it to a stand-alone function. chain and ap on functions are really nice. chain(f, g) //=> (x) => f(g(x))(x). and ap(f, g) //=> (x) => f(x)(g(x)). This means that if we want to work with [original, gloss] records instead of [gloss, original], it's only a slight tweak: const dsu = (gloss, process) => compose (map (head), process, map (ap (pair) (gloss))).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.