Compare two objects with specified keys in TS - javascript

I am trying to compare two objects but only with three keys instead of all keys. It looks like this:
Object.keys(StateA)
.filter((k) => [k == 'name', 'x', 'y'])
.every((k) => StateA[k] == StateB[k])
)
I am getting false results; what I am doing wrong?

There's no need to filter keys if you already have/know the ones to compare beforehand; use every just as you are:
const A = { a: 0, b: 1, c: 2, x: 3, y: 4, z: 5 };
const B = { a: 0, b: 2, c: 3, x: 3, y: 4, z: 6 };
const match = ['a', 'x', 'y'].every(key => A[key] === B[key]);
console.log(match);
Keep in mind a simple check like this may give you false positives - it depends on what you want. One case would be a key missing in one object but present in the other set to undefined. If that's the case, you may want to also check for the presence of the key:
const A = { a: 0, b: 1, c: undefined };
const B = { a: 0, b: 1 };
const keys = ['a', 'b', 'c'];
const match = keys.every(key => A[key] === B[key]);
console.log(match); // true
const stricterMatch = keys.every(key => (
key in A && key in B && A[key] === B[key]
));
console.log(stricterMatch); // false

export function hasSameProps (source: Record<string,unknown>, target: Record<string,unknown>) {
Object.keys(source).every(key => target.hasOwnProperty(key));
or
for (const key in ObjA) {
const current = ObjB[key];
if (!current) {
// does not exists
}
}

Related

How to make array of object with limmited values

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)

What is wrong in this sorting array skipping some elements?

I have array of objects. I want sort it leave some elements in the same position (with b="NOT")
var a=[{a:1,b:"YES"},{a:2,b:"YES"},{a:5,b:"NOT"},{a:0,b:"NOT"},{a:0,b:"YES"}]
function sortc(x,y){
if (x.b=="NOT" || y.b=="NOT")
return Infinity ;
return (Number(x.a)-Number(y.a))
}
console.log(a.sort(sortc));
the result is :
0: {a: 1, b: "YES"}
1: {a: 2, b: "YES"}
2: {a: 5, b: "NOT"}
3: {a: 0, b: "NOT"}
4: {a: 0, b: "YES"}
The expected result was ( with sort components with b="YES".) :
{ "a": 0, "b": "YES" }
{ "a": 1, "b": "YES" }
{ "a": 5, "b": "NOT" }
{ "a": 0, "b": "NOT" }
{ "a": 2, "b": "YES" }
You cannot only sort some items using the Array#sort() method - you either sort all or none. You don't define the position of the items, either - you only have to define their relationship to other items and the sorting algorithm will take care of the rest.
What you can do as a workaround is
Extract all items that should be sorted.
Sort them.
Go over the original array and only replace anything that should be sorted, leave the rest of the items in their place.
var a = [
{ a: 1, b: "YES" },
{ a: 2, b: "YES" },
{ a: 5, b: "NOT" },
{ a: 0, b: "NOT" },
{ a: 0, b: "YES" }
]
//get only `b: "YES"` items
const dataToSort = a.filter(item => item.b === "YES");
//sort them
dataToSort.sort((x, y) => x.a - y.a);
//replace only items that need to be sorted
const it = dataToSort.values()
for (let i = 0; i < a.length; i++) {
if (a[i].b === "NOT")
continue;
a[i] = it.next().value;
}
console.log(a);
For the record, the final loop can just be replaced with even shorter with more iterator usage, although it might be slightly more confusing:
const it = dataToSort.values()
for (const [key, item] of a.entries()) { //use the key-value iterator from the array
if (item.b === "NOT")
continue;
[a[key]] = it; //array destructuring internally advances an iterator
}
var a = [
{ a: 1, b: "YES" },
{ a: 2, b: "YES" },
{ a: 5, b: "NOT" },
{ a: 0, b: "NOT" },
{ a: 0, b: "YES" }
]
//get only `b: "YES"` items
const dataToSort = a.filter(item => item.b === "YES");
//sort them
dataToSort.sort((x, y) => x.a - y.a);
//replace only items that need to be sorted
const it = dataToSort.values()
for (const [key, item] of a.entries()) {
if (item.b === "NOT")
continue;
[a[key]] = it;
}
console.log(a);
Finally, this can be made somewhat more convenient with helper generator function and few small utility functions
/* library code */
const transformArg = transform => f => (...args) => f(transform(...args));
function* filter(predicate, it) {
for (const item of it) {
if (predicate(item))
yield item;
}
}
/* /library code */
var a = [
{ a: 1, b: "YES" },
{ a: 2, b: "YES" },
{ a: 5, b: "NOT" },
{ a: 0, b: "NOT" },
{ a: 0, b: "YES" }
]
/* helpers */
//extract the `b` key in this case so we don't need to repeat it.
const getSortableAttribute = transformArg(({b}) => b);
//get the value from key-value pair
const getValue = transformArg(([, value]) => value);
//check if the attribute is "YES"
const isSortable = getSortableAttribute(attr => attr === "YES");
const dataToSort = a.filter(isSortable);
dataToSort.sort((x, y) => x.a - y.a);
const it = dataToSort.values()
//iterate only over sortable key-value pairs by re-using the `isSortable` filter
for (const [key, item] of filter(getValue(isSortable), a.entries())) {
[a[key]] = it;
}
console.log(a);
This is an approach by using sort directly, but shaping the access with a Proxy for length and the indices.
const
sortOnly = (array, indices) => new Proxy(array, {
get (target, prop) {
if (isFinite(prop)) return target[indices[prop]];
if (prop === 'length') return indices.length;
return target[prop];
},
set (target, prop, receiver) {
target[indices[prop]] = receiver;
return true;
}
}),
array = [{ a: 1, b: "YES" }, { a: 2, b: "YES" }, { a: 5, b: "NOT" }, { a: 0, b: "NOT" }, { a: 0, b: "YES" }];
sortOnly(array, [...array.keys()].filter(i => array[i].b !== 'NOT'))
.sort((a, b) => a.a - b.a)
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Find duplicate or falsy value in a JS object - Javascript

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>

Sum of certain values of object

I have to calculate a sum of certain object values ( not all )
I have this object :
let object = {a: 1, b: 4, c: 2, d: 3, e: 10}
I need to sum just the a, c, d, e values.
Actually I use this method which sums all the values and gives me 20, but I need to have 16.
Object.keys(object).reduce((sum, key) => sum + parseFloat(object[key] || 0), 0)
How can I do this sum ?
Your sum function is good as it is, you just need to apply a filter
Object.keys(object)
.filter(key => key !== 'b')
.reduce((sum, key) => sum + parseFloat(object[key] || 0), 0)
Or, if you want a whitelist
const validKeys = {
a: true,
b: false, // optional
c: true,
d: true,
e: true
}
Object.keys(object)
.filter(key => validKeys[key])
.reduce((sum, key) => sum + parseFloat(object[key] || 0), 0)
To follow what you originally did, You should have an array of the keys and check to see if it is included before you add it.
const myObject = {a: 1, b: 4, c: 2, d: 3, e: 10}
const keys = ['a', 'c','d', 'e']
const entries = Object.entries(myObject)
const result = entries.reduce( (total, [key, value]) => (keys.includes(key) ? value : 0) + total, 0)
console.log(result)
smarter way is to loop over the keys
const myObject = {a: 1, b: 4, c: 2, d: 3, e: 10}
const keys = ['a', 'c','d', 'e']
const result = keys.reduce( (total, key) => (myObject[key] || 0) + total, 0)
console.log(result)
I'll add my two cents to the thread for...in is awesome too xD
let object = {a: 1, b: 4, c: 2, d: 3, e: 10}
let sum = 0;
const keys = ['a', 'c', 'd', 'e'];
for(let key in object) {
if(keys.includes(key)) //or key === 'a' || key === 'c' ..
sum += object[key];
}
console.log(sum);
You could take the wanted keys directly.
let object = {a: 1, b: 4, c: 2, d: 3, e: 10},
keys = ['a', 'c', 'd', 'e'],
result = keys.reduce((sum, key) => sum + (object[key] || 0), 0);
console.log(result);
You could either declare the keys you want to sum (whitelist) or those you wish to omit (blacklist). I've used the latter approach here:
let object = {a: 1, b: 4, c: 2, d: 3, e: 10},
ignore = ['b'],
sum = Object.keys(object)
.filter(key => !ignore.includes(key))
.reduce((total, key) => total += object[key], 0);
console.log(sum); //16
Fiddle

Using reduce to add values of properties of a collection objects in JavaScript

Pretty straight forward:
var bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
var reduce = bar.reduce((acc, item) => {
acc['a'] = item.a++;
acc['b'] = item.b++
return acc;
}, {});
console.log(reduce);
{a: 10, b: 20}
I'd like reduce assigned the reference: {a:20, b: 40}
Here is a general solution that will work even if your object inside your array contains different properties.
var bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
var reduce = bar.reduce((acc, item) => {
for (let [key, value] of Object.entries(item)){
if( acc.hasOwnProperty(key)) {
acc[key] += value
}
else {
acc = {...acc, [key]: value }
}
}
return acc;
}, {});
console.log(reduce);
Rather than assigning the accumulator's property the item's property incremented by one, you should add to the existing accumulator's property value. You also shouldn't pass an initial object to the reduce given this implementation (or, if you do, you'll need to define the a and b properties).
Since you're using reduce, I think you should also consider using const instead of var - const is less bug-prone and easier to read:
const bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
const reduced = bar.reduce((acc, item) => {
acc.a += item.a;
acc.b += item.b;
return acc;
});
console.log(reduced);
You could return a new object with added values.
var bar = [{ a: 10, b: 20 }, { a: 10, b: 20 }],
reduce = bar.reduce((a, b) => ({ a: a.a + b.a, b: a.b + b.b }));
console.log(reduce);
Or with a complete dynamic approach for all properties.
const add = (a, b) =>
Object.assign({}, a, ...Object.entries(b).map(([k, v]) => ({ [k]: a[k] + v })));
var bar = [{ a: 10, b: 20 }, { a: 10, b: 20 }],
reduce = bar.reduce(add);
console.log(reduce);

Categories