Reputation: 2844
I am trying to reduce an array of objects (config values in my case). My array looks like this:
const settings = [
{room: null, key: 'radioEnabled', value: true},
{room: 24, key: 'radioEnabled', value: false},
{room: 24, key: 'name', value: 'Jack'},
{room: 23, key: 'name', value: 'Mike'},
{room: 23, key: 'radioEnabled', value: false},
{room: null, key: 'tvEnabled', value: false},
];
This array is not ordered in any way.
If a room is set to null
, this means it is a global setting.
Global settings can be overwritten by local settings.
I am trying to write a function to get all settings for a room. For room 24 it should return:
[
{room: 24, key: 'radioEnabled', value: false},
{room: 24, key: 'name', value: 'Jack'},
{room: null, key: 'tvEnabled', value: false},
]
The order in which the values are returned is not important to me. I have been able to achieve this in more than one way, but the solutions just don't seem that elegant/readable to me. Can anybody suggest a more elegant idea?
My solutions are below and on jsfiddle.
const settings = [
{room: null, key: 'radioEnabled', value: true},
{room: 24, key: 'radioEnabled', value: false},
{room: 24, key: 'name', value: 'Jack'},
{room: 23, key: 'name', value: 'Mike'},
{room: 23, key: 'radioEnabled', value: false},
{room: null, key: 'tvEnabled', value: false},
];
const getAll_1 = function(room){
return settings.reduce( (a, b) => {
// remove all other rooms
if(b.room && b.room!== room){
return a;
}
// see if the setting is already added
let found = a.find( (setting) => {
return setting.key === b.key;
})
// we already have a local value in our return array, don't add/replace anything
if( found && found.room === room) {
return a;
}
// we have a value, but it's not local. Replace the current value with the new one
if( found ) {
const index = a.findIndex( (setting) => {
return setting.key === b.key;
})
a[index] = b;
return a;
}
// we don't have this setting at all. add it.
return a.concat(b);
}, []);
}
const getAll_2 = function(room){
return settings
// first filter out all other room settings, only keep current room and global values
.filter( (setting) => {
return setting.room === null || setting.room === room;
})
// than sort em so all local (high prio) values are up top
.sort( (a, b) => {
return (a.room > b.room) ? -1 : ( a.room < b.room ) ? 1 : 0;
})
// reduce the array, adding only global values if they are not already added as local value
.reduce( (a, b) => {
const found = a.find( (setting) => {
return setting.key === b.key;
})
if (found){
return a;
}
return a.concat(b);
}, [])
}
console.log(`Stack Overflow does not support console.table. Open your console for better representation`);
console.log(`getAll_1 response:`);
console.table(getAll_1(24));
console.log(`getAll_2 response:`);
console.table(getAll_2(24));
Check your console
Upvotes: 2
Views: 113
Reputation: 50787
Another approach, which might or might not help with your fundamental requirements, is to convert this to a more useful format:
const roomSettings = settings => {
const globals = settings.filter(s => s.room == null)
.reduce((all, {key, value}) => ({...all, [key]: value}), {})
return settings.filter(s => s.room != null)
.reduce((all, {room, key, value}) => ({
...all,
[room]: {...(all[room] || globals), [key]: value}
}), {} )
}
const settings = [{"key": "radioEnabled", "room": null, "value": true}, {"key": "radioEnabled", "room": 24, "value": false}, {"key": "name", "room": 24, "value": "Jack"}, {"key": "name", "room": 23, "value": "Mike"}, {"key": "radioEnabled", "room": 23, "value": false}, {"key": "tvEnabled", "room": null, "value": false}, {"key": "name", "room": 25, "value": "Beth"}]
console.log(roomSettings(settings))
Note that this returns something like the following:
{
23: {
radioEnabled: false,
tvEnabled: false,
name: "Mike"
},
24: {
radioEnabled: false,
tvEnabled: false,
name: "Jack"
},
25: {
radioEnabled: true,
tvEnabled: false,
name: "Beth"
}
}
(I added 'Beth' to have at least one that wasn't false
/false
.)
This format looks more useful, but it certainly might not be for you.
Upvotes: 2
Reputation: 17190
You could first get the specific
settings, and then add the general
ones if there isn't already a specific one for the key
:
const settings = [
{room: null, key: 'radioEnabled', value: true},
{room: 24, key: 'radioEnabled', value: false},
{room: 24, key: 'name', value: 'Jack'},
{room: 23, key: 'name', value: 'Mike'},
{room: 23, key: 'radioEnabled', value: false},
{room: null, key: 'tvEnabled', value: false},
];
const generalSettings = settings.filter(x => x.room === null);
const getSettings = (roomID) =>
{
let keysAdded = new Set();
// Get specific settings and add keys on the set.
let res = settings.filter(x => x.room == roomID && keysAdded.add(x.key));
// Add general settings.
return generalSettings.reduce(
(acc, curr) => keysAdded.has(curr.key) ? acc : [...acc, curr],
res
);
}
console.log(getSettings(23));
console.log(getSettings(24));
Upvotes: 1
Reputation: 3721
basically it is easier to use filter over your array, this filter function wrapped in a higher function to receive the room number as a parameter.
edit: forgot about the reduce to remove the duplicates.
const settings = [{
room: 24,
key: 'radioEnabled',
value: false
},
{
room: null,
key: 'radioEnabled',
value: true
},
{
room: 24,
key: 'name',
value: 'Jack'
},
{
room: 23,
key: 'name',
value: 'Mike'
},
{
room: 23,
key: 'radioEnabled',
value: false
},
{
room: null,
key: 'tvEnabled',
value: false
},
];
const getConfigs = (room = null, settings = []) => {
return settings
// filter all the options that match our criteria.
.filter(setting => setting.room === room || setting.room === null)
// using reduce we will remove the duplicated key entries.
.reduce((accum, currentVal) => {
// if the index is -1 it means it is not on the array, so we add it.
const index = accum.findIndex(accumSetting => accumSetting.key === currentVal.key)
if (index === -1) {
accum.push(currentVal);
} else { // it means that we have the entry. replace if we have a local one.
if(currentVal.room === room && accum[index].room === null){
accum[index] = currentVal;
}
}
return accum;
}, [])
}
const result24 = getConfigs(24, settings);
const result23 = getConfigs(23, settings);
console.log(result24);
console.log(result23);
Upvotes: 1
Reputation: 122027
You can do this with one reduce
loop if you store result in object based on key
field and then get its values to return an array as a result.
null
or target valuekey
property for that setting doesn't exist or if its room value is null
const settings = [{"room":null,"key":"radioEnabled","value":true},{"room":24,"key":"radioEnabled","value":false},{"room":24,"key":"name","value":"Jack"},{"room":23,"key":"name","value":"Mike"},{"room":23,"key":"radioEnabled","value":false},{"room":null,"key":"tvEnabled","value":false}]
const getAll = room => {
return Object.values(settings.reduce((r, e) => {
if (e.room == null || e.room == room) {
if (!r[e.key] || r[e.key].room == null) r[e.key] = e
}
return r;
}, {}))
}
console.log(getAll(24))
Upvotes: 0
Reputation: 350137
I would suggest:
room
is nullroom
is the desired room numberMap
to get only one entry per keyThe fact that the first two filter-results are concatenated in that order will ensure that null
room entries get lower priority than non-null entries.
function getAll(room){
return [...new Map([...settings.filter(a => a.room === null),
...settings.filter(a => a.room === room)]
.map(a => [a.key, a])).values()]
}
const settings = [{room: null, key: 'radioEnabled', value: true},{room: 24, key: 'radioEnabled', value: false},{room: 24, key: 'name', value: 'Jack'},{room: 23, key: 'name', value: 'Mike'},{room: 23, key: 'radioEnabled', value: false},{room: null, key: 'tvEnabled', value: false}];
console.log(getAll(24));
Upvotes: 1
Reputation: 135197
Here's a possible alternative -
const globals =
{ radioEnabled: true
, tvEnabled: false
}
const settings =
[ { room: 24, name: 'Jack' }
, { room: 24, radioEnabled: false }
, { room: 25, name: 'Mike' }
, { room: 25, tvEnabled: true }
]
const assign = (o1, o2) =>
({ ...o1, ...o2 })
const getConfig = (room, settings = []) =>
settings
.filter (s => s.room === room)
.reduce (assign, globals)
console .log
( getConfig (24, settings)
// { radioEnabled: false
// , tvEnabled: false
// , room: 24
// , name: 'Jack'
// }
, getConfig (25, settings)
// { radioEnabled: true
// , tvEnabled: true
// , room: 25
// , name: 'Mike'
// }
)
Upvotes: 1