ramda.js how to do the groupby, count, sort - javascript

I hava a data collection like:
[{"id":1,"score":4},{"id":2,"score":3},{"id":1,"score":4},{"id":2,"score":3},{"id":3,"score":4},{"id":1,"score":3}]
I want the ouput like :
[{"id":1,"count":3},{"id":2,"count":2},{"id":3,"count":1}]
Is there any solution to use Ramda.js to do this?
I tried to use countBy(prop("id")), but i can not figure out the way of doing sort by count number.

Create a function with R.pipe, that uses R.countBy to get an object of { [id]: count }, then converts the data to pairs, and generate an array of objects with R.map, and R.applySpec. Then sort it with R.sortBy.
const { pipe, countBy, prop, toPairs, map, applySpec, head, last, sortBy, descend } = R
const fn = pipe(
countBy(prop('id')),
toPairs,
map(applySpec({
id: pipe(head, Number), // or just id: head if the id doesn't have to be a number
count: last,
})),
sortBy(descend(prop('count'))), // or ascend
)
const arr = [{"id":1,"score":4},{"id":2,"score":3},{"id":1,"score":4},{"id":2,"score":3},{"id":3,"score":4},{"id":1,"score":3}]
const result = fn(arr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>

Obviously countBy will be part of the solution, if you're using Ramda. I would then choose to pipe that to toPairs and zipObj to get your final results:
const collect = pipe (
countBy (prop ('id')),
toPairs,
map (zipObj (['id', 'count']))
)
const data = [{id: 1, score: 4}, {id: 2, score: 3}, {id: 1, score: 4}, {id: 2, score: 3}, {id: 3, score: 4},{id: 1, score: 3}]
console .log (collect (data))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {pipe, countBy, prop, toPairs, map, zipObj} = R </script>
zipObj takes an array of property names and an array of values and zips them together into a single object.

With vanillaJS you can simply use reduce and Map
const data = [{"id":1,"score":4},{"id":2,"score":3},{"id":1,"score":4},{"id":2,"score":3},{"id":3,"score":4},{"id":1,"score":3}]
const final = [...data.reduce((op,{id,score})=>{
if(op.has(id)){
op.set(id, op.get(id)+1)
} else {
op.set(id,1)
}
return op;
}, new Map()).entries()].map(([id,count])=>({id,count}))
console.log(final)

Related

Javascript or Ramda Transform JSON By Attributes

I am using Ramda library in my project.
is it possible to transform following JSON array
from
[
{
"id": 1,
"name": "test",
},
{
"id": 2,
"name": "test2"
}
];
To
[
{
"id": 1,
"id": 2,
},
{
"name": "test",
"name": "test2"
}
];
pleas help
As OriDrori pointed out, your requested output is not valid JS. I'm going to make a slightly different guess, though, as to a useful variant of it that is valid, namely that we want an output like this:
{
id: [1, 2],
name: ['test1', 'test2']
}
Here's one simple way to achieve that in vanilla JS:
const extract = (data) => {
const keys = [... new Set (data .flatMap (Object .keys))]
return Object .fromEntries (
keys.map (k => [k, data .map (o => o [k])])
)
}
const data = [{id: 1, name: "test"}, {id: 2, name: "test2"}]
console .log (
extract (data)
)
We can definitely clean that up with Ramda functions. Another version might look like this:
const extract = (data) => fromPairs (
map (k => [k, map (o => o [k], data)]) (uniq (chain (keys) (data)))
)
const data = [{id: 1, name: "test"}, {id: 2, name: "test2"}]
console .log (extract (data))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js"></script>
<script> const {fromPairs, map, uniq, chain, keys} = R </script>
While we could go completely point-free, I find this version much less readable:
const extract = compose (
fromPairs,
lift (map) (
unary (compose (ap (pair), flip (pluck))),
compose (uniq, chain (keys))
)
)
const data = [
{id: 1, name: "test"},
{id: 2, name: "test2"}
]
console .log (extract (data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js"></script>
<script>
const {compose, fromPairs, lift, map, unary, ap, pair, flip, pluck, uniq, chain, keys} = R
</script>
Objects can't have multiple properties with the same key, so
{ "id": 1, "id": 2 } and { "name": "test", "name": "test2" } are invalid. I assume that you need an array of ids and an array of names:
[[1, 2, 3], ['test', 'test2', 'test3']]
If all objects are have the same order of keys - ie no { id: 1, name: 'test'} and { name: 'test2', id: 1 }, and you need all the values in an object, you can map the objects to their values, and then transpose:
const { pipe, map, values, transpose } = R;
const fn = pipe(
map(values),
transpose,
);
const arr = [{"id":1,"name":"test"},{"id":2,"name":"test2"},{"id":3,"name":"test3"}];
const result = fn(arr);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
If some objects have a different keys insertion order, you want to change the order of the resulting arrays, or if you need some of the keys, you can get the values with R.props, and then transpose:
const { pipe, map, props, transpose } = R;
const fn = pipe(
map(props(['name', 'id'])), // example - name would be the 1st sub-array
transpose,
);
const arr = [{"id":1,"name":"test"},{"id":2,"name":"test2"},{"id":3,"name":"test3"}];
const result = fn(arr);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
If you want the structure suggested by Scott Sauyet's:
{
id: [1, 2],
name: ['test1', 'test2']
}
I would map and flatten the objects to an array of pairs with R.chain and R.toPairs, group them by the 1st item in each pair (the original key), and then map each groups item to the last item in each pair (the original value).
const { pipe, chain, toPairs, groupBy, head, map, last } = R
const fn = pipe(
chain(toPairs),
groupBy(head),
map(map(last)), // map(pipe(map(last), uniq)) if you want only unique items
)
const arr = [{"id":1,"name":"test"},{"id":2,"name":"test2"},{"id":3,"name":"test3"}];
console.log(fn(arr))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js"></script>

Suggested way to Map an Object array to Array of Ids

If given an array of ids [1,2,3,4,5]
And an object array:
[{animal:tiger, id:1}, {animal:"fish", id:2}]
What would be the suggested way to return 'tiger, fish'. Would that be through using .map or would a for loop be better for constructing the sentence?
What you need is just go through the list of ids and find corresponding animal in the animals list.
Note, that in case animals list is not expected to store all the animals and some of them are missing, you will need to add additional filter step to be sure that no undefined values appear on the last map step.
const ids = [1,5,2,4,3,6]; // added 6 which is missing in animals
const animals = [
{name:'Tiger',id:1},
{name:'Horse',id:2},
{name:'Mouse',id:3},
{name:'Elephant',id:4},
{name:'Cat',id:5}
];
const result = ids
.map(id => animals.find(a => a.id === id))
.filter(Boolean) // this will exclude undefined
.map(a => a.name)
.join(',');
console.log(result);
var ids = [1,2,3,4,5];
var objects = [{ animal:"tiger", id: 1 }, { animal: "fish", id: 2 }];
objects.map(function(o) { if(ids.includes(o.id)) return o.animal }).join();
I'm guessing you only want the animal names who's id appears in your array. If so, you could filter the array of objects first, followed by a map and a join.
let idarr = [1, 2, 3, 4];
let objarr = [{
animal: "tiger",
id: 1
}, {
animal: "fish",
id: 2
}];
console.log(objarr.filter(x => idarr.includes(x.id)).map(x => x.animal).join(', '))
I suggest two options, depending on your data & use cases.
1. map + find if the animal kingdoms are not too many to loop through.
const animals = [
{animal:tiger, id:1},
{animal:"fish", id:2}
]
const ids = [1,2,3,4,5];
const names = ids.map(id =>
animals.find(animal => animal.id === id));
2. convert animals array to object first, for easier frequent access later. One upfront loop, then easier to access by id later.
const animals = [
{animal: "tiger", id:1},
{animal: "fish", id:2}
]
/*
Convert
[{animal:tiger, id:1}, {animal:"fish", id:2}]
to
{
1: { animal: "tiger", id: 1 },
2: { animal: "fish", id: 2 },
}
*/
const animalsObj = animals.reduce((acc, animal) => {
return {
...acc,
[animal.id]: animal,
}
}, {});
const ids = [1,2,3,4,5];
const names = ids.map(id => animalsObj[id].animal)

react javascript - json how to merge multiple array elements into one string

I have a JSON response as below:
[{
"id": 1,
"food": {
"fruits": ["Banana", "Orange", "Apple", "Mango"],
"veggies": ["greens", "peppers", "carrot", "potatoes"],
}
},
{
"id": 2,
"food": {
"fruits": ["grapes", "berries", "peach", "pears"],
"veggies": ["cabbage", "spinach"],
"dairy": ["nutmilk", "goatmilk"]
}
}
]
Now i want to merge the Arrays each "id" (1,2 in example) into string ( ; delimited) like below:
id_1 = Banana;Orange;Apple;Mango;greens;peppers;carrot;potatoes
// observer "id_2" has additional array - "dairy"
id_2 = grapes;berries;peach;pears;cabbage;spinach;nutmilk;goatmilk
The key's are dynamic so for some records there are 2 arrays and for some records it can be 3 or 4 and may be 1.
I tried using react/Java Script Array.concat(), but i am not sure how to do it dynamically. Please help me. Thank you.
This is doable easily using Object.values().flat().join(';') demonstrated below:
let arr=[{"id":1,"food":{"fruits":["Banana","Orange","Apple","Mango"],"veggies":["greens","peppers","carrot","potatoes"],}},{"id":2,"food":{"fruits":["grapes","berries","peach","pears"],"veggies":["cabbage","spinach"],"dairy":["nutmilk","goatmilk"]}}];
const result = arr.map(({id,food}) => ({id, food: Object.values(food).flat().join(';')}));
console.log(result);
You may easily restructure the output by simply changing it to e.g. ["id_"+id]: Object.values(...)
First flatten using map, flat and join. Then convert the resulting array of objects to a single object using assign.
var db = [{"id": 1,"food": {"fruits": ["Banana", "Orange", "Apple", "Mango"], "veggies": ["greens","peppers","carrot","potatoes"], }},{"id" : 2,"food": {"fruits": ["grapes", "berries", "peach", "pears" ], "veggies": ["cabbage","spinach"], "dairy": ["nutmilk","goatmilk"]}}];
var flat = db.map(
({id, food}) => ({[`id_${id}`]: Object.values(food).flat().join(';')})
);
var result = Object.assign(...flat);
console.log(result);
This is really two problems: looping through an array of objects to combine them into one object, and looping through an object to concat all of its array.
Tackling the second one first, something like this would work:
const concatArraysInObject = obj =>
Object.values(obj).reduce((result, arr) => result.concat(arr), []);
const input = { a: [1,2,3], b: [4,5,6], c: [7,8,9] };
const output = concatArraysInObject(input);
console.log(output);
Object.values() will give you an array of all arrays in an object.
The reduce() function takes a two parameters: a function and initial value.
The function should also take (at least) 2 parameters: the result of the last call (or initial value) and the current value in the array.
It'll call the function once for each element in the array.
Now, with that solved, we can tackle the first problem.
For this, we can also use reduce() as well, and we'll construct our combined object on each call.
const concatArraysInObject = (obj) =>
Object.values(obj).reduce((result, arr) => result.concat(arr), []);
const mergeObjectsInArray = (arr, dataKey) =>
arr.reduce((result, obj) => ({ ...result, [obj.id]: concatArraysInObject(obj[dataKey]) }), {});
const input = [
{ id: 'A', data: { a: [1,2,3], b: [4,5,6] } },
{ id: 'B', data: { c: [7,8,9], d: [10,11,12] } }
];
const output = mergeObjectsInArray(input, 'data');
console.log(output);
An important note of warning: object key order is NOT guaranteed in JavaScript. While 99% of the time they will be in the order you expect, this is not a guarantee, so if you depend on the order of the keys for the order of the array (if order matters), you'll want to change your input structure. If order doesn't matter, it is probably fine how it is.
Try this using basic for loop. Inside you will compute key dynamically and value being flattened array of Object.values of the iterating object.
var input = [{
id: 1,
food: {
fruits: ["Banana", "Orange", "Apple", "Mango"],
veggies: ["greens", "peppers", "carrot", "potatoes"]
}
},
{
id: 2,
food: {
fruits: ["grapes", "berries", "peach", "pears"],
veggies: ["cabbage", "spinach"],
dairy: ["nutmilk", "goatmilk"]
}
}
];
var temp = [];
for (var i = 0; i < input.length; i++) {
temp.push({
[`id_${input[i].id}`]: Object.values(input[i].food)
.flat(1)
.join(";")
});
}
console.log(temp); // this gives you an array
console.log(Object.assign(...temp));// in case you require one single object

Destructuring data from a multidimensional array

I have an array of nested objects, like so:
const objArr = [{obj, obj, objIWant}, {obj, obj, objIWant}, {obj, obj, objIWant}]
Is there a way to get to objIWant without having to loop twice like:
ObjArr.map((obj)=> obj.map(({ objIWant }) => myFunc(objIWant)))
I was hoping I could perhaps leverage destructuring but trying something like [{ objIWant }] = objArr only returns the first objIWant. Am I missing something about destructuring syntax that would allow for this? Many thanks!
No - the only way to do it is with nested map calls.
ObjArr.map(obj => obj.map(({ objIWant }) => myFunc(objIWant));
If you are able to do so, you could change myFunc:
myFunc({ objIWant }) {...}
And then change your code to do this:
ObjArr.map(obj => obj.map(myFunc));
But there's no way using destructuring to do what you're asking.
Destructuring will not make it look cleaner. This is the only way to destructure the properties you want. Using the map function as you are now is a cleaner way of doing it.
const objArr = [{obj:1, obj2:2, objIWant:3}, {obj:4, obj2:5, objIWant:6}, {obj:7, obj2:8, objIWant:9}];
const [{objIWant}, {objIWant:test2}, {objIWant:test3}] = objArr;
window.console.log(objIWant);
window.console.log(test2);
window.console.log(test3);
Not sure if your structure is expected to return an array of objects - or an array of values from each object. But the solution would be the same - use .map() on the original array to return the values (or objects) that you want.
const objArr = [
{id: 1, name: 'first item', value: 1},
{id: 2, name: 'second item', value: 2},
{id: 3, name: 'third item', value: 3}
];
const objIWant = 'value';
const result = objArr.map(item => item[objIWant]);
console.log(result);
// expected output: Array ["1", "2", "3"]
if its a nested object then same deal - use .map() on the original array to construct a new array of the desired objects.
const objArr = [
{"obj": {id: 1}, "obj": { id:1 } , "objIWant": { id:1 }},
{"obj": {id: 1}, "obj": { id:1 } , "objIWant": { id:2 }},
{"obj": {id: 1}, "obj": { id:1 } , "objIWant": { id:3 }}
];
const objIWant = 'objIWant';
const result = objArr.map(item => item[objIWant]);
console.log(result);
// expected output: Array [{"id": 1},{"id": 2},{"id": 3}]

Creating and accessing array of objects in Javascript

I'm having trouble figuring out how to access data in an array of objects. I've spent hours trying to find some example of this, but all I find is you must reference arrays by an index number, which doesn't seem efficient.
For example, I have a table of animals and the number of legs on that animal. How do I access the value (number of legs) for that animal based on the name of the animal. If I pass "human" to a function I want to be able to return "2".
Is this concept called something I'm not yet familiar with yet? Is it just not possible to use a "key" to access data in an array? Do I really have to use a loop to search through the entire array to fine the right entry if I don't know the index number?
What is the simplest way to do this?
let animalsLegs = [{animal: "human", legs: 2},
{animal: "horse", legs: 4},
{animal: "fish", legs: 0}]
function findLegs(animalToFind) {
return animalLegs[animalToFind];
}
console.log(findLegs("human"));
I would expect an output of 2.
Use an object instead of an array:
const animalLegsByAnimalName = {
human: 2,
horse: 4,
fish: 0
};
function findLegs(animalToFind) {
return animalLegsByAnimalName[animalToFind];
}
console.log(findLegs("human"));
If you want to keep using the array, but also use an object for quick, easy lookup, just reduce the initial array into the above object first:
const animalsLegs = [{animal: "human", legs: 2},
{animal: "horse", legs: 4},
{animal: "fish", legs: 0}];
const animalLegsByAnimalName = animalsLegs.reduce((a, { animal, legs }) => {
a[animal] = legs;
return a;
}, {});
function findLegs(animalToFind) {
return animalLegsByAnimalName[animalToFind];
}
console.log(findLegs("human"));
If the object in the array is more complicated than that, and you (for example) want access to additional properties, you can have the object's values be the object in the array, instead of just the value of the leg property:
const animalsLegs = [{animal: "human", legs: 2},
{animal: "horse", legs: 4},
{animal: "fish", legs: 0}];
const animalLegsByAnimalName = animalsLegs.reduce((a, animalObj) => {
a[animalObj.animal] = animalObj;
return a;
}, {});
function findLegs(animalToFind) {
const foundAnimal = animalLegsByAnimalName[animalToFind];
if (!foundAnimal) {
return 'No animal found with that name!';
}
return foundAnimal.legs;
}
console.log(findLegs("human"));
If you want to keep the Array data structure:
function findLegs(animalToFind) {
const animal = animalsLegs.find(animal => animal.name === animalToFind);
return animal.legs;
}
PS: An array data structure is more inefficient than an object, it will take O(n) to find an item, while an object is like a hash table, you can find values in O(1). You can read more about Big O notation here.
I think an array is best for this kind of data, you simply have to write a small function to retrieve what you want.
const animals = [
{ animal: "human", legs: 2 },
{ animal: "horse", legs: 4 },
{ animal: "fish", legs: 0 }
]
function getAnimalByName(name) {
return animals.reduce((a, b) => b.animal !== name ? a : b, null)
}
// Human?!
console.log(getAnimalByName('fish'))

Categories

Resources