I need to filter an array by property:
Here's the data, which I get from the server:
const mockResults = [
{
user: {
firstName: '1',
lastName: '1'
},
status: 'WRONG'
},
{
user: {
firstName: '2',
lastName: '2'
},
status: 'WRONG'
},
{
user: {
firstName: '3',
lastName: '3'
},
status: 'CORRECT'
}
];
To display the data, I need to transform it to a required by ReactNative SectionList format:
const requiredFormat = [
{
status: 'WRONG',
data: [{ user: {firstName: '1', lastName: '1'}}, { user: {firstName: '2', lastName: '2'}}],
},
{
status: 'CORRECT',
data: [{ user: {firstName: '3', lastName: '3'}}],
},
];
Basically, the mockResults should be sorted by status. There can be maximum of 4 statuses: correct, wrong, missed, chosen. All these statuses should include all the data marked with them.
What is the right way to implement this?
I've tried to filter the array, but I'm stuck at this point:
const transformArray = mockResults.filter(item => {
return {
answerStatus: item.status,
data: [item.user]
}
})
You may walk through the array (using Array.prototype.reduce() method) and create new element of resulting array once you see there's no such with current status or append current element data if one exists:
const mockResults = [{user:{firstName:'1',lastName:'1'},status:'WRONG'},{user:{firstName:'2',lastName:'2'},status:'WRONG'},{user:{firstName:'3',lastName:'3'},status:'CORRECT'}],
result = mockResults.reduce((r,{status, ...rest}) => {
const common = r.find(e => e.status == status)
common ?
common.data.push(rest) :
r.push({status, data:[rest]})
return r
}, [])
console.log(result)
.as-console-wrapper {min-height:100%}
You can use reduce() to achieve that:
const mockResults = [{user: { firstName: '1', lastName: '1'}, status: 'WRONG'},{user: {firstName: '2',lastName: '2'},status: 'WRONG'},{user: { firstName: '3',lastName: '3'},status: 'CORRECT'}];
const result = mockResults.reduce((a, {user, status}) => {
const temp = a.find(e => e.status === status);
if (temp) {
temp.data.push({user});
} else {
a.push({status, data: [{user}]});
}
return a;
}, []);
console.log(result);
Read from the Array.prototype.reduce() documentation:
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
I hope that helps!
You can reduce the array onto an object with its keys being the status property and its value an array of users with that status. Then, map over the entries to turn it back into an array of objects.
Note: The reduce function is creating a new object (accumulator) at each index. This is probably not practical for large data sets as it would be really slow.
const toSectionList = results =>
Object.entries(
results.reduce(
(obj, { user, status }) => ({
...obj,
[status]: [...(obj[status] || []), { user }],
}),
{}
)
).map(([status, data]) => ({ status, data }))
// example use:
const requiredFormat = toSectionList(mockResults)
The reduce function in this one modifies the accumulator object instead of creating a new one. It should preform better with larger data sets.
const toSectionList = results =>
Object.entries(
results.reduce((obj, { user, status }) => {
obj[status] = obj[status] || []
obj[status].push({ user })
return obj
}, {})
).map(([status, data]) => ({ status, data }))
That's what I did.
const mockResults = [
{
user: {
firstName: '1',
lastName: '1'
},
status: 'WRONG'
},
{
user: {
firstName: '2',
lastName: '2'
},
status: 'WRONG'
},
{
user: {
firstName: '3',
lastName: '3'
},
status: 'CORRECT'
}
]
function format(data) {
const resultDict = {}
for (let i of data) {
if (!resultDict[i.status]) {
resultDict[i.status] = { data: [] }
}
resultDict[i.status].data.push(i)
delete resultDict[i.status].data[resultDict[i.status].data.length - 1].status
}
const result = []
for (let i in resultDict) {
const res = {
status: i,
data: resultDict[i].data
}
result.push(res)
}
return result
}
console.log(format(mockResults))
Related
I have an array of objects and each object has an id. I have another array with id values. If an objects id is found in arrayOfIds, I would like to add new property called found and set to true. I was thinking of using findIndex as a possible function to use here.
const arrayOfObjects = [ { id: '123' }, { id: '456' }, { id: '789' } ]
const arrayOfIds = ['456']
Expected Output
const arrayOfObjects = [ { id: '123' }, { id: '456', found: true }, { id: '789' } ]
const arrayOfObjects = [ { id: '123' }, { id: '456' }, { id: '789' } ]
const arrayOfIds = ['456']
arrayOfObjects.map((object) => {
if(arrayOfIds.includes(object.id)) {
object.found = true;
}
})
console.log(arrayOfObjects);
I think it would make sense to convert your arrayOfIds to a Set to allow O(1) lookup.
const ids = new Set(arrayOfIds);
const augmentedObjects = arrayOfObjects.map(obj => {
if (ids.has(obj.id)) {
obj.found = true;
}
return obj;
});
You can create a Set from the array and use Array#map along with Set#has.
const arrayOfObjects = [ { id: '123' }, { id: '456' }, { id: '789' } ]
const ids = new Set(['456']);
const res = arrayOfObjects.map(o => ({...o, ...ids.has(o.id) && {found: true}}));
console.log(res);
You can map through the objects and spread the object with the found property.
const output = arrayOfObjects.map((obj) => {
if (arrayOfIds.includes(obj.id)) {
return { ...obj, found: true };
}
return obj;
});
You could do it by creating a lookup object. Create the lookup object from arrayOfIds and use that lookup object when traversing arrayOfObjects array and make certain changes.
const arrayOfObjects = [{ id: '123' }, { id: '456' }, { id: '789' }];
const arrayOfIds = ['456'];
const lookup = Object.fromEntries(arrayOfIds.map((x) => [x, { found: true }]));
const ret = arrayOfObjects.map((x) => ({ ...x, ...lookup[x.id] }));
console.log(ret);
Just run a map over the array of objects, and use the includes to add show: true by spreading the existing object else return the original object.
You could try something like this
const arrayOfObjects = [{ id: "123" }, { id: "456" }, { id: "789" }];
const arrayOfIds = ["456"];
const result = arrayOfObjects.map((item) => {
if (arrayOfIds.includes(item.id)) {
return {
...item,
found: true
};
}
return item;
});
console.log(result);
const personData = {
personID: 1234567,
personList: [{
name: ['name1', 'name2'],
address: false,
pin: 12345
},
{
name: ['name1', 'name2', 'name3'],
address: true,
pin: 45678
},
{
name: ['name1'],
address: false,
pin: 12345
},
]
};
const keys = ['name', 'address', 'pin']
const values = ['name1', 'name2', false, 12345]
let personDataArr = personData.personList.filter(function(e) {
return keys.every(function(a) {
return e[a] &&
e[a].length > 0 ? values.every(x => e[a].includes(x)) : values.includes(e[a])
});
});
console.log(personDataArr);
The code shows the person Data object which includes the information like name, address and pin.
I tried using filter but I am not getting a properly filtered result.
I am executing the code I am getting null, but I am expecting the first person List object.
Could some one take a look into the code and modify if possible and send me the expected result as output.
I will suggest you to have your key value as an object instead of two different arrays
const {personList} = {personID: 1234567,personList: [{name: ['name1', 'name2'],address: false,pin: 12345},{name: ['name1', 'name2', 'name3'],address: true,pin: 45678},{name: ['name1'],address: false,pin: 12345},]};
const filterBy = { name:['name1', 'name2'],address: false,pin:12345 }
let personDataArr = personList.filter(e => {
return Object.keys(e).every(key => {
return Array.isArray(e[key]) ? filterBy[key].every(value=> e[key].includes(value))
: e[key] === filterBy[key]
})
})
console.log(personDataArr);
It will get really hard to reliably filter with the filter criteria you've provided. How should the code determine which of the values are needed for every given key? Not possible.
It'll be better to provide the values in a well structured way, such as
const filterCriteria = [
{
key: 'name',
values: ['name1', 'name2']
}, {
key: 'address',
value: false
}, {
key: 'pin',
value: 12345
}
];
const getPerson = (list, criterias) =>
list.filter(element =>
criterias.every(criteria =>
element.hasOwnProperty(criteria.key) &&
criteria.hasOwnProperty('value') ?
element[criteria.key] === criteria.value :
(criteria.values.every(value => element[criteria.key].includes(value))) && element[criteria.key].every(value => criteria.values.includes(value)))
);
console.log(getPerson(personData.personList, filterCriteria));
<script>
const personData = {
personID: 1234567,
personList: [
{
name: ['name1', 'name2'],
address: false,
pin: 12345
},
{
name: ['name1', 'name2', 'name3'],
address: true,
pin: 45678
},
{
name: ['name1'],
address: false,
pin: 12345
},
]
};
const filterCriteria = [
{
key: 'name',
values: ['name1', 'name2']
}, {
key: 'address',
value: false
}, {
key: 'pin',
value: 12345
}
];
</script>
this is what my response looks like
data: [
{
id: 3,
name: "Oliver Green",
email: "test#gmail.com",
contacts: "09179878564"
},
{
id: 2,
name: "Orval McLaughlin",
email: "okoch#example.org",
contacts: "09083692343",
}
]
I used the map function to get the user id and user name, now what I'm trying to do is to save all of the result to an Object
data(){
return {
autoComplete:{},
}
},
let vm = this;
response.data.data.map((user) =>
{
return vm.autoComplete = { [user.id] : user.name};
});
I get the result however I'm only getting one result
autoComplete:Object
2:"Orval McLaughlin"
the result should be
autoComplete:Object
3: "Oliver Green"
2: "Orval McLaughlin"
You need to return the object from map() not the result of an assignment. You are currently assigning vm.autoComplete on each iteration. After you do this you can assign the output of map to the variable you want:
let data = [
{
id: 3,
name: "Oliver Green",
email: "test#gmail.com",
contacts: "09179878564"
},
{
id: 2,
name: "Orval McLaughlin",
email: "okoch#example.org",
contacts: "09083692343",
}
]
let autoComplete = data.map((user) => {
return { [user.id] : user.name};
});
console.log(autoComplete)
EDIT:
If you want an object instead of an array, you should use reduce() because map() always returns an array:
let data = [
{
id: 3,
name: "Oliver Green",
email: "test#gmail.com",
contacts: "09179878564"
},
{
id: 2,
name: "Orval McLaughlin",
email: "okoch#example.org",
contacts: "09083692343",
}
]
let autoComplete = data.reduce((obj, user) =>{
obj[user.id] = user.name; // this assumes user.id will be unique
return obj
}, {});
console.log(autoComplete)
Try this for not wrapping it in array.
response.data.data.map((user) => {
return vm.autoComplete = Object.assign(vm.autoComplete, {[user.id] : user.name}); }
It seems the autoComplete be overwrite for each.
Maybe you can try these:
data(){
return {
autoComplete:{},
}
},
let vm = this;
vm.autoComplete = response.data.data.map((user) => {
return { [user.id] : user.name};
});
I have two array like below
const registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
const dates = ['20171225', '20180914'];
and I want they comebine like this
const result = [
{ user: 'Allen', type: 'phone', date: '20171225' }
{ user: 'Allen', type: 'phone', date: '20180914' }
{ user: 'Eric', type: 'email', date: '20171225' }
{ user: 'Eric', type: 'email', date: '20180914' }
];
I think there is an proper function of Lodash to use in this case,
but I can't find it by myself.
Is there any cool geek can help to get out of this hell. Thanks~
I agree to use of plugins instead of rewrite what people have made available. But when It's simple, using a plugin is complicating things, it's better to use the simplier soluce when there is one.
const registers = [{
user: 'Allen',
type: 'phone',
}, {
user: 'Eric',
type: 'email',
}];
const dates = [
'20171225',
'20180914',
];
const arr = registers.reduce((tmp, x) => [
...tmp,
...dates.map(y => ({
...x,
date: y,
})),
], []);
console.log(arr);
One method is with reduce + forEach, but any double loop should work.
const registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
const dates = ['20171225', '20180914'];
let answer = registers.reduce((acc, n) => {
dates.forEach(x => {
acc.push({user: n.user, type: n.type, date: x});
});
return acc;
}, [])
console.log(answer);
In this case, use of lodash doesn't yield much code reduction (you could simply replace the calls to _.map(a, ...) with a.map(...)). But well, here goes:
const registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
const dates = ['20171225', '20180914'];
const result = _.map(registers, ({ user, type }) => _.map(dates, (date) => ({ user, type, date })));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.core.js"></script>
As OP stated to use lodash , Here is a solution
Lodash Variant
var registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
var dates = ['20171225', '20180914'];
let result = _.map(registers, function(value, index, _source){
return _.extend(value, { date: (dates[index] || null)});
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.core.min.js"></script>
Vanilla JS Variant
var registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
var dates = ['20171225', '20180914'];
let result = registers.map(function(value, index, _source){
return Object.assign(value, { date: (dates[index] || null)});
});
console.log(result);
Updated Answer
var registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
var dates = ['20171225', '20180914'];
const result = _.flatten(_.map(registers, function(value, index, _source){
return _.map(dates, (v, i, _s) => _.extend(value, { date: v}));
}));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.core.js"></script>
i have nested object users
let users = {
'0LsRDanmw1s1KBvZAAAC':
{
name: 'user1',
room: 'Lobby'
},
'Qvd9v0EStnwNe11mAAAD':
{
name: 'user2',
room: 'Music'
},
};
I want to get an array of users where room === 'Lobby'
let onlineUsers = ['user1'];
Thanks in advance
edit:
alot of good solutions thank you guys!
Get values, execute .filter() and then .map().
let users = { '0LsRDanmw1s1KBvZAAAC': { name: 'user1', room: 'Lobby' }, 'Qvd9v0EStnwNe11mAAAD': { name: 'user2', room: 'Music' },};
var onlineUsers = Object.values(users)
.filter(u => u.room === 'Lobby')
.map(u => u.name);
console.log(onlineUsers );
.as-console-wrapper { max-height: 100% !important; top: 0; }
Or you can get values and execute .reduce().
let users = { '0LsRDanmw1s1KBvZAAAC': { name: 'user1', room: 'Lobby' }, 'Qvd9v0EStnwNe11mAAAD': { name: 'user2', room: 'Music' },};
var onlineUsers = Object.values(users)
.reduce((a, u) => (u.room === 'Lobby' ? [...a, u.name] : a), []);
console.log(onlineUsers);
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}
You can take the values of the array, filter as desired and map only the name.
var users = { '0LsRDanmw1s1KBvZAAAC': { name: 'user1', room: 'Lobby' }, 'Qvd9v0EStnwNe11mAAAD': { name: 'user2', room: 'Music' } },
result = Object
.values(users)
.filter(({ room }) => room === 'Lobby')
.map(({ name }) => name);
console.log(result);
Combination of Object.values, filter and map:
let users = {'0LsRDanmw1s1KBvZAAAC': { name: 'user1',room: 'Lobby' },'Qvd9v0EStnwNe11mAAAD':{ name: 'user2',room: 'Music' },};
const res = Object.values(users)
.filter( o => o.room === 'Lobby')
.map( o => o.name );
console.log(res);
Use ES8 Object.values() to get all the values from the object, then apply .filter and .map
let users = {
'0LsRDanmw1s1KBvZAAAC':
{
name: 'user1',
room: 'Lobby'
},
'Qvd9v0EStnwNe11mAAAD':
{
name: 'user2',
room: 'Music'
},
};
var onlineUsers = Object.values(users).filter(el => el.room==="Lobby").map(el => el.name);
console.log(onlineUsers);
I still like the old school, old-browser-safe, version of a simple for loop, so for the sake of completeness:
var users = {
'0LsRDanmw1s1KBvZAAAC':
{
name: 'user1',
room: 'Lobby'
},
'Qvd9v0EStnwNe11mAAAD':
{
name: 'user2',
room: 'Music'
},
};
var lobbyUsers = [];
for (var prop in users) {
if (users.hasOwnProperty(prop)) {
if (users[prop].room === 'Lobby') {
lobbyUsers.push(users[prop].name);
}
}
}
console.log(lobbyUsers);