Reduce Array of Array of Objects Using Ramda - javascript

I have an array of array of objects. I want to reduce that to an array of object and adding one more property to each object.
The sample input is:
const data = [
[
{name:"a", val:5},
{name:"b", val:10},
{name:"c", val:20},
{name:"d", val:50},
{name:"e", val:100}
],
[
{name:"a", val:0},
{name:"b", val:20},
{name:"c", val:30},
{name:"d", val:40},
{name:"e", val:10}
],
[
{name:"a", val:60},
{name:"b", val:50},
{name:"c", val:40},
{name:"d", val:70},
{name:"e", val:30}
]
];
And the Output should be:
[{name: 'a', val: 65, rank: 'si'},
{name: 'b', val: 80, rank: 'dp'},
{name: 'c', val: 90, rank: 'en'}
{name: 'd', val: 160, rank: 'fr'}]
Rank is static text means for a, it will always be "si"
How can I achieve this using ramda?

You can convert flatten all sub arrays to a single array, group by the name, and then map the groups, and reduce each group to a single object using R.mergeWithKey to add the val property. Convert back to an array using R.values, and map to add the static ranks property by name.
Note that you must create a Map or a dictionary object to take the rank by name from.
const { mergeWithKey, pipe, flatten, groupBy, prop, map, reduce, values } = R
const ranks = new Map([['a', 'si'], ['b', 'dp'], ['c', 'en'], ['d', 'fr']])
// merge deep and combine val property values
const combine = mergeWithKey((k, l, r) => k == 'val' ? l + r : r)
const mergeData = pipe(
flatten, // flatten to a single array
groupBy(prop('name')), // group by the name
map(reduce(combine, {})), // combine each group to a single object
values, // convert back to array
map(o => ({ ...o, rank: ranks.get(o.name) })), // add the static rank property
)
const data = [[{"name":"a","val":5},{"name":"b","val":10},{"name":"c","val":20},{"name":"d","val":50},{"name":"e","val":100}],[{"name":"a","val":0},{"name":"b","val":20},{"name":"c","val":30},{"name":"d","val":40},{"name":"e","val":10}],[{"name":"a","val":60},{"name":"b","val":50},{"name":"c","val":40},{"name":"d","val":70},{"name":"e","val":30}]]
const results = mergeData(data)
console.log(results)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>

Related

Making an array from two another

I have two arrays of objects (10 objects in each arrray) ->
arr1 = [{name: '', age: ''}...]
and
arr2 = [{surname: '', position: ''}...]
which I hold in two separated states.
My goal is to create the third array of the objects which contains also 10 elements
arr3=[{name: arr1.name, surname: arr2.surname}...]
How can I do this ?
As long as it is a 1 to 1 where each index matches, it is a simple Array map and using the spread to copy over the properties and values.
const arr1 = [{a: 1, b: 2}, {a: 3, b: 4}];
const arr2 = [{c: 11, d: 22}, {c: 33, d: 44}];
const arr3 = arr1.map((obj, ind) => ({...obj, ...arr2[ind]}), []);
console.log(arr3);
This is what you need,
let arr3 = []
arr1.forEach((obj1, index) => {
let obj3 = {...obj1, ...arr2[index]}
arr3.push(obj3)
})
If the indexes are assumed to match between the two arrays (which also implies that the two arrays are the same length), you can use map() on one array and use its index parameter to reference the other array. (It doesn't really matter which.)
For example:
const arr3 = arr1.map((a, i) => ({
name: a.name,
surname: arr2[i].surname
}));
Note that this is based on the example shown, where only two properties (one from each array) are in the resulting objects. If you want all properties in the resulting objects, you don't need to specify all of them. You can just combine them all:
const arr3 = arr1.map((a, i) => ({
...a,
...arr2[i]
}));
Here is how you could combine both arrays using a for loop:
const arr3 = []
for (let i = 0; i < arr1.length; i++) {
arr3[i] = {
name: arr1[i].name,
surname: arr2[i].surname
}
}
I don't get exactly what do you want to do but I think this question has been answered a few times.
You wanna do arr1 + arr2 ? Like they follow each others in the array3 ?

How to reduce unsorted array against another sorted array, keeping sorted order?

Given an array of objects named allItems which is pre-sorted, but cannot be sorted again from the information it contains - what is an alternative implementation to the reduce function below that will retain the sorted order of allItems?
The logic below will output:
[{ id: 'd' }, { id: 'a' }, { id: 'b' }]
The desired output is:
[{ id: 'a' }, { id: 'b' }, { id: 'd' }]
// NOTE: allItems is pre-sorted, but lacks the information to re-sort it
const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const includedIds = ['d', 'a', 'b'];
// QUESTION: How to create the same output, but in the order they appear in allItems
const unsortedIncludedItems = includedIds.reduce((accumulator, id) => {
const found = allItems.find(n => n.id === id);
if (found) accumulator.push(found);
return accumulator;
}, [])
As mentioned in response to #Ben, simply reversing the logic is a deal breaker for performance reasons.
Instead of iterating over the includedIds (in the wrong order) and seeing whether you can find them in allItems, just iterate over allItems (which is the right order) and see whether you can find their ids in includedIds:
const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const includedIds = ['d', 'a', 'b'];
const includedItems = allItems.filter(item => includedIds.includes(item.id));
The issue you have here is that your code reverses the list. You can simply push to the front of the list instead, and the original order will be maintained.
Unfortunately, pushing to the front of a list is slower, it's O(n) rather than O(1). It looks like Array.prototype.unshift is supposed to be faster, but it's still O(n) according to this blog. Assuming that the number of found elements is small you won't notice any performance issues. In that case, replace push with unshift like so:
// NOTE: allItems is pre-sorted, but lacks the information to re-sort it
const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const includedIds = ['d', 'a', 'b'];
// QUESTION: How to create the same output, but in the order they appear in allItems
const unsortedIncludedItems = includedIds.reduce((accumulator, id) => {
const found = allItems.find(n => n.id === id);
if (found) accumulator.unshift(found);
return accumulator;
}, [])
Otherwise, these are your options:
Create a wrapper around this object that reverses the indexes rather than the array. This can be done with a function like this:
const getFromEnd = (arr, i) => arr[arr.length - 1 - i]
Note that this can be replaced with arr.at(-i) in new browser versions (last few months). This could be encapsulated within a class if you're feeling OOP inclined.
Remember to manually invert the indices wherever you use this array (this will be bug-prone, as you may forget to invert them)
Reverse the array. As shown in this fiddle, even with 10,000 elements, the performance is not bad. Assuming this isn't a hotpath or user-interactive code, I think that even 100,000 is probably fine.
Update
Example B will use the index of the input array to sort the filtered array.
Try .filter() and .include() to get the desired objects and then .sort() by each object's string value. See Example A.
Another way is to use .flatMap() and .include() to get an array of arrays.
// each index is from the original array
[ [15, {id: 'x'}], [0, {id: 'z'}], [8, {id: 'y'}] ]
Then use .sort() on each sub-array index.
[ [0, {id: 'z'}], [8, {id: 'y'}], [15, {id: 'x'}] ]
Finally, use .flatMap() once more to extract the objects and flatten the array of arrays into an array of objects.
[ {id: 'z'}, {id: 'y'}, {id: 'x'} ]
See Example B
Example A (sort by value)
const all = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const values = ['d', 'a', 'b'];
const sortByStringValue = (array, vArray, key) => array.filter(obj => vArray.includes(obj[key])).sort((a, b) => a[key].localeCompare(b[key]));
console.log(JSON.stringify(sortByStringValue(all, values, 'id')));
Example B (sort by index)
const all = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const values = ['d', 'a', 'b'];
const alt = [{name:'Matt'}, {name:'Joe'}, {name:'Jane'}, {name:'Lynda'}, {name:'Shelly'}, {name:'Alice'}];
const filter = ['Shelly', 'Matt', 'Lynda'];
const sortByIndex = (array, vArray, key) => array.flatMap((obj, idx) => vArray.includes(obj[key]) ? [[idx, obj]] : []).sort((a, b) => a[0] - b[0]).flatMap(sub => [sub[1]]);
console.log(JSON.stringify(sortByIndex(all, values, 'id')));
console.log(JSON.stringify(sortByIndex(alt, filter, 'name')));
Why not just reverse the logic, Filter out the ids which not suppose to be includes.
// NOTE: allItems is pre-sorted, but lacks the information to re-sort it
const allItems = [
{ id: "a" },
{ id: "b" },
{ id: "c" },
{ id: "d" },
{ id: "e" },
{ id: "f" },
];
const includedIds = ["d", "a", "b"];
const findElms = (items, includedIds) => items.filter((n) => includedIds.includes(n.id))
console.log(findElms(allItems, includedIds));

Extract subarray of Columns from a multidimensional array and mutate the original array to remove those columns JavaScript

I want to extract a subarray of Columns from a multidimensional array and mutate the original array to remove those columns in JavaScript
Ex: If I have an array
originalArray =
[
[A, B, C, D, E, F],
[a1,b1,c1,d1,e1,f1],
[a2,b2,c2,d2,e2,f2],
[a3,b3,c3,d3,e3,f3]
[
Where A,B,C,D,E,F are headers
and I want to extract columns [4,0,3] in this order I would get
subArray =
[
[E, A, D],
[e1,a1,d1],
[e2,a2,d2],
[e3,a3,d3]
[
originalArray =
[
[B, C, F],
[b1,c1,f1],
[b2,c2,f2],
[b3,c3,f3]
[
I found this:
Extract subarray from a multidimensional array and mutate the original array
which does this for rows
let subArray = originalArray.reduce((accumulator, current, index) => {
if (idxList.includes(index)) {
//if the current index is included in the idxList array,
//push the current value to the accumulator
accumulator.push(current);
} else {
//push the current value to the `tempArray`.
tempArray.push(current)
}
return accumulator;
}, []);
Then I will concatenate these two arrays
reorderedArray = [...subArray, ...originalArray]
Reduce seems like an interesting approach and it seems like it should be a simple fix but I have to admit I am having a hard time understanding Reduce
Thanks in advance for your assistance
You could add the missing indices to order and map the array by mapping nested array with the indices of order.
let
data = [['A', 'B', 'C', 'D', 'E', 'F'], ['a1', 'b1', 'c1', 'd1', 'e1', 'f1'], ['a2', 'b2', 'c2', 'd2', 'e2', 'f2'], ['a3', 'b3', 'c3', 'd3', 'e3', 'f3']],
order = [4, 0, 3],
seen = new Set(order),
i = 0;
while (order.length < data[0].length) {
while (seen.has(i)) ++i;
order.push(i++);
}
data = data.map(a => order.map(i => a[i]));
data.forEach(a => console.log(...a));
regular mapping seems like it would work:
function myFunction() {
var originalArray = SpreadsheetApp.getActive().getRange('Sheet1!A:F').getValues();
var subarray = originalArray.map(e=>[e[4],e[0],e[3]]);
originalArray = originalArray.map(e=>[e[1],e[2],e[5]]);
}

How to merge an array of objects into one array and then filter out the duplicates

Firstly, I am trying to merge an array of many objects into a single array with every key in each object.
Lastly, any duplicate items in the array should be removed as well as any elements named "name".
Input:
const data = [
{
name: '10/20',
Tyler: 1,
Sonia: 0,
Pedro: 0,
},
{
name: '10/23',
Tyler: 0.5,
Sonia: 0.25,
Pedro: 0.75,
George: 0.5,
},
];
Output:
["Tyler", "Sonia", "Pedro", "George"]
This is what I've tried so far:
const mergedData = data.reduce((prev, cur) => {
const obj = cur[0];
const keys = Object.keys(obj);
const names = keys.splice(1);
return { names };
}, []);
I am trying to capture any key name other than "name" and add it to the final array. However, this is where I get stuck because I get this error, TypeError: Cannot convert undefined or null to object
Note: Objects may be different lengths, contain a mix of names, but never any duplicates.
An option is to find all keys put in a set and remove the name key
const data = [
{
name: '10/20',
Tyler: 1,
Sonia: 0,
Pedro: 0,
},
{
name: '10/23',
Tyler: 0.5,
Sonia: 0.25,
Pedro: 0.75,
George: 0.5,
},
];
const set = new Set(data.reduce((acc, i) => [...acc, ...Object.keys(i)], []));
set.delete('name');
const result = [...set];
console.log(result);
If you have access to ES6 methods, you can do this using a Set (unique values are ensured at creation) and converting it back into an array if you want through Destructuring.
data = [{name: '0', Tyler: '1', Dan: '2', Carl: '3'}, {name: '0', Tyler: '1', Dan: '2', Eric: '3', Danny: '4'}];
const output = (data) => {
let output = [];
// This makes sure you get each array and then strips just the keys as desired
data.forEach(item => {
output = output.
concat(Object.keys(item))
});
// This creates the set, strips our dups, and then spreads itself into an array
return [...new Set(output)]
// Strip out the 'name' key as needed
// NOTE: This should be a param instead of hard-coded, but this is easier to show
.filter(res => res != 'name');
}
console.log(output(data));
This should be fairly performant considering it only navigates the full array one time and each object itself shouldn't have millions of properties to cause .keys() any issues.

Ramda - combine duplicates of objects in array with same key value

I have an array of objects, where I want to combine if they have the same name, and add their feederTeams into an array.
The array looks like this:
const TEAMS = [{
name: 'Liverpool',
id: '1',
feederTeam: 'Tranmere'
}, {
name: 'Liverpool',
id: '2',
feederTeam: 'Stockport'
}, {
name: 'Man Utd',
feederTeam: 'Bla',
id: '3',
}, {
name: 'Liverpool',
id: '6',
feederTeam: 'Oldham'
}]
and I want to transform it into this using Ramda:
[{
name: 'Liverpool',
feederTeam: ['Tranmere', 'Stockport', 'Oldham']
}, {
name: 'Man Utd',
feederTeam: ['Bla'],
}]
I have tried many approaches with no luck. Here is my latest attempt.
R.forEach((team => R.filter(R.propEq('name', team.name), TEAMS)), TEAMS);
Since you want to group several elements together use R.groupBy. Then get an array from the object of groups with R.values, and map each group to the required form:
const { pipe, groupBy, prop, values, map, applySpec, nth } = R
const fn = pipe(
groupBy(prop('name')), // group object by the name property
values, // get an array of groups
map(applySpec({ // map each group to an object
name: pipe(nth(0), prop('name')), // take the name from the 1st object in the group
feederTeam: map(prop('feederTeam')), // collect all feedTeam props to an array
})),
)
const teams = [{"name":"Liverpool","id":"1","feederTeam":"Tranmere"},{"name":"Liverpool","id":"2","feederTeam":"Stockport"},{"name":"Man Utd","feederTeam":"Bla","id":"3"},{"name":"Liverpool","id":"6","feederTeam":"Oldham"}]
const result = fn(teams)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>
A slight variation would be to map the object of groups with R.mapObjIndexed. In this case the name the key (is the 2nd param supplied to R.applySpec). After mapping the groups to objects, convert the result to an array with R.values:
const { pipe, groupBy, prop, values, mapObjIndexed, applySpec, map, nthArg } = R
const fn = pipe(
groupBy(prop('name')), // group object by the name property
mapObjIndexed(applySpec({ // map each group to an object
name: nthArg(1), // take the name from key (2nd arg)
feederTeam: map(prop('feederTeam')), // collect all feedTeam props to an array
})),
values, // convert to an array of objects
)
const teams = [{"name":"Liverpool","id":"1","feederTeam":"Tranmere"},{"name":"Liverpool","id":"2","feederTeam":"Stockport"},{"name":"Man Utd","feederTeam":"Bla","id":"3"},{"name":"Liverpool","id":"6","feederTeam":"Oldham"}]
const result = fn(teams)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>

Categories