Group consecutive similar array of elements - javascript

Here is my simple array of objects
const array = [
{ name: 'a', val: '1234' },
{ name: 'b', val: '5678' },
{ name: 'c', val: '91011' },
{ name: 'c', val: '123536' },
{ name: 'e', val: '5248478' },
{ name: 'c', val: '5455' },
{ name: 'a', val: '548566' },
{ name: 'a', val: '54555' }
]
I need to group consecutive name elements and push the corresponding val. So the expected output should be
const array = [
{ name: 'a', vals: '1234' },
{ name: 'b', vals: '5678' },
{ name: 'c', vals: ['91011', '123536'] },
{ name: 'e', vals: '5248478' },
{ name: 'c', vals: '5455' },
{ name: 'a', vals: ['548566', '54555'] }
]
I tried it But could not get over it. Please help
const output = []
const result = array.reduce((a, c) => {
if (a.name === c.name) {
output.push(a);
}
}, []);

You were actually quite close:
const output = [];
array.reduce((a, c) => {
if (a.name === c.name) { // current element equals previous element, lets merge
a.values.push(c.val);
} else output.push(a = { name: c.name, values: [c.val] ); // otherwise add new entry
return a; // the current element is the next previous
} , {}); // start with an empty a, so that c always gets pushed
Note that it makes little sense to store numbers as string though.

You can reduce the array like this. Compare the current name with previous item's name. If they are not the same, add a new item to the accumulator. If they are the same, then use concat the merge val with the last item in accumulator. concat is used because vals could either be a string or an array.
const array = [
{ name: 'a', val: '1234' },
{ name: 'b', val: '5678' },
{ name: 'c', val: '91011' },
{ name: 'c', val: '123536' },
{ name: 'e', val: '5248478' },
{ name: 'c', val: '5455' },
{ name: 'a', val: '548566' },
{ name: 'a', val: '54555' }
]
const merged = array.reduce((acc, { name, val }, i, arr) => {
// check if name is same as the previous name
if (arr[i - 1] && arr[i - 1].name === name) {
const prev = acc[acc.length - 1]; // last item in the accumulator
prev.vals = [].concat(prev.vals, val)
} else
acc.push({ name, vals: val })
return acc
}, [])
console.log(merged)

Related

JavaScript: Get non-repeating items from 2d array of objects

Compare 2d array of objects and convert it into single array of objects by getting the unique items (non-repeating in terms of id) using Javascript.
Sample input:
const data = [
[ { name: 'x', id: 1 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'y', id: 12 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'z', id: 22 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ]
];
Expected output:
const out = [
{ name: 'a', id: 1 },
{ name: 'b', id: 12 },
{ name: 'b', id: 22 }
]
Using Array#flat, convert the 2d array into one
Using Array#reduce, iterate over the latter while updating a Map where the id is the key and the count is the value
Using Map#entries, get the list of id-count pairs from the Map. Then, using Array#filter, get the ids whose count is 1. Finally, using Array#map, get the list of resulting ids
Using Array#filter, iterate over the flattened array of objects and return the elements whose ids belong to the non-repeating ids list
const data = [
[ { name: 'x', id: 1 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'y', id: 12 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'z', id: 22 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ]
];
const arr = data.flat();
const idCountMap = arr.reduce((map, { id }) => map.set(id, (map.get(id) ?? 0) + 1), new Map);
const nonRepeatingIds =
[...idCountMap.entries()]
.filter(([ id, count ]) => count === 1)
.map(([ id ]) => id);
const nonRepeatingItems = arr.filter(({ id }) => nonRepeatingIds.includes(id));
console.log(nonRepeatingItems);
You can do this by converting the use case in two parts.
Part 1 : Concat the array of objects to a new array
var array = [];
data.forEach(val => {
array = [...array, ...val];
});
Part 2 : Find the unique values in the new array
const result = [];
const map = new Map();
for (const item of array) {
if(!map.has(item.id)){
map.set(item.id, true); // set any value to Map
result.push({
id: item.id,
name: item.name
});
}
}
console.log(result)
you have to loop the array of array and check if you already have added the id or not. In this way you can make the id unique in the result
const data= [
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},],
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},],
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},]
]
//loop for each array and filter
var addedId = [];
//init result
var out = [];
//loop array
data.forEach(function(arr){
//loop element in array
arr.forEach(function(obj){
// check if the id has been already added to the result
if(!addedId.includes(obj.id)){
addedId.push (obj.id);
out.push(obj);
}
})
})

Unflatten array to Tree without parent id but with level

I'm a bit stuck with something implying recursion. I am receiving data
from an API. It looks like this:
const input = [
{ id: 'a', level: 0 },
{ id: 'b', level: 1 },
{ id: 'c', level: 1 },
{ id: 'd', level: 2 },
{ id: 'e', level: 1 },
{ id: 'f', level: 0 },
];
and I need something like
const out = [
{ id: 'a', nodes: [
{ id: 'b', nodes: [] },
{ id: 'c', nodes: [
{ id: 'd', nodes: [] },
] },
{ id: 'e', nodes: [] },
] },
{ id: 'f', nodes: [] },
];
How would you achieve that in an elegant way such as out = f(input) ?
I feel we can do a recursive nest method through a reduce but I did not manage to get it right :)
Thanks in advance!
You could use a helper array for the levels with the latest array/nodes property from the object.
const
input = [{ id: 'a', level: 0 }, { id: 'b', level: 1 }, { id: 'c', level: 1 }, { id: 'd', level: 2 }, { id: 'e', level: 1 }, { id: 'f', level: 0 }],
result = [],
levels = [result];
input.forEach(({ id, level }) =>
levels[level].push({ id, nodes: levels[level + 1] = [] })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You may try out like,
function makeObject(id){
return { id: id, nodes:[] };
}
function addObjectToNodes(array, id, node){
array.map(a => {
if(a.id === id)
a.nodes.push(node);
});
}
const nodes = [];
nodes.push(makeObject('a'));
nodes.push(makeObject('f'));
addObjectToNodes(nodes, 'a', makeObject('b'));
addObjectToNodes(nodes, 'a', makeObject('c'));
addObjectToNodes(nodes, 'a', makeObject('d'));
addObjectToNodes(nodes, 'a', makeObject('e'));
console.log(nodes);

Ramda GroupBy - How to group by any prop

given:
interface Dict {
[key: string]: any
}
const data: Dict[] = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' },
];
where the keys of these Dict objects aren't specified...
How can I get this result?
const result = {
id: ['a', 'b', 'c', 'd', 'e'],
b: ['something', 'else'],
extra: ['hello world'],
// ... and any other possible key
}
You can flatten the object into a list of pairs, group it, and convert the pairs back to values:
const data = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' },
];
let z = R.pipe(
R.chain(R.toPairs),
R.groupBy(R.head),
R.map(R.map(R.last))
)
console.log(z(data))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Slight variation from Ori Drori answer:
(assuming that no properties in your objects are contained in arrays already)
const data = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' }
];
const run = reduce(useWith(mergeWith(concat), [identity, map(of)]), {});
console.log(
run(data)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {reduce, useWith, mergeWith, concat, identity, map, of} = R;</script>
Use R.reduce with R.mergeWith and concat all items:
const { mergeWith, reduce } = R
const fn = reduce(mergeWith((a, b) => [].concat(a, b)), {})
const data = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' },
];
const result = fn(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
If you need the single value (extra) in array as well, map the items, and wrap with an array, just the values that are not an array already:
const { pipe, mergeWith, reduce, map, unless, is, of } = R
const fn = pipe(
reduce(mergeWith((a, b) => [].concat(a, b)), {}),
map(unless(is(Array), of))
)
const data = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' },
];
const result = fn(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

how to transform or sort array against different array?

const r = ['a', 'b', 'c', 'l', 'p'];
const arr = [{
name: "ss3",
id: 'c'
}, {
name: "ss2",
id: 'b'
}, {
name: "ss4",
id: 'p'
}, {
name: "ss1",
id: 'a'
}]
var newArray =arr.map((i)=>{
let e = r[i];
if(i.id===e){
return i
}
})
console.log(newArray)
Expected output
const arr = [{
name: "ss1",
id: 'a'
}, {
name: "ss2",
id: 'b'
}, {
name: "ss3",
id: 'c'
}, {
name: "ss4",
id: 'p'
}
]
Given two arrays r and arr, I wish to sort arr with respect to r, i.e. in alphabetical order by id.
https://jsbin.com/yitijiboso/edit?html,js,output
I think this might be a concise (although not very performant) way to achieve the desired output:
const arr1 = ['a', 'b', 'c', 'l', 'p'];
const arr2 = [
{
name: "ss3",
id: 'c'
},
{
name: "ss2",
id: 'b'
}, {
name: "ss4",
id: 'p'
},
{
name: "ss1",
id: 'a'
}
];
arr2.sort((a, b) => arr1.indexOf(a.id) - arr1.indexOf(b.id));
console.log(arr2);
Easy:
make a map from main 'arr' keyBy 'id' https://www.npmjs.com/package/lodash.keyby
loop across 'r', if key exist in new map, get value and push to new array
const arrMap = _.keyBy(arr, 'id');
let newR = [];
r.forEach( key => {
if ( arrMap[key] ) {
newR.push( arrMap[key] );
}
} );
console.log( 'new array', newR );
Taking a clue from #Petr Broz, here's my suggestion:
const r = ['a', 'b', 'c', 'l', 'p'];
const arr = [
{
name: "ss3",
id: 'c'
},
{
name: "ss2",
id: 'b'
}, {
name: "ss4",
id: 'p'
},
{
name: "ss1",
id: 'a'
}
];
arr.sort((a, b) => r.indexOf(a.id) > r.indexOf(b.id));
console.log(arr);
Main difference is that this code utilizes the arrays as named by the OP and uses a greater than comparison operator. However, if you just want to have the array arr sorted in alphabetical order you don't need to compare it with array r:
const arr = [
{
name: "ss3",
id: 'c'
},
{
name: "ss2",
id: 'b'
}, {
name: "ss4",
id: 'p'
},
{
name: "ss1",
id: 'a'
}
];
arr.sort(function(a, b)
{
if (a.id > b.id) {
return 1;
}
else
if (a.id < b.id) {
return -1;
}
else
{
return 0;
}
});
console.log(arr);
Note, in this example the return values are numeric instead of boolean values which would be helpful if the array to be sorted were to have duplicate values.

JavaScript operation array object merge

I have two arrays, and now I want to merge the two arrays.
The first array:
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
Two arrays:
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
I want to merge them into this:
var data3 = [
{name: 'aa', value: 1},
{name: 'bb', value: 2},
{name: 'cc', value: 3},
{name: 'dd', value: 0},
{name: 'ee', value: 0}
];
console.log(data3)
At present, my experience is not enough. Please help me solve this problem.
Thanks in advance.
You can try following based on following assumptions
data2 is a collection of names and expecting its length to be always more than length of data
Order of objects can be different
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
// Iterate over the names array
var data3 = data2.map(({name}) => {
// get the matched object in data corresponding to the name
var match = data.find((obj) => obj.name === name);
// if found, return value else default value to 0
return match ? match : {name, value : 0};
});
console.log(data3);
If the input arrays are indeed in order like that, then a simple .map would suffice:
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
const output = data2.map(({ name }, i) => ({ name, value: data[i] ? data[i].value : 0 }));
console.log(output);
Create an object lookup for each name using array#reduce. Extract all the values using the Object.values() from the object lookup.
const data1 = [ { name: 'aa', value: 1 }, { name: 'bb', value: 2 }, { name: 'cc', value: 3 } ],
data2 = [ { name: 'aa' }, { name: 'bb' }, { name: 'cc' }, { name: 'dd' }, { name: 'ee' } ],
result = Object.values([data1, data2].reduce((r,a) => {
a.forEach(({name, value = 0}) => {
r[name] = name in r ? r[name] : {name, value};
});
return r;
},{}));
console.log(result);
You can array#concat both the arrays and using array#reduce create an object lookup and then get all the values using the Object.values().
const data1 = [ { name: 'aa', value: 1 }, { name: 'bb', value: 2 }, { name: 'cc', value: 3 } ],
data2 = [ { name: 'aa' }, { name: 'bb' }, { name: 'cc' }, { name: 'dd' }, { name: 'ee' } ],
result = Object.values(data1.concat(data2).reduce((r,{name, value=0}) => {
r[name] = name in r ? r[name] : {name, value};
return r;
},{}));
console.log(result);
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
let output = new Array(data2.length).fill(data2.length).map(v => new Object());
// Logic
data.forEach((val2)=> {
data2.forEach((val, i)=> {
if (val.name == val2.name){
output[i]["name"] = val.name
output[i]["value"] = val2.value
} else{
output[i]["name"] = val.name
}
})
})
output.map((val,i) => {
if (!val.hasOwnProperty("value")){
console.log(val)
val["value"] = 0
}
})
console.log("------Your Expected Format", output)

Categories

Resources