What is the best way to transform an array like this:
const arr = [
{ name: 'Bob' },
{ name: 'Ben' }
{ name: 'Cole' }
{ name: 'Mary' }
{ name: 'Travis' }
]
to an object like:
const obj = {
'B': ['Bob', 'Ben'],
'C': ['Cole'],
'M': ['Mary'],
'T': ['Travis']
}
Using only vanilla JS
You can use array#reduce. Iterate through each object of your array and then extract out the first letter and add names corresponding to it.
const arr = [{name: 'Bob'}, {name: 'Ben'}, {name: 'Cole'}, {name: 'Mary'}, {name: 'Travis'}],
result = arr.reduce((r,{name}) => {
r[name[0]] = r[name[0]] || [];
r[name[0]].push(name);
return r;
},{});
console.log(result);
Vanilla JS you say? Here you go
let nil = x => x === undefined;
let empty = ([h]) => nil(h);
let first = ([h]) => h;
let last = ([h, ...t]) => empty(t) ? h : last(t);
let map = ([h, ...t], f) => nil(h) ? [] : [f(h), ...map(t, f)];
let reduce = ([h, ...t], f, i) => nil(h) ? i : reduce(t, f, f(i, h));
let tab = (a, f) => map(a, x => [x, f(x)]);
let push = (a, x) => nil(a) ? [x] : [...a, x];
let groupBy = (a, f) => _groupBy(tab(a, f));
let _groupBy = ka => reduce(ka, (g, [x, k]) => ({...g, [k]: push(g[k], x)}), {});
///
const arr = [{ name: 'Bob' },{ name: 'Ben' },{ name: 'Cole' },{ name: 'Mary' },{ name: 'Travis' }]
z = groupBy(map(arr, x => x.name), first)
console.log(z)
No built-ins!
I created an array where the key is the first letter of the name using the reduce function and restructuring the 'name' from the objects. If the key exists in the array the name is pushed (using spread operator). Else, it creates the key with only one element.
const arr = [
{ name: 'Bob' },
{ name: 'Ben' },
{ name: 'Cole' },
{ name: 'Mary' },
{ name: 'Travis' }
];
const obj = arr.reduce((res, {name})=>{
res[name[0]] = res[name[0]] ? [...res[name[0]],name] : [name];
return res;
}, {});
console.log(obj);
I think this thread is missing a non functional answer, so here it is:
const obj = {};
for(const {name} of arr)
(obj[name[0]] || (obj[name[0]] = [])).push({name});
let obj = {};
arr.forEach( e => {
const name = e.name;
if (!obj[name.charAt(0)]) obj[name.charAt(0)] = [];
obj[name.charAt(0)].push(name);
})
I'm generating a new object and adding to it news keys based in the first char of the name values (only if the key hasn't been already added).
Then, I add each value to the key that corresponds.
Related
I have an array of object and each object is for example :
const myArr=[{name:"john",id:1}{name:"john",id:2}{name:"mary",id:3}]
for the first 2 element for the property "name" I have the name "john" that is duplicate.
How can I modify the rendered names like that:
const myArr=[{name:"john (1 of 2)",id:1}{name:"john (2 of 2)",id:2}{name:"mary",id:3}]
Thanks in advance!
Reduce the input array into a map by name (i.e. group by name property), and map the array of values to the result array. If the group array has more than 1 element in it then sub-map the group to include the numbering. Flatten the overall result.
const myArr = [
{ name: "john", id: 1 },
{ name: "john", id: 2 },
{ name: "mary", id: 3 }
];
const res = Object.values(
myArr.reduce((groups, current) => {
if (!groups[current.name]) {
groups[current.name] = [];
}
groups[current.name].push(current);
return groups;
}, {})
).flatMap((value) => {
if (value.length > 1) {
return value.map((current, i, arr) => ({
...current,
name: `${current.name} (${i + 1} of ${arr.length})`
}));
}
return value;
});
console.log(res);
You can do use reduce(), filter(), and flat() and do this:
const myArr = [
{name:"john", id:1},
{name:"john", id:2},
{name:"mary", id:3}
]
const res = Object.values(myArr.reduce((acc, curr) => {
const total = myArr.filter(({ name }) => name === curr.name).length;
if(!acc[curr.name]) {
acc[curr.name] = [
{...curr}
]
} else {
const currentSize = acc[curr.name].length;
if(currentSize === 1) {
acc[curr.name][0].name = `${acc[curr.name][0].name} (1 of ${total})`
}
acc[curr.name].push({
...curr,
name: `${curr.name} (${currentSize + 1} of ${total})`
})
}
return acc;
}, {})).flat();
console.log(res);
const myArr = [{name:"john",id:1}, {name:"john",id:2}, {name:"mary",id:3}];
const namesArray = myArr.map(elem => elem.name);
const namesTraversed = [];
let currentCountOfName = 1;
let len = 0;
myArr.forEach(elem => {
len = namesArray.filter(name => name === elem.name).length;
if (len > 1) {
if (namesTraversed.includes(elem.name)) {
namesTraversed.push(elem.name);
currentCountOfName = namesTraversed.filter(name => name === elem.name).length;
elem.name = `${elem.name} (${currentCountOfName} of ${len})`;
} else {
namesTraversed.push(elem.name);
currentCountOfName = 1;
elem.name = `${elem.name} (${currentCountOfName} of ${len})`;
}
}
});
console.log(myArr);
Check if this helps you
const myArr = [{
name: "john",
id: 1
}, {
name: "john",
id: 2
}, {
name: "mary",
id: 3
}]
// to keep a track of current copy index
let nameHash = {}
const newMyArr = myArr.map(ele => {
const noOccurence = myArr.filter(obj => obj.name ===ele.name).length;
if(noOccurence > 1){
// if there are multiple occurences get the current index. If undefined take 1 as first copy index.
let currentIndex = nameHash[ele.name] || 1;
const newObj = {
name: `${ele.name} (${currentIndex} of ${noOccurence})`,
id: ele.id
}
nameHash[ele.name] = currentIndex+ 1;
return newObj;
}
return ele;
})
console.log(newMyArr);
I tried to print "person[0].name: a" and "person[1].name: b" and so on, based on this person array of object:
I used object entries twice, any other way I can make the loop more efficient?
const person = [{
name: 'a',
}, {
name: 'b'
}]
Object.entries(person).forEach(([key, value]) => {
Object.entries(value).forEach(([key2, value2]) => {
console.log(`person[${key}].${key2}`, ':', value2)
})
})
You could take a recursive approach and hand over the parent path.
const
show = (object, parent) => {
const wrap = Array.isArray(object) ? v => `[${v}]` : v => `.${v}`;
Object.entries(object).forEach(([k, v]) => {
if (v && typeof v === 'object') show(v, parent + wrap(k));
else console.log(parent + wrap(k), v);
});
},
person = [{ name: 'a' }, { name: 'b' }];
show(person, 'person');
You don't really need the first call. person is already an Array.
const person = [{
name: 'a',
}, {
name: 'b'
}]
person.forEach((value, key) => {
Object.entries(value).forEach(([key2, value2]) => {
console.log(`person[${key}].${key2}`, ':', value2)
})
})
Just in case forEach is for side effects. If you want to create another array with transformed values you better use map/flatMap instead.
const person = [{
name: 'a',
}, {
name: 'b'
}]
const transformed = person.flatMap((value, key) => {
return Object.entries(value).map(([key2, value2]) => `person[${key}].${key2}:${value2}`)
})
console.log(transformed)
You can also try something like this, with only one forEach() loop
const person = [{
name: 'a',
},{
name: 'b'
}];
person.forEach((el,i) => {
let prop = Object.keys(el).toString();
console.log(`person[${i}].${prop}`, ':', el[prop])
});
There are two array of objects like this
var a = [
{id:'1'},
{id:'2'}
];
var b = [
{id:'1',name:'a'},
{id:'2',name:'b'},
{id:'3',name:'c'}
]
And I need a function, if all ids of the elements of array a can be found in array b, it will return true, otherwise return false
You could use a Set and check with Array#every.
const check = (a, b) => a.every((s => ({ id }) => s.has(id))(new Set(b.map(({ id }) => id))));
var a = [{ id: '1' }, { id: '2' }],
b = [{ id: '1', name: 'a' }, { id: '2', name: 'b' }, { id: '3', name: 'c' }];
console.log(check(a, b));
This is not the most efficient way, as it needs to create the list of ids in b for each item in a.
var a = [
{id:'1'},
{id:'2'},
{id:'7'},
];
var b = [
{id:'1',name:'a'},
{id:'2',name:'b'},
{id:'3',name:'c'}
]
const allPresent = a
.map(item => item.id)
.map(id => Object.assign({
id,
present: b
.map(item => item.id)
.indexOf(id) > -1,
}))
console.log(allPresent)
You can use the following
var a = [
{id:'1'},
{id:'2'}
];
var b = [
{id:'1',name:'a'},
{id:'2',name:'b'},
{id:'3',name:'c'}
]
console.log(checkobject());
function checkobject()
{
var checkid=true;
a.forEach(function(el)
{
var check=b.findIndex(function(element) {
return element.id===el.id;
});
if(check==-1)
{
checkid=false;
return;
}
});
return checkid;
}
Can use this simple way:
var a = [
{id:'1'},
{id:'2'}
];
var b = [
{id:'1',name:'a'},
{id:'2',name:'b'},
{id:'3',name:'c'}
];
var id_a = a.map((current)=>{
return current.id;
}); console.log(id_a); // ["1", "2"]
var id_b = b.map((current)=>{
return current.id;
}); console.log(id_b); // ["1", "2", "3"]
// check if id_a in id_b, check total element of each set
let bool = Array.from(new Set(id_b) ).length == Array.from(new Set(id_b.concat(id_a)) ).length;
console.log(bool);
Solution using Array.prototype.filter and Array.prototype.some:
const includeCheck = (a, b) => {
if (b.filter(el => a.some(obj => obj.id === el.id)).length === b.length) {
console.log('b fully includes a')
} else {
console.log('b does not fully include a')
}
}
let a = [{id:'1'}, {id:'2'}, {id:'3'}],
b = [{id:'1',name:'a'}, {id:'2',name:'b'}, {id:'3',name:'c'}]
includeCheck(a, b);
It compares lengths of original b array and b array filtered by a array's ids to determine whether b has all a ids or not.
I have two arrays that I am concatinating.
However each of these arrays has same property name I want to leave by adding prefix to each.
Array A(aData) looks like
[
{
id: 1,
title: `title`
code: '34x'
},
...
]
Array B(bData) looke like:
[
{
id: 1
prop: 3,
otherporp: `prop`
code: 'hi67'
},
...
]
In order to combine the arrays I am doing concat and reduce to get only matching id's
const data: any = aData.concat(bData).reduce((acc, x) => {
acc[x.id] = Object.assign(acc[x.id] || {}, x);
return acc;
}, {});
return Object.values(data);
But the issue is that my bData code props getting lost.
Is there any way I can rename the code from aData to say aCode and the code from bData to bCode ?
You can create a new array from both of your array with updated key value aCode and bCode instead of code key. Then concat both of these arrays and merge them on the id key.
const arrA = [{ id: 1, title: `title`, code: '34x' }],
arrB = [{ id: 1, prop: 3, otherporp: `prop`, code: 'hi67'}];
const newArrA = arrA.map(({code, ...rest}) => ({...rest, aCode : code}));
const newArrB = arrB.map(({code, ...rest}) => ({...rest, bCode : code}))
const merged = Object.values([].concat(newArrA, newArrB).reduce((r,o) => {
r[o.id] = r[o.id] || Object.assign({},o);
Object.assign(r[o.id], o);
return r;
}, {}));
console.log(merged);
var arrA = [{
id: 1,
title: `title`,
code: '34x'
}],
arrB = [{
id: 1,
prop: 3,
otherporp: `prop`,
code: 'hi67'
}];
let newArrA = arrA.map(({
code,
...rest
}) => ({ ...rest,
aCode: code
}));
const newArrB = arrB.map(({
code,
...rest
}) => ({ ...rest,
bCode: code
}));
result = newArrA.map(function(v) {
var ret;
$.each(newArrB, function(k, v2) {
if (v2.id === v.id) {
ret = $.extend({}, v2, v);
return false;
}
});
return ret;
});
console.log(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This is my data
[{name:'james',grade:'A'},
{name:'john',grade:'B'},
{name:'iris',,grade:'A'},
{name:'ivan',,grade:'C'}]
I want to keep object that has grade A and C, it's easy I can just do filter like
person.filter(obj => obj.grade === 'A' || obj.grade === 'C')
but now I have an array of object.
[{grade:'A'},{grade:'C'}]
any clue how can I do filtering now? do I need nested loop?
Use Array.prototype.some:
let person = [{name:'james', grade:'A'},
{name:'john', grade:'B'},
{name:'iris', grade:'A'},
{name:'ivan', grade:'C'}];
let predicate = [{grade:'A'},{grade:'C'}];
let result = person.filter(obj => predicate.some(p => p.grade == obj.grade))
console.log('result:', result)
If your predicate is more dynamic than that, compare all object properties instead of just p.grade.
person.filter(obj => predicate.some(p => {
return Object.keys(p).every(k => obj[k] == p[k]);
}));
Using underscore lib
eg -
var bbb = [
{id: 839},
{id: 854}
];
var ids = {};
_.each(bbb, function (bb) { ids[bb.id] = true; });
var data = [{grade:'A'},{grade:'C'}];
var value = {};
_.each(data , function (d) { value[data.garde] === 'A' | value[data.garde] === 'C' ; });
Objects having grades A and C should be filtered as (classic js syntax),
var a = [
{name:'james',grade:'A'},
{name:'john',grade:'B'},
{name:'iris',grade:'A'},
{name:'ivan',grade:'C'}
];
a.filter(function(e) {
return (e.grade == 'A') || (e.grade == 'C');
});
If you have many grades to check (I don't know like all of them ;) ). You could first convert array into Set
const grades = new Set([{grade:'A'},{grade:'C'}].map(({grade}) => grade))
const persons = [{name:'james',grade:'A'},
{name:'john',grade:'B'},
{name:'iris',grade:'A'},
{name:'ivan',grade:'C'}]
And then filter persons array using has
const filtered = persons.filter(({grade}) => grades.has(grade))
You could use a Set for the predicates and filter accordingly
let person = [{ name: 'james', grade: 'A' }, { name: 'john', grade: 'B' }, { name: 'iris', grade: 'A' }, { name: 'ivan', grade: 'C' }],
predicate = [{ grade: 'A' }, { grade: 'C' }],
result = person.filter((s => p => s.has(p.grade))(new Set(predicate.map(p => p.grade))));
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }