I am trying to incorporate array method: reduce.
Basically, what I am trying to accomplish here is to reduce the array below to an object where anything that matches obj's key value.
const arr = ['a', 'c', 'e'];
const obj = { a: 1, b: 2, c: 3, d: 4 };
let output = select(arr, obj);
console.log(output); // --> { a: 1, c: 3 }
My select method:
function select(arr, obj) {
let newObj = {};
for (let prop in obj) {
for (let i = 0; i < arr.length; i++) {
if (prop === arr[i]) {
newObj[prop] = obj[prop];
}
}
}
return newObj;
}
I set {} as initializer for arr.reduce as such if current value of array matches key of object then it would add to accumulator the key value, but I am receiving an error message from the console that if expression cannot return boolean.
Here is my attempt using .reduce():
function select(arr, obj) {
let result = arr.reduce(function(x, y) {
if (y in obj) {
x[y] = obj[y]
return x;
}
}, {})
return result;
}
Please advise.
You must always return the accumulator. Here is how to use reduce
function select(arr, obj) {
return arr.reduce(function (acc, key) {
if (key in obj) acc[key] = obj[key];
return acc;
}, {});
}
const arr = ['a', 'c', 'e'];
const obj = { a: 1, b: 2, c: 3, d: 4 };
let output = select(arr, obj);
console.log(output); // --> { a: 1, c: 3 }
The accumulator should be returned in all the cases.
I used an implementation using a filter for your reference:
const arr = ['a', 'c', 'e'];
const obj = { a: 1, b: 2, c: 3, d: 4 };
function select (obj,arr){
let newObj = Object.keys(obj).filter(key => arr.includes(key)).reduce((acc,key) => {
acc[key]=obj[key]
return acc
},{})
return newObj
}
console.log(select(obj,arr));
function select(arr, obj) {
return arr.reduce((acc, curr) => {
if(obj[curr]) {
acc[curr] = obj[curr];
}
return acc;
}, {})
}
Related
Sorry, in advance if the title is unclear, but it's hard to describe it in a words.
What I have:
const obj = {
a: 5,
b: 3,
c: 0,
d: 9
}
What I want to have:
const arr = [[a, 5] ,[b, 3]]
Basically, I try to write a function that return me array of entries, but it has too meet requirements:
don't want objects when values is equal to 0
sum of values must be less than 10
First point is easy for me and I can do it by
Object.entries(obj).filter(([k, v])=> v !== 0)
but I can't handle with the second one.
May I use reduce here?
You can use a closure and an IIFE to store the sum
Object.entries(obj).filter((() => {
let sum = 0;
return ([k, v]) => { sum += v; return v !== 0 && sum < 10; };
})());
Examples:
function convert(obj) {
return Object.entries(obj).filter((() => {
let sum = 0;
return ([k, v]) => { sum += v; return v !== 0 && sum < 10; };
})());
}
const obj = { a: 5, b: 3, c: 0, d: 9 };
const arr = convert(obj);
console.log(arr);
const obj2 = { a: 0, b: 0, c: 8, d: 0, e: 1, f: 5 };
const arr2 = convert(obj2);
console.log(arr2);
const obj3 = { a: 12 };
const arr3 = convert(obj3);
console.log(arr3);
#jabaa's answer is great and you should accept it.
Just to confirm your intuition, you could have used reduce, but it would get rather complicated:
const obj = {
a: 5,
b: 3,
c: 0,
d: 9
}
const result = Object.entries(obj).reduce(
(o, newPair) => {
o.sum += newPair[1];
newPair[1] !== 0 && o.sum < 10 && o.pairs.push(newPair);
return o;
},
{
sum: 0,
pairs: []
}
).pairs;
console.log(result)
I have an object where a few of the keys are nested single level hashes. In this example only b is nested.
const j = {
a: 'A',
b: {
bb: 'BB',
bbb: 'BBB',
},
c: 'C'
};
Question
What I am looking for is a way to loop over the object and if a key is a nested object, then print its keys instead.
a
bb
bbb
c
Does anyone know how to do that?
You can do this recursively:
function printKeys(obj) {
for (const [key, val] of Object.entries(obj)) {
if (typeof val === "object") {
printKeys(val);
} else {
console.log(key);
}
}
}
If you only have one level of nesting at most, #blex's answer is probably the better one.
You could do it with Object.entries and flatMap:
const j = { a: 'A', b: { bb: 'BB', bbb: 'BBB' }, c: 'C' };
function getOneLevelKeys(obj) {
return Object.entries(obj)
.flatMap(([key, value]) => typeof value === "object" ? Object.keys(value) : key);
}
console.log( getOneLevelKeys(j) );
You can use a recursive flatMap with Object.keys.
const j = {
a: 'A',
b: {
bb: 'BB',
bbb: 'BBB',
},
c: 'C'
};
const getKeys = o => Object.keys(o).flatMap(x => o[x] === Object(o[x])
? getKeys(o[x]) : x);
console.log(getKeys(j));
I have an object where it can contain a duplicate and/or a falsy value. I want to compose an array of objects based on that and add a new boolean property based on the check for case-insensitive values.
This is what I have:
const obj = {
a: 'A',
b: 'B',
c: 'C',
d: 'c',
e: 'E',
f: ''
}
console.log(Object.keys(obj).map(i => {
return {
key: i,
isDuplicateOrFalsy: _.filter(
Object.values(obj),
j =>
_.trimEnd(_.toLower(j)) ===
_.trimEnd(
_.toLower(
obj[i]
)
)
).length > 1 ||
!_.every(
Object.values(obj),
Boolean
)
}
}))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
Expected Output:
[{
isDuplicateOrFalsy: false,
key: "a"
}, {
isDuplicateOrFalsy: false,
key: "b"
}, {
isDuplicateOrFalsy: true,
key: "c"
}, {
isDuplicateOrFalsy: true,
key: "d"
}, {
isDuplicateOrFalsy: false,
key: "e"
}, {
isDuplicateOrFalsy: true,
key: "f"
}]
Please advice.
Convert the object to entries of [key, value] with _.toPairs(), and group them by the lower case version of the value. Flat map the groups, and map each entry in the group back to an object. Any item within a group with length greater than 1 is a duplicate. Merge the objects, and get the items in the correct order using _.at():
const fn = obj => _.at(
_.merge(..._.flatMap(
_.groupBy(_.toPairs(obj), ([, v]) => _.lowerCase(v)),
group => group.map(([key, v]) => ( { [key]:{
key,
isDuplicateOrFalsy: group.length > 1 || _.isEmpty(_.trim(v))
}}))
)),
_.keys(obj)
)
const obj = {"a":"A","b":"B","c":"C","d":"C","e":"E","f":"","g":"c"}
const result = fn(obj)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
You could do something similar to this:
const obj = { a: 'A', b: 'B', c: 'C', d: 'C', e: 'E', f: '' };
const res = Object.entries(obj)
.map(([key, val], i, arr) => ({
key,
isDuplicateOrFalsy: !val ||
arr.some(([k, v], j) =>
j !== i && v.toLowerCase().trim() === val.toLowerCase().trim()
)
}));
console.log(res);
Solution does not contain unnecessary cycles:
const obj = {
a: 'A',
b: 'B',
c: 'C',
d: 'C',
e: 'E',
f: ''
}
// make array [ [key, value], ... ] and sort by values
values = Object.entries(obj).sort((a,b) => a[1] > b[1])
result = values.map((e,i, arr) => {
const [key, value] = e;
const last = values.length -1; // last index
let isDuplicateOrFalsy = false;
// true conditions = dublicates are near
if (!value) isDuplicateOrFalsy = true; // falsy check
else if (i > 0 && i < last // for middle
&& (arr[i-1][1] === value || arr[i+1][1] === value)) isDuplicateOrFalsy = true;
else if (i === 0 && arr[1][1] === value) isDuplicateOrFalsy = true; // for first
else if (i === last && arr[last-1][1] === value) isDuplicateOrFalsy = true; // for last
return {
key,
isDuplicateOrFalsy
}
})
console.log(result)
const obj = {
a: 'A',
b: 'B',
c: 'C',
d: 'c',
e: 'E',
f: ''
};
const isDuplicateOrFalsyByValue = Object
.values(obj)
.reduce(
(result, value) => {
const caseInsensetiveValue = value.toLowerCase();
result[caseInsensetiveValue] = result[caseInsensetiveValue] === undefined
/*
* If `caseInsensetiveValue` is a falsy value,
then set `isDuplicateOrFalsy` to `true`
* Otherwise set it to `false`
*/
? !caseInsensetiveValue
/*
* If result[caseInsensetiveValue] is `true` (we had a falsy value),
then this `true` won't hurt
* Otherwise we have a duplicate at this point
and should set it to `true` as well.
*/
: true;
return result;
},
{},
);
const keysWithDuplicationOrFalsyInfo = Object
.entries(obj)
.reduce(
(result, [key, value]) => [
...result,
{
isDuplicateOrFalsy: isDuplicateOrFalsyByValue[value.toLowerCase()],
key,
},
],
[],
);
console.log('keysWithDuplicationOrFalsyInfo');
console.log(keysWithDuplicationOrFalsyInfo);
A short, and more human readable.
const obj = {
a: 'A',
b: 'B',
c: 'C',
d: 'c',
e: 'E',
f: ''
}
// Object map number of occurance of each value. { a: 1, b: 1, c: 2, d: 1 }
const valuesOccurance = _.mapValues(_.groupBy(obj, _.lowerCase), occurances => occurances.length);
// function to check duplicate
const isDuplicate = value => valuesOccurance[_.lowerCase(value)] > 1;
// function to check falsy value
const isFalsy = value => !value;
const result = _.map(obj, (value, key) => {
return {
isDuplicateOrFalsy: isFalsy(value) || isDuplicate(value),
key,
};
});
console.log({ result })
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
I have a following object:
{
a: {
b: {
c: undefined
}
},
b: {
c: 15,
d: []
},
c: {
d: [11, undefined ,12],
e: {}
}
}
And i need to get this:
{
b: {
c: 15
},
c: {
d: [11, 12]
}
}
I found this function (source: Remove undefined properties from object )
function filter(obj) {
for (var key in obj) {
if (obj[key] === undefined) {
delete obj[key];
continue;
}
if (obj[key] && typeof obj[key] === "object") {
filter(obj[key]);
if (!Object.keys(obj[key]).length) {
delete obj[key];
}
}
}
return obj;
}
But it just delete elements of array and it turns out the following
{
b: {
c: 15
},
c: {
d: [11, empty ,12]
}
}
You need a recursive solution. Make a function that takes a value and returns something falsey if its value is falsey, or if all of its recursive elements are falsey or empty arrays or without keys:
const removeRecursive = (obj) => {
// Falsey primitive, including null:
if (!obj) return;
// Truthy primitive (or function):
if (typeof obj !== 'object') return obj;
// Array, transform all values and return the new array
// if there are any truthy transformed values:
if (Array.isArray(obj)) {
const newArr = obj.map(removeRecursive).filter(Boolean);
return newArr.length ? newArr : undefined;
}
// Otherwise, it's an object:
const newObj = Object.fromEntries(
Object.entries(obj)
.map(([key, val]) => ([key, removeRecursive(val)]))
.filter(([, val]) => val)
);
if (Object.keys(newObj).length) {
return newObj;
}
};
const obj = {
a: {
b: {
c: undefined
}
},
b: {
c: 15,
d: []
},
c: {
d: [11, undefined ,12],
e: {}
}
};
console.log(removeRecursive(obj));
Here is the object
{
a: 1,
b: {
c: {
d: 2
},
e: 3
}
}
Here is the map
{
'a': 'aaa',
'b': 'bbb',
'b.c.d': 'bcd'
}
Here is the expected result.
{
aaa: 1,
bbb: {
c: {
bcd: 2
},
e: 3
}
}
I know there's a function in lodash _.get could get the value like b.c.d.
But how can I change the key name with the map?
You can do this recursively by keeping track of the current path and building a key into the map with that:
let o = {a: 1,b: {c: {d: 2},e: 3}}
let map = {
'a': 'aaa',
'b': 'bbb',
'b.c.d': 'bcd'
}
function makeObj(obj, map, p=[]) {
let ret = {}
Object.entries(obj).forEach(([k, v]) => {
let path = p.concat(k) // add onto current path
let mapKey = map[path.join('.')] || k
ret[mapKey] = (typeof v === 'object')
? makeObj(v, map, path) // if an object recurse and pass on the current path
: v // otherwise set the value
})
return ret
}
console.log(makeObj(o, map))