How to count items in List with ImmutableJS? - javascript

I have a List which looks like this:
["green", "blue","green","green", "blue", "black"]
How can I use the ImmutableJS operators to count these items and get a Map which could look like this:
{ green: {name: green, count: 3}, blue:{name: blue, count: 2}, black:{name: black, count: 1}}
I found the following function here to do just that in plain JS, so how could I achieve this with ImmutableJS?
export const countOccurances = obj => {
return obj
.reduce((counter, x) => {
if (counter.hasOwnProperty(x)) {
counter[x].count = counter[x].count + 1
} else {
counter[x] = {name: x, count: 1}
}
return counter;
}, {})
}

Try groupBy followed by map. Something like this:
list.groupBy(elem => elem).map(arr => {
return {"name" : arr.get(0), "count": arr.size }
})
list.groupBy(elem => elem) will return a KeyedSeq with the color String as it's key and an Iterable of Strings as value. It would look like this in pure JS.
{ green: ["green","green","green"], blue:["blue","blue"], black: ["black"]}
We can then map these to return the size of the iterable as well as the name and we should get what you wanted:
{ green: {name: "green", count: 3}, blue:{name: "blue", count: 2}, black:{name: "black", count: 1}}
Of course if you wanted to get exactly that, you would need to use toJS() at the end.

You could also use a mixed solution:
list.toJS().length

Related

JavaScript Updating arrays with data from separate array

I have two arrays both consisting of 30 items.
Array 1: [ {cat, red} , {dog, blue} , ... ]
Array 2: [ {1}, {2}, ... ]
My goal is to have Array 3 like so:
[ {Cat, red, 1} , {dog, blue, 2} ... ]
I am using JavaScript and cheerio to grab the data.
Your objects look strange, {1} isn't a valid object. Maybe you meant something like {id: 1}?
Anyway, looks like you need something like this:
const arr3 = arr1.map((el, index) => {
return {
...el,
...arr2[index],
}
})
It's hard to tell what the data actually looks like, since [ {cat, red} , {dog, blue} , ... ] isn't valid JSON or anything, but the general idea would be to get the corresponding element from the other array by index, either using .map((el, index) => { // ... }) or just looping over it, so something like:
const arr1 = [{title: 'movieA', rank: '1' }, {title: 'MovieB', rank: '2'}]
const arr2 = [{score: 10}, {score: 20}]
console.log(arr1.map((el, i) => {
return { ...el, ...arr2[i] }
}));

Return Object from map() function not Array

Sorry if the title is misleading I'm not sure how to accurately describe what I am looking for.
I have an array result which I want to turn into multiple objects where each objects property is the field Name Test I need to keep my result.map method as I use it to merge result with data
result = [{
"Name Test": "Yellow",
Count: 124,
},
{
"Name Test": "Black",
Count: 124,
},
{
"Name Test": "Blue",
Count: 124,
}
];
data = [{
"Name Test": "Yellow",
pop: 1
},
{
"Name Test": "Black",
pop: 1
},
{
"Name Test": "Blue",
pop: 1
}
];
result = result.map((obj1, index) => {
const obj2 = data[index];
return {
[obj1["Name Test"].toUpperCase()]: {
Count: obj1.Count,
pop: obj2.pop,
}
};
});
console.log(JSON.stringify(result))
This code returns an array of objects which is not what I want as I need to be able to use result["YELLOW"] later in my code. I therefore need the result to be in this format, with no arrays.
{
"YELLOW":{
"Count":124,
"pop":1
},
"BLACK":{
"Count":124,
"pop":1
},
"BLUE":{
"Count":124,
"pop":1
}
}
I hope this makes sense and I feel I am very close to what I want and just am missing something small, but every way I have tried to make this work turns into a syntax error.
map() will always return an array.
You should use reduce() and set accumulator to empty object {}
Use the destructuring and spread syntax to isolate Name Test and other properties.
Set the property of accumulator whose key is "Name Test" property of each object and its value is rest of the object.
const arr = [{ "Name Test": "Yellow", Count: 124, pop: 1 }, { "Name Test": "Black", Count: 124, pop: 1 }, { "Name Test": "Blue", Count: 124, pop: 1 } ];
const res = arr.reduce((ac,{["Name Test"]:x,...rest}) => (ac[x] = rest,ac),{})
console.log(res)
map will create a new array. So you can use reduce and pass an empty object in the accumulator
let result = [{
"Name Test": "Yellow",
Count: 124,
pop: 1
},
{
"Name Test": "Black",
Count: 124,
pop: 1
},
{
"Name Test": "Blue",
Count: 124,
pop: 1
}
];
let newResult = result.reduce(function(acc, curr) {
// acc is accumulator which is the required object.
// this will create a nee key in accumulator and will set its value
acc[curr['Name Test'].toUpperCase()] = {
Count: curr.Count,
pop: curr.pop
}
return acc;
}, {}) // {} is accumulator object. This will hold the required keys and values
console.log(newResult)
It's not available in MS browsers yet, (I think), but fromEntries() is pretty nice for this. You can pass it an iterable or key.value pairs, such as the results from map().
let result = [{"Name Test": "Yellow",Count: 124,pop: 1},{"Name Test": "Black",Count: 124,pop: 1},{"Name Test": "Blue",Count: 124,pop: 1}];
let data = [{"Name Test": "Yellow",pop: 1},{"Name Test": "Black",pop: 1},{"Name Test": "Blue",pop: 1}];
let o = Object.fromEntries(result.map(({'Name Test':n, ...o}) => ([n, o])))
let d = Object.fromEntries(result.map(({'Name Test':n, ...o}) => ([n, o])))
console.log(Object.assign(o, d))

GroupBy and reduce an array of object in a pointfree style

I recently started using Ramda and trying to find a pointfree way to write a method to reduce an array of objects.
Here is the array of object :
const someObj = [
{
name: 'A',
city: 1,
other: {
playtime: 30
}
},
{
name: 'B',
city: 2,
other: {
playtime: 20
}
},
{
name: 'c',
city: 1,
other: {
playtime: 20
}
}
];
What I am trying is to reduce the object using ramda in poinfree style like
{
'1': {
count: 2,
avg_play_time: 20 + 30 / count
},
'2': {
count: 1,
avg_play_time: 20 / count
}
}
I can do it using an array reduce method but not sure how can I write the same in ramda pointfree style. Any suggestion will be appreciated.
One solution would be to do something like this:
// An optic to extract the nested playtime value
// Coupled with a `lift` operation which allows it to be applied over a collection
// Effectively A -> B => A[] -> B[]
const playtimes = R.lift(R.path(['other', 'playtime']))
R.pipe(
// Group the provided array by the city value
R.groupBy(R.prop('city')),
// Return a body specification which computes each property based on the
// provided function value.
R.map(R.applySpec({
count: R.length,
average: R.pipe(playtimes, R.mean)
}))
)(someObj)
Ramda also has another function called R.reduceBy which provides something inbetween reduce and groupBy, allowing you to fold up values with matching keys together.
So you can create a data type like the following that tallies up the values to averaged.
const Avg = (count, val) => ({ count, val })
Avg.of = val => Avg(1, val)
Avg.concat = (a, b) => Avg(a.count + b.count, a.val + b.val)
Avg.getAverage = ({ count, val }) => val / count
Avg.empty = Avg(0, 0)
Then combine them together using R.reduceBy.
const avgCities = R.reduceBy(
(avg, a) => Avg.concat(avg, Avg.of(a.other.playtime)),
Avg.empty,
x => x.city
)
Then pull the average values out of the Avg into the shape of the final objects.
const buildAvg = R.applySpec({
count: x => x.count,
avg_play_time: Avg.getAverage
})
And finally pipe the two together, mapping buildAvg over the values in the object.
const fn = R.pipe(avgCities, R.map(buildAvg))
fn(someObj)
I would write it like this, hope it helps!
const stats = R.pipe(
R.groupBy(R.prop('city')),
R.map(
R.applySpec({
count: R.length,
avg_play_time: R.pipe(
R.map(R.path(['other', 'playtime'])),
R.mean,
),
}),
),
);
const data = [
{ name: 'A', city: 1, other: { playtime: 30 } },
{ name: 'B', city: 2, other: { playtime: 20 } },
{ name: 'c', city: 1, other: { playtime: 20 } },
];
console.log('result', stats(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
Here's another suggestion using reduceBy with mapping an applySpec function on each property of the resulting object:
The idea is to transform someObj into this object using getPlaytimeByCity:
{ 1: [30, 20],
2: [20]}
Then you can map the stats function on each property of that object:
stats({ 1: [30, 20], 2: [20]});
// { 1: {count: 2, avg_play_time: 25},
// 2: {count: 1, avg_play_time: 20}}
const someObj = [
{ name: 'A',
city: 1,
other: { playtime: 30 }},
{ name: 'B',
city: 2,
other: { playtime: 20 }},
{ name: 'c',
city: 1,
other: { playtime: 20 }}
];
const city = prop('city');
const playtime = path(['other', 'playtime']);
const stats = applySpec({count: length, avg_play_time: mean});
const collectPlaytime = useWith(flip(append), [identity, playtime]);
const getPlaytimeByCity = reduceBy(collectPlaytime, [], city);
console.log(
map(stats, getPlaytimeByCity(someObj))
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {prop, path, useWith, flip, append, identity, applySpec, length, mean, reduceBy, map} = R;</script>
I like all the other answers given so far. So naturally I want to add my own. ;-)
Here is a version that uses reduceBy to keep a running track of the count and mean. This would not work if you were looking for the median value or some other statistic, but given a count, an average, and a new value, we can calculate the new count and average directly. This allows us to iterate the data only once at the expense of doing some arithmetic on every iteration.
const transform = reduceBy(
({count, avg_play_time}, {other: {playtime}}) => ({
count: count + 1,
avg_play_time: (avg_play_time * count + playtime) / (count + 1)
}),
{count: 0, avg_play_time: 0},
prop('city')
)
const someObj = [{city: 1, name: "A", other: {playtime: 30}}, {city: 2, name: "B", other: {playtime: 20}}, {city: 1, name: "c", other: {playtime: 20}}]
console.log(transform(someObj))
<script src="https://bundle.run/ramda#0.26.1"></script>
<script>
const {reduceBy, prop} = ramda
</script>
This is not point-free. Although I'm a big fan of point-free style, I only use it when it's applicable. I think seeking it out for its own sake is a mistake.
Note that the answer from Scott Christopher could easily be modified to use this sort of calculation

JSON manipulation - javascript - adding new keys and shifting data

I'm learning to manipulate JSON data and I am stuck trying to figure out how to cajole the following JSON into what I want as shown below:
Any pointers to function/terms/concepts that I should learn for this sort of problem would be greatly appreciated! Thanks
JSON object
{
car: 1,
van: 5,
cat: 99999999999999999999999
}
Desired outcome:
items: [
{ "type": "car", "value": "1"},
{ "type": "van", "value": "5"},
{ "type": "cat", "value": "99999999999999999999999"}
]
You can use a combination of Object.entries and Array.prototype.map:
const obj = { car: 1, van: 5, cat: 99999999999999999999999 };
let list = Object.entries(obj) // [["car",1],["van",5],["cat",99999999999999999999999]]
.map(x => ({ type: x[0], value: x[1] }));
console.log(list);
Or, with some destructuring:
const obj = { car: 1, van: 5, cat: 99999999999999999999999 };
let list = Object.entries(obj)
.map(([type, value]) => ({ type, value }));
console.log(list);
The callback to map:
([type, value]) => ({ type, value })
Expects an array as parameter: [type, value]. The first value in that array is assigned to type, the second one to value.
Then we use a shorthand form to set these values in our returned object:
=> ({ type, value })
I'm a beginner. I tried to solve the problem and this is the best I can come up with, tested in Node.js 10.
const obj = {"car": 1, "van": 5, "cat": 999999}
const items = []
for (let key in obj) {
items.push({"type": key, "value": obj[key]})
}
console.log(items)
One thing I am slightly confused about is the difference between for..in vs for..of, I'm currently looking into it.
Object.keys will return:
['car', 'van', 'cat'];
On this array you can use Array's map function which creates a new array with the results of calling a provided function on every element in the calling array.
var a = {
car: 1,
van: 5,
cat: 99999999999999999999999
}
m = Object.keys(a).map((v)=>{
return {
type: v,
value: a[v]
}
})
console.log(m);
#GustavMahler hope you understand. To learn more about array functions you should look map, reduce and filter.
This one uses object.keys
let js = {car:1, van:5, cat:9999}
Object.keys(js).map( x => ({type: x, value: js[x] }) )
[ { type: 'car', value: 1 },
{ type: 'van', value: 5 },
{ type: 'cat', value: 9999 } ]

prevent duplication code with ramda

i have a list of same operation on same list using ramda like below :
size: { sum: R.sum(R.map(R.prop('size'), ordersRep)) },
price: { sum: R.sum(R.map(R.prop('price'), ordersRep)) },
profit: { sum: R.sum(R.map(R.prop('profit'), ordersRep)) },
discount: { sum: R.sum(R.map(R.prop('discount'), ordersRep)) },
which i want to define main function : R.sum(R.map(R.prop('somthing'), ordersRep)) other place and use it wheneve needed. but it's take two argument one list and one prop name. how can i handle it ?
let combined = (prop, coll) => R.sum(R.pluck(prop, coll))
For an arguably more functional version (point-free courtesy of Ross Mackay):
let combined = prop => R.compose(R.sum, R.pluck(prop))
let sumPrice = combined('price');
sumPrice([{price: 2}, {price: 3}]); // 5
Point-free:
let combined = R.curryN(2, R.compose(R.sum, R.pluck));

Categories