ramda.js

Filter array based on a value in nested array with Ramda


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)

  

Solution

  • 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.