Perform different actions in top 3 values in Dictionary? - javascript

I have a sorted dictionary with certain number of entries:
dict = {B:3, A:2, C:2, D:1, E:0, F:0...}
After filtering the dictionary to get only the entries with top 3 largest values:
result = Object.fromEntries(Object
.entries(dict)
.sort(([, a], [, b]) => b - a)
.filter((s => ([, v]) => s.add(v).size <= 3)(new Set))
);
The current dictionary is
{"B": 3, "A": 2, "C": 2, "D": 1}
So I am trying to add 4 to the largest values,2 to the second largest values and 1 to the third largest values, what are the ways to do this?
The expected output: {"B": 7, "A": 4, "C": 4, "D": 2}
One of the ways I can think of is:
for (const key of Object.keys(result)) {
// if result[key] largest
//plus 4
// if result[key] second largest
//plus 2
// else
//plus 1
}
Thanks for reading..

You can do this using Map and flatMap as:
const dict = { B: 3, A: 2, C: 2, D: 1, E: 0, F: 0 };
const valuesToAdd = [4, 2, 1];
const result = Object.fromEntries([
...Object.entries(dict)
.sort(([, a], [, b]) => b - a)
.filter( ( (s) => ([, v]) => s.add(v).size <= 3 )(new Set()) )
.reduce((map, arr) => {
const [k, v] = arr;
map.has(v) ? map.get(v).push(arr) : map.set(v, [arr]);
return map;
}, new Map())
.values(),
].flatMap((arr, i) => arr.map(([k, v]) => [k, v + valuesToAdd[i]]))
);
console.log(result);

Related

Cumulative sum of specific keys with array output using reduce

Say I have the following array:
let arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]
I would like to compute the cumulative sum of each key, but I would also like the output to be an array of the same length with the cumulative values at each step. The final result should be:
[{a: 1, b: 2}, {a: 3, b: 6}, {a: 11, b: 5}]
My issue is that I am not able to obtain the array as desired. I only get the final object with this:
let result = arr.reduce((accumulator, element) => {
if(accumulator.length === 0) {
accumulator = element
} else {
for(let i in element){
accumulator[i] = accumulator[i] + element[i]
}
}
return accumulator
}, [])
console.log(result); // {a: 11, b: 5}
What you're after sounds like the scan() higher-order function (borrowing the idea from ramda.js), which allows you to return an accumulated result for each element within your array. The scan method is similar to how the .reduce() method behaves, except that it returns the accumulator for each element. You can build the scan() function yourself like so:
let arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}];
const scan = ([x, ...xs], fn) => xs.reduce((acc, elem) => {
return [...acc, fn(acc.at(-1), elem)];
}, xs.length ? [x] : []);
const res = scan(arr, (x, y) => ({a: x.a+y.a, b: x.b+y.b}));
console.log(res);
You might consider further improvements such as providing an initial value to the scan method (similar to how reduce accepts one). Also, if you need better browser support the .at() method currently has limited browser support, so you may instead consider creating your own at() function:
const at = (arr, idx) => idx >= 0 ? arr[idx] : arr[arr.length + idx];
You can easily achieve the result using reduce as
let arr = [
{ a: 1, b: 2 },
{ a: 2, b: 4 },
{ a: 8, b: -1 },
];
const result = arr.reduce((acc, curr, i) => {
if (i === 0) acc.push(curr);
else {
const last = acc[i - 1];
const newObj = {};
Object.keys(curr).forEach((k) => (newObj[k] = curr[k] + last[k]));
acc.push(newObj);
}
return acc;
}, []);
console.log(result);
Something like this:
const arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]
const result = arr.reduce((accumulator, element, index) => {
if(accumulator.length === 0) {
accumulator.push(element)
} else {
const sum = {};
for(let i in element) {
sum[i] = element[i] + (accumulator[index - 1][i] || 0)
}
accumulator.push(sum)
}
return accumulator
}, [])
console.log(result);
Another option is keep sum result using a Map, it helps if keys in elements of the array are not always same.
const arr = [{a: 1, b: 2}, {a: 2}, {a: 8, b: -1}];
const map = new Map();
const result = arr.map((element) => {
const sum = {};
for (let i in element) {
sum[i]= element[i] + (map.get(i) || 0);
map.set(i, sum[i]);
}
return sum;
});
console.log(result);
Here is a bit more concise reduce, probably not as readable as a consequence...
array.reduce((y,x,i) => ( i===0 ? y : [...y, {a: x.a + y[i-1].a, b: x.b + y[i-1].b}]),[array[0]])
let array = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]
let culm = array.reduce((y,x,i) => ( i===0 ? y : [...y, {a: x.a + y[i-1].a, b: x.b + y[i-1].b}]),[array[0]])
console.log(culm)
Given:
const xs =
[ {a: 1, b: 2}
, {a: 2, b: 4}
, {a: 8, b: -1}];
Define a function sum such as:
const sum = ([head, ...tail]) =>
tail.reduce((x, y) =>
({a: (x.a+y.a), b: (x.b+y.b)}), head);
sum(xs);
//=> {a: 11, b: 5}
Then apply that function in a map on larger slices of xs:
xs.map((_, i, arr) => sum(arr.slice(0, i+1)));
//=> [ {a: 1, b: 2}
//=> , {a: 3, b: 6}
//=> , {a: 11, b: 5}]

How to make array of object with limmited values

Sorry, in advance if the title is unclear, but it's hard to describe it in a words.
What I have:
const obj = {
a: 5,
b: 3,
c: 0,
d: 9
}
What I want to have:
const arr = [[a, 5] ,[b, 3]]
Basically, I try to write a function that return me array of entries, but it has too meet requirements:
don't want objects when values is equal to 0
sum of values must be less than 10
First point is easy for me and I can do it by
Object.entries(obj).filter(([k, v])=> v !== 0)
but I can't handle with the second one.
May I use reduce here?
You can use a closure and an IIFE to store the sum
Object.entries(obj).filter((() => {
let sum = 0;
return ([k, v]) => { sum += v; return v !== 0 && sum < 10; };
})());
Examples:
function convert(obj) {
return Object.entries(obj).filter((() => {
let sum = 0;
return ([k, v]) => { sum += v; return v !== 0 && sum < 10; };
})());
}
const obj = { a: 5, b: 3, c: 0, d: 9 };
const arr = convert(obj);
console.log(arr);
const obj2 = { a: 0, b: 0, c: 8, d: 0, e: 1, f: 5 };
const arr2 = convert(obj2);
console.log(arr2);
const obj3 = { a: 12 };
const arr3 = convert(obj3);
console.log(arr3);
#jabaa's answer is great and you should accept it.
Just to confirm your intuition, you could have used reduce, but it would get rather complicated:
const obj = {
a: 5,
b: 3,
c: 0,
d: 9
}
const result = Object.entries(obj).reduce(
(o, newPair) => {
o.sum += newPair[1];
newPair[1] !== 0 && o.sum < 10 && o.pairs.push(newPair);
return o;
},
{
sum: 0,
pairs: []
}
).pairs;
console.log(result)

Sum array of objects with dynamic keys - Javascript

I've got the following array
arr = [
{ 'Atencion Personalizada': 2, 'Caja': 3 },
{ 'Atencion Personalizada': 1 },
{ 'Tarifa Social': 3 }
]
Expected output: 9
And I would like to sum the properties the shortest way possible. The thing is that the object keys are always variable so i can't go for:
arr.reduce((acc,item) => acc+item.keyName)
Found this post but can't adapt the code to my solution either:
var data = [{ A: 4, B: 2 }, { A: 2, B: 1 }, { A: 3, B: 1 }, { A: 2, B: 1, C: 1 }],
result = data.reduce((r, o) => (Object.entries(o).forEach(([k, v]) => r[k] = (r[k] || 0) + v), r), {});
Thank you in advance
Here's my solution. Map through the array and flatten the properties values, then reduce them.
const arr = [
{ 'Atencion Personalizada': 2, 'Caja': 3 },
{ 'Atencion Personalizada': 1 },
{ 'Tarifa Social': 3 }
]
console.log(arr.flatMap(e => Object.values(e)).reduce((a, b) => a + b));
Use reduce twice, once on the outer array and once on the values of each object inside it.
const arr = [
{ 'Atencion Personalizada': 2, 'Caja': 3 },
{ 'Atencion Personalizada': 1 },
{ 'Tarifa Social': 3 }
];
const total = arr.reduce((gt, item) =>
gt + Object.values(item).reduce((t, sub) => t + sub, 0)
, 0);
console.log(total);
Maybe something like this?
let acc = {};
for (let o of arr) for (let key in o) {
acc[key] ??= 0;
acc[key] += o[key];
}
Not a one-liner though
You can do something like this:
total = arr.reduce((acc,item) => {
const values = Object.values(item)
values.forEach(item=> acc += item)
return acc;
}, 0);
Check I passed the first value (0) as the second parameter to reduce method.
Hope this would help:
arr = [
{ 'Atencion Personalizada': 2, 'Caja': 3 },
{ 'Atencion Personalizada': 1 },
{ 'Tarifa Social': 3 }
]
var sum = arr.map(item => {
var x = 0
for (const [key, value] of Object.entries(item)) {
x+=value
}
return x
}).reduce( (acc, curr) => acc + curr)
console.log(sum)
arr = [
{ 'Atencion Personalizada': 2, 'Caja': 3 },
{ 'Atencion Personalizada': 1 },
{ 'Tarifa Social': 3 }
]
var total = 0;
arr.forEach((x, i) =>
total += Object.values(x).reduce((a,b) => a+b)
);
console.log(total)
Solve it with this approach:
use JSON.stringify to get arr as string.
extract numbers with match with regex \d+ and flag g to return it as group.
map it to real numbers rather than string.
reduce it to the sum of all numbers.
JSON.stringify(arr) //'[{"Atencion Personalizada":2,"Caja":3},{"Atencion Personalizada":1},{"Tarifa Social":3}]'
.match(/\d+/g) //['2', '3', '1', '3']
.map(Number) //[2, 3, 1, 3]
.reduce((acc, n)=>acc+n, 0) //9

Sum of certain values of object

I have to calculate a sum of certain object values ( not all )
I have this object :
let object = {a: 1, b: 4, c: 2, d: 3, e: 10}
I need to sum just the a, c, d, e values.
Actually I use this method which sums all the values and gives me 20, but I need to have 16.
Object.keys(object).reduce((sum, key) => sum + parseFloat(object[key] || 0), 0)
How can I do this sum ?
Your sum function is good as it is, you just need to apply a filter
Object.keys(object)
.filter(key => key !== 'b')
.reduce((sum, key) => sum + parseFloat(object[key] || 0), 0)
Or, if you want a whitelist
const validKeys = {
a: true,
b: false, // optional
c: true,
d: true,
e: true
}
Object.keys(object)
.filter(key => validKeys[key])
.reduce((sum, key) => sum + parseFloat(object[key] || 0), 0)
To follow what you originally did, You should have an array of the keys and check to see if it is included before you add it.
const myObject = {a: 1, b: 4, c: 2, d: 3, e: 10}
const keys = ['a', 'c','d', 'e']
const entries = Object.entries(myObject)
const result = entries.reduce( (total, [key, value]) => (keys.includes(key) ? value : 0) + total, 0)
console.log(result)
smarter way is to loop over the keys
const myObject = {a: 1, b: 4, c: 2, d: 3, e: 10}
const keys = ['a', 'c','d', 'e']
const result = keys.reduce( (total, key) => (myObject[key] || 0) + total, 0)
console.log(result)
I'll add my two cents to the thread for...in is awesome too xD
let object = {a: 1, b: 4, c: 2, d: 3, e: 10}
let sum = 0;
const keys = ['a', 'c', 'd', 'e'];
for(let key in object) {
if(keys.includes(key)) //or key === 'a' || key === 'c' ..
sum += object[key];
}
console.log(sum);
You could take the wanted keys directly.
let object = {a: 1, b: 4, c: 2, d: 3, e: 10},
keys = ['a', 'c', 'd', 'e'],
result = keys.reduce((sum, key) => sum + (object[key] || 0), 0);
console.log(result);
You could either declare the keys you want to sum (whitelist) or those you wish to omit (blacklist). I've used the latter approach here:
let object = {a: 1, b: 4, c: 2, d: 3, e: 10},
ignore = ['b'],
sum = Object.keys(object)
.filter(key => !ignore.includes(key))
.reduce((total, key) => total += object[key], 0);
console.log(sum); //16
Fiddle

Point-free group by multiple properties

I'm struggling a bit with implementing a variant groupBy that would allow grouping by multiple properties in a point-free style. (I'm using typescript & ramda).
I want to group some elements say of type A by properties returned from function getProperties :: A a -> [b]. In imperative paradigm the implementation could look like that:
const getProperties = (item: Item): Array<keyof Item> => [item.x, item.y.z];
const groupByMany = (items: Item[]): Dictionary<Item[]> => {
let groupped: Dictionary<Item[]> = {};
for (let item of items) {
for (let key of getProperties(item)) {
if (groupped[key]) {
groupped[key].push(item);
} else {
groupped[key] = [item];
}
}
}
}
Example:
const items = [
{ i: 1, x: 'A', y: { z: 'B' } },
{ i: 2, x: 'A' },
{ i: 3, x: 'B', y: { z: 'B' } },
];
const expectedOutput = {
A: [ { i: 1, ... }, { i: 2, ... }],
B: [ { i: 1, ... }, { i: 3, ... }],
};
I'll get you started -
const reduce = (f, init, xs) =>
xs .reduce (f, init)
const upsert = (m, k, v) =>
m .has (k)
? m .get (k) .push (v)
: m .set (k, [ v ])
const groupByMany = (f, xs) =>
reduce
( (m, x) =>
( f (x) .forEach (k => k && upsert (m, k, x))
, m
)
, new Map
, xs
)
const items =
[ { i: 1, x: 'A', y: { z: 'B' } }
, { i: 2, x: 'A' }
, { i: 3, x: 'B', y: { z: 'B' } }
]
const result =
groupByMany
( item => [ item.x, item.y && item.y.z ]
, items
)
console.log(Object.fromEntries(result.entries()))
Notice how the last item has a B for .x and .y.z so it get's inserted into the B group twice. We change upsert so it will not insert a duplicate value -
const upsert = (m, k, v) =>
m .has (k)
? m .get (k) .includes (v)
? m
: m .get (k) .push (v)
: m .set (k, [ v ])
Expand the snippet below to see the final result in your own browser -
const reduce = (f, init, xs) =>
xs .reduce (f, init)
const upsert = (m, k, v) =>
m .has (k)
? m .get (k) .includes (v)
? m
: m .get (k) .push (v)
: m .set (k, [ v ])
const groupByMany = (f, xs) =>
reduce
( (m, x) =>
( f (x) .forEach (k => k && upsert (m, k, x))
, m
)
, new Map
, xs
)
const items =
[ { i: 1, x: 'A', y: { z: 'B' } }
, { i: 2, x: 'A' }
, { i: 3, x: 'B', y: { z: 'B' } }
]
const result =
groupByMany
( item => [ item.x, item.y && item.y.z ]
, items
)
console.log(Object.fromEntries(result.entries()))
A note on SO's peculiar output: SO will not display the same object twice, instead it will give an object a reference, and print that reference where the duplicate object would appear. For example /**id:3**/ in the program's output -
{
"A": [
{
/**id:3**/
"i": 1,
"x": "A",
"y": {
"z": "B"
}
},
{
"i": 2,
"x": "A"
}
],
"B": [
/**ref:3**/,
{
"i": 3,
"x": "B",
"y": {
"z": "B"
}
}
]
}
Which matches your expected output -
const expectedOutput = {
A: [ { i: 1, ... }, { i: 2, ... }],
B: [ { i: 1, ... }, { i: 3, ... }],
};
It's not point-free like you asked for, but I only said I'd get you started ...
I couldn't tell from the question whether you wanted something that made it easy for you to code point-free or if for some reason you were looking for an actual point-free implementation. If it's the latter, then I'm afraid this will be no help. But it's a fairly simple
const groupByMany = (fn) => (xs) =>
xs .reduce
( (a, x) => [...new Set ( fn(x) )] . reduce
( (a, k) => k ? {...a, [k]: [... (a [k] || []), x ] } : a
, a
)
, {}
)
// const getProperties = (item) => [path(['x'], item), path(['y', 'z'], item)]
const getProperties = juxt ( [path (['x']), path (['y', 'z']) ] )
const items = [{ i: 1, x: 'A', y: { z: 'B' } }, { i: 2, x: 'A'}, { i: 3, x: 'B', y: { z: 'B' } }]
console .log
( groupByMany (getProperties) (items)
)
<script src="https://bundle.run/ramda#0.26.1"></script></script>
<script>const { juxt, path } = ramda </script>
Running the keys through [... new Set ( fn(x) ) ] is just a quick way to eliminate duplicates from the array returned by fn (x). The rest of the function should be pretty clear.

Categories