Using Ramda compose, groupBy and sort in order to process an array of objects - ramda.js

I am new to Ramda and I am trying to achieve the following:
I have an array of objects (i.e. messages).
I want to group the messages by counter party ID (either the sender or the recipient ID, whichever is not 1, see my groupBy lambda below).
I am going to obtain an object whose keys will be the counter party IDs and values an array of the messages exchanged with that counter party.
I then want to sort by date descending those arrays of messages.
And finally keep the most recent message and thus obtain an array containing the most recent message exchanged with each of the counter parties.
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.

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.

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)

Related

sort dropdown items (response from JSON) in vuejs

Can anyone please suggest me on how to apply sorting to dropdown list in Vue Js
I have used v-select from vuetify
`<v-select v-model='coursesList' :options="courses" :enableDropup="true" options-label="name" options-value="id" name='courseName[]'></v-select>`
mutations:{
setCourseItems (state,response){
state.courseItems=response
}
}
JSON:
[
{
"stock": "available",
"name": "Higher Engineering maths"
},
{
"stock": "available",
"name": "Qantum Physics"
},
{
"stock": "available",
"name": "Biology"
}
]
In javascript you can use the sort method to sort an array.
Here your response seems to be a json array, you can call sort() on it and give it a custom function for sorting. Example :
var myArray = [
{
stock: "available",
name: "Higher Engineering maths"
},
{
stock: "available",
name: "Qantum Physics"
},
{
stock: "available",
name: "Biology"
}
]
myArray.sort(function (item1, item2) => {
//if the result of this method is negative, item2 is considered "bigger" than item1, and vice versa
return item1.name.toLowerCase() < item2.name.toLowerCase() ? -1 : 1
})
this will sort your array by alphabetical order of the name field. Note that toLowerCase() is not necessary but it's advised in case you don't control the input and don't want to treat capital letters differently.

RxJs marble testing : Assertion fail log hard to understand

I have this Rxjs testing code. It fail deliberately, because i want to show you the failing log. Which i found hard to understand, or at least i cannot read it fluently.
Someone can explain me what means : $[i].frame = i' to equals i'' ?
import { delay } from 'rxjs/operators';
import { TestScheduler } from 'rxjs/testing';
describe('Rxjs Testing', () => {
let s: TestScheduler;
beforeEach(() => {
s = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
});
it('should not work', () => {
s.run(m => {
const source = s.createColdObservable('-x-y-z|');
const expected = '-x-y-z|'; // correct expected value is '---x-y-z|'
const destination = source.pipe(delay(2));
m.expectObservable(destination).toBe(expected);
});
});
});
To help you better understand what is going on with the output, let's first try to follow statements from the console. There is a link that points at where the error has happened. It's at 10th line of code which is this line:
expect(actual).toEqual(expected);
Setting breakpoint to this line and running the test in debug mode reveals actual and expected objects.
actual values are (represented in JSON format):
[
{
"frame": 3,
"notification": {"kind": "N", "value": "x", "hasValue": true}
},
{
"frame": 5,
"notification": {"kind": "N", "value": "y", "hasValue": true}
},
{
"frame": 7,
"notification": {"kind": "N", "value": "z", "hasValue": true}
},
{
"frame": 8,
"notification": {"kind": "C", "hasValue": false}
}
]
And the expected:
[
{
"frame": 1,
"notification": {"kind": "N", "value": "x", "hasValue": true}
},
{
"frame": 3,
"notification": {"kind": "N", "value": "y", "hasValue": true}
},
{
"frame": 5,
"notification": {"kind": "N", "value": "z", "hasValue": true}
},
{
"frame": 6,
"notification": {"kind": "C", "hasValue": false}
}
]
Comparing the two arrays, you can see that frame properties are different for each object of the same index. This weird output comes from Jasmine's toEqual function, so let's try to understand it based on the values from above. This line from the console
Expected $[0].frame = 3 to equal 1.
means that expected value of 1 is not 1, but is actually 3. This part $[0].frame = 3 suggests what the actual value is, and this to equal 1 is what you as developer think it should be. I.e. expected[0].frame (which is 1) is not equal to actual[0].frame (which is 3). And so on, expected[1].frame is not equal to actual[1].frame...
Now, you may wonder why do you even get such values for actual and expected. This is explained much more in detail on the official docs. When you create cold observable with this marble diagram -x-y-z| and delay it with 2 units, it becomes ---x-y-z| which is then transformed to something comparable - an actual array. The first three - signs indicate three empty, non-emitting frames. They are at the positions 0, 1 and 2. They do not have representations in any of the two arrays.
Then comes the first real value x. It is represented as the first object in actual array (actual[0]). x is at position 3 thus the frame property has the same value. notification property has some meta data like the value of the emitted item. You can conclude values for y and z the same way.
Side note: when using run() method, frames become values of 1, 2, 3, etc. instead of 10, 20, 30 when not using run(), for legacy reasons.

find object in nested array with lodash

I have json data similar to this:
{
"Sections": [
{
"Categories": [
{
"Name": "Book",
"Id": 1,
"Options": [
{
"Name": "AAAA",
"OptionId": 111
},
"Selected": 0
},
{
"Name": "Car",
"Id": 2,
"Options": [
{
"Name": "BBB",
"OptionId": 222
},
"Selected": 0
},
],
"SectionName": "Main"
},
... more sections like the one above
]
}
Given this data, I want to find a category inside a section based on its (Category) Id, and set its selected option, I tried this, but couldn't get it to work....Note Category Id will be unique in the whole data set.
_.find(model.Sections, { Categories: [ { Id: catId } ]});
According to your data model, it looks like you're trying to find an element that is inside a matrix: Sections can have multiple Categories and a Category can have multiple types (car, book...).
I'm afraid there isn't a function in lodash that allows a deep find, you'll have to implement it the 'traditional' way (a couple of fors).
I provide this solution that is a bit more 'functional flavoured' than the traditional nested fors. It also takes advantage of the fact that when you explicitly return false inside a forEach, the loop finishes. Thus, once an element with the provided id is found, the loop is ended and the element returned (if it's not found, undefined is returned instead).
Hope it helps.
const findCategoryById = (sections, id) => {
var category;
_.forEach(sections, (section) => {
category = _.find(section.Categories, ['Id', id]);
return _.isUndefined(category);
});
return category;
};
const ex = {
"Sections": [{
"Categories": [{
"Name": "Book",
"Id": 1,
"Options": [{
"Name": "AAAA",
"OptionId": 111
}],
"Selected": 0
},
{
"Name": "Car",
"Id": 2,
"Options": [{
"Name": "BBB",
"OptionId": 222
}],
"Selected": 0
}
],
"SectionName": "Main"
}]
};
console.log(findCategoryById(ex.Sections, 2));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>

how to create dynamic data source for sectionlist in RN?

I am currently using sectionlist to create a list with a header, but I am struggling with dataSource.
Object {
"A": Array [
Object {
"fid": "2",
"name": "Manage",
"posts": "1",
"threads": "1",
"todayposts": "1",
},
],
"B": Array [
Object {
"fid": "36",
"name": "Anime",
"posts": "1",
"threads": "1",
"todayposts": "1",
},
Object {
"fid": "37",
"name": "Novel",
"posts": "2",
"threads": "2",
"todayposts": "2",
},
],
}
these are the data fetched from the server, and they are objects, so right now I have to transfer these data to a struct which can be accepted by section list, otherwise, I got an error message like,
TypeError:props.section.reduce is not a function.(In 'props.sections.reduce(function(v,section){
stickyHeaderIndices.push(v+offset);
return v + section.data.length +2;
},0)','props.sections.reduce' is undefined)
So what I tried is, using for loop to create a new array, but seems I failed.
Update:
<SectionList
sections={dataSource}
/>
So clearly I need a dataSource is an array with keys inside, not the one I have right now. So I need to find a way to trans the current object to array.
Can anyone help me with this?
So I figured out how to change it from object to an array with keys.
let SECTIONS = []
for (const key in dataSource) {
if (dataSource.hasOwnProperty(key)) {
SECTIONS.push({
data: dataSource[key],
key: key,
})
}
}
It solved my problem.

Is it possible to extend graphql response other than just data for pagination?

In GraphQL response normally looks like followings.
{
"data": [{
"id": 1,
"username": "Jon Snow",
"email": "crow#northofthew.all",
"age": 20
}, {
"id": 2,
"username": "Tyrion Lannister",
"email": "drunk#i.mp",
"age": 34
}, {
"id": 3,
"username": "Sansa Stark",
"email": "redhead#why.me",
"age": 17
}]
}
Is it possible to add meta data to your response such as pagination like this.
{
"pagination": {
"total": 14,
"count": 230,
},
"data": [{
"id": 1,
"username": "Jon Snow",
"email": "crow#northofthew.all",
"age": 20
}, {
"id": 2,
"username": "Tyrion Lannister",
"email": "drunk#i.mp",
"age": 34
}]
}
I'm using express-graphql and currently put those pagination to custom response header, which is fine but it can be better. Since GraphQL response is already wrapped with "data", it is not very strange to add more "data" to its response.
Reenforcing what #CommonsWare already stated, according to the specification that would a be an invalid GraphQL response. Regarding pagination, Relay has its own pagination approach called connections, but indeed, several other approaches are possible and even more suitable in some situtations (connections aren't a silver bullet).
I want to augment what was already said by adding that the hierarchical nature of GraphQL incites related data to be at the same level. An example is worth a thousands words, so here it goes:
query Q {
pagination_info { # what is this info related to? completely unclear
total
count
}
user {
friends {
id
}
}
}
Instead...
query Q {
user {
friends {
pagination_info { # fairly obvious that this is related to friends
total
count
}
friend {
id
}
}
}
}