balteo
balteo

Reputation: 24679

Using Ramda compose, groupBy and sort in order to process an array of objects

I am new to Ramda and I am trying to achieve the following:

I have an array of objects (i.e. messages).

Because I have two counter parties above, I should have an array of two messages.

Here is what I have attempted:

const rawMessages = [
  {
    "sender": {
      "id": 1,
      "firstName": "JuliettP"
    },
    "recipient": {
      "id": 2,
      "firstName": "Julien"
    },
    "sendDate": "2017-01-28T19:21:15.863",
    "messageRead": true,
    "text": "ssssssss"
  },
  {
    "sender": {
      "id": 3,
      "firstName": "Juliani"
    },
    "recipient": {
      "id": 1,
      "firstName": "JuliettP"
    },
    "sendDate": "2017-02-01T18:08:12.894",
    "messageRead": true,
    "text": "sss"
  },
  {
    "sender": {
      "id": 2,
      "firstName": "Julien"
    },
    "recipient": {
      "id": 1,
      "firstName": "JuliettP"
    },
    "sendDate": "2017-02-07T22:19:51.649",
    "messageRead": true,
    "text": "I love redux!!"
  },
  {
    "sender": {
      "id": 1,
      "firstName": "JuliettP"
    },
    "recipient": {
      "id": 3,
      "firstName": "Juliani"
    },
    "sendDate": "2017-03-13T20:57:52.253",
    "messageRead": false,
    "text": "hello Juliani"
  },
  {
    "sender": {
      "id": 1,
      "firstName": "JuliettP"
    },
    "recipient": {
      "id": 3,
      "firstName": "Juliani"
    },
    "sendDate": "2017-03-13T20:56:52.253",
    "messageRead": false,
    "text": "hello Julianito"
  }
];

const currentUserId = 1;
const groupBy = (m: Message) => m.sender.id !== currentUserId ? m.sender.id : m.recipient.id;
const byDate = R.descend(R.prop('sendDate'));
const sort = (value, key) => R.sort(byDate, value);
const composition = R.compose(R.map, R.head, sort, R.groupBy(groupBy));
const latestByCounterParty = composition(rawMessages);
console.log(latestByCounterParty);

Here is the corresponding codepen:

https://codepen.io/balteo/pen/JWOWRb

Can someone please help?

edit: Here is a link to the uncurried version: here. The behavior is identical without the currying. See my comment below with my question as to the necessity of currying.

Upvotes: 4

Views: 7615

Answers (2)

Scott Sauyet
Scott Sauyet

Reputation: 50797

While I think the solution from Scott Christopher is fine, there are two more steps that I might take with it myself.

Noting that one of the important rules about map is that

map(compose(f, g)) ≍ compose(map(f), map(g))

when we're already inside a composition pipeline, we can choose to unnest this step:

R.map(R.compose(R.head, R.sort(R.descend(R.prop('sendDate'))))),

and turn the overall solution into

const currentMessagesForId = R.curry((id, msgs) =>
  R.compose(
    R.values,
    R.map(R.head), 
    R.map(R.sort(R.descend(R.prop('sendDate')))),
    R.groupBy(m => m.sender.id !== id ? m.sender.id : m.recipient.id)
  )(msgs)
)

Doing this, is, of course, a matter of taste. But I find it cleaner. The next step is also a matter of taste. I choose to use compose for things that can be listed on a single line, and hence make some obvious connection between the formats compose(f, g, h)(x) and f(g(h(x))). If it spans multiple lines, I prefer to use pipe, which behaves the same way, but runs it's functions from first to last. So I would change this a bit further to look like this:

const currentMessagesForId = R.curry((id, msgs) =>
  R.pipe(
    R.groupBy(m => m.sender.id !== id ? m.sender.id : m.recipient.id),
    R.map(R.sort(R.descend(R.prop('sendDate')))),
    R.map(R.head), 
    R.values
  )(msgs)
)

I find this top-down reading easier than the bottom up needed with longer compose versions.

But, as I said, these are matters of taste.

You can see these examples on the Ramda REPL.

Upvotes: 4

Scott Christopher
Scott Christopher

Reputation: 6516

Your example was close to what you wanted, though you need just needed to move the composition of head and sort to the function argument given to map and then call values on the final result to convert the object to an array of values.

const currentMessagesForId = R.curry((id, msgs) =>
  R.compose(
    R.values,
    R.map(R.compose(R.head, R.sort(R.descend(R.prop('sendDate'))))),
    R.groupBy(m => m.sender.id !== id ? m.sender.id : m.recipient.id)
  )(msgs))

currentMessagesForId(currentUserId, rawMessages)

Upvotes: 2

Related Questions