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.
Related
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);
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)
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
I have data in the form of an Object of Objects
{
a: {
x: 1,
y: 1
},
b: {
x: 10,
y: 10
}
}
I would like to transform it into an Array of Objects, each Object being formed from the key and values of the original one. In my case what would hold the key, and value_x the content of x in the value (of the original object):
[{
value_x: 1,
what: "a"
}, {
value_x: 10,
what: "b"
}]
I can do that via
o = {
a: {
x: 1,
y: 1
},
b: {
x: 10,
y: 10
}
}
a = Object.entries(o).map(e => {
return {
what: e[0],
value_x: e[1].x
}
})
console.log(a)
It works but seem quite ugly. Coming from a Python background, I was hoping to be able to get directly the key and value elements via something like
a = Object.entries(o).map([k, v] => {return {what: k, value_x: v.x}})
or
a = Object.entries(o).map((k, v) => {return {what: k, value_x: v.x}})
but none of them work.
Is there a better solution? (= more aligned with the language)
You could take the entries for the nested object as well with a flatMap approach.
const
o = { a: { x: 1, y: 1 }, b: { x: 10, y: 10 } },
a = Object
.entries(o)
.flatMap(([what, values]) => Object
.entries(values)
.map(([k, v]) => ({ what, [`value_${k}`]: v }))
);
console.log(a);
let obj = {
a: {
x: 1,
y: 1
},
b: {
x: 10,
y: 10
}
};
console.log(
Object.entries(obj).map(([key,{x}])=>({value_x: x,what: key}))
);
Pretty straight forward:
var bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
var reduce = bar.reduce((acc, item) => {
acc['a'] = item.a++;
acc['b'] = item.b++
return acc;
}, {});
console.log(reduce);
{a: 10, b: 20}
I'd like reduce assigned the reference: {a:20, b: 40}
Here is a general solution that will work even if your object inside your array contains different properties.
var bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
var reduce = bar.reduce((acc, item) => {
for (let [key, value] of Object.entries(item)){
if( acc.hasOwnProperty(key)) {
acc[key] += value
}
else {
acc = {...acc, [key]: value }
}
}
return acc;
}, {});
console.log(reduce);
Rather than assigning the accumulator's property the item's property incremented by one, you should add to the existing accumulator's property value. You also shouldn't pass an initial object to the reduce given this implementation (or, if you do, you'll need to define the a and b properties).
Since you're using reduce, I think you should also consider using const instead of var - const is less bug-prone and easier to read:
const bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
const reduced = bar.reduce((acc, item) => {
acc.a += item.a;
acc.b += item.b;
return acc;
});
console.log(reduced);
You could return a new object with added values.
var bar = [{ a: 10, b: 20 }, { a: 10, b: 20 }],
reduce = bar.reduce((a, b) => ({ a: a.a + b.a, b: a.b + b.b }));
console.log(reduce);
Or with a complete dynamic approach for all properties.
const add = (a, b) =>
Object.assign({}, a, ...Object.entries(b).map(([k, v]) => ({ [k]: a[k] + v })));
var bar = [{ a: 10, b: 20 }, { a: 10, b: 20 }],
reduce = bar.reduce(add);
console.log(reduce);