Related
I have two deal with two Set instances.
const set1 = new Set([
{ name: 'a' },
{ name: 'b', lastname: 'bb' },
{ name: 'c' },
{ name: 'd' },
]);
const set2 = new Set([
{ name: 'b' },
{ name: 'd' },
]);
Any object within a set will feature several and also distinct keys and values. The goal is to find structurally equal objects (same keys and values) in both sets, which is ... The intersection of equal data items in/of set1 and set2.
In the following example the expected result is [ { name: 'd' } ] ...
console.log([...set1].filter(item => set2.has(item)));
... but it logs an empty array / [] instead.
An object features more than 20 keys so one has to compare them one by one, which can not be done in a hard coded way.
How could one achieve a generic approach for an intersection of two lists of structurally equal data items?
You can do something like this:
const set1 = new Set([
{name: 'a'},
{name: 'b', lastname: 'bb'},
{name: 'c'},
{name: 'd'}
]);
const set2 = new Set([
{name: 'b'},
{name: 'd'}
]);
set1.forEach((value) => {
if (![...set2].some((o) => Object.entries(o).every(([k, v], _, arr) => (Object.keys(value).length === arr.length && value[k] === v)))) {
set1.delete(value);
}
})
console.log([...set1]);
What this does, is to iterate through set1 and if the item at the current iteration is not the same as any item in set2 (![...set2].some(..)), it is deleted.
The items are considered the same if they have the same number of keys and if the values at the same key are strictly equal.
This only works if the values of the objects in the sets are primitives, if they are not, you'll have to change value[k] === v to an appropriate comparison.
One could write a generic solution which compares pure, thus JSON conform, data structures regardless of any object's nesting depth/level and (creation time) key order.
Such a function would be self recursive for Array item (order matters) and Object property (key order does not matter) comparison. Otherwise values are compared strictly.
function isDeepDataStructureEquality(a, b) {
let isEqual = Object.is(a, b);
if (!isEqual) {
if (Array.isArray(a) && Array.isArray(b)) {
isEqual = (a.length === b.length) && a.every(
(item, idx) => isDeepDataStructureEquality(item, b[idx])
);
} else if (
a && b
&& (typeof a === 'object')
&& (typeof b === 'object')
) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
isEqual = (aKeys.length === bKeys.length) && aKeys.every(
(key, idx) => isDeepDataStructureEquality(a[key], b[key])
);
}
}
return isEqual;
}
const objA = { // `objA` equals `objB`.
name: 'foo',
value: 1,
obj: {
z: 'z',
y: 'y',
a: {
name: 'bar',
value: 2,
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
name: 'baz',
value: 3,
obj: {
k: 'k',
i: 'i',
c: 'c',
},
arr: ['5', 6, 'B', 'A'],
}],
},
},
arr: ['Z', 'Y', 1, '2'],
};
const objB = { // `objB` equals `objA`.
arr: ['Z', 'Y', 1, '2'],
obj: {
z: 'z',
y: 'y',
a: {
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
obj: {
k: 'k',
i: 'i',
c: 'c',
},
name: 'baz',
value: 3,
arr: ['5', 6, 'B', 'A'],
}],
name: 'bar',
value: 2,
},
},
name: 'foo',
value: 1,
};
const objC = { // `objC` equals neither `objA` nor `objB`.
arr: ['Z', 'Y', 1, '2'],
obj: {
z: 'z',
y: 'y',
a: {
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
obj: {
k: 'k',
i: 'i',
c: 'C', // the single difference to `objA` and `objB`.
},
name: 'baz',
value: 3,
arr: ['5', 6, 'B', 'A'],
}],
name: 'bar',
value: 2,
},
},
name: 'foo',
value: 1,
};
console.log(
'isDeepDataStructureEquality(objA, objB) ?..',
isDeepDataStructureEquality(objA, objB)
);
console.log(
'isDeepDataStructureEquality(objA, objC) ?..',
isDeepDataStructureEquality(objA, objC)
);
console.log(
'isDeepDataStructureEquality(objB, objC) ?..',
isDeepDataStructureEquality(objB, objC)
);
console.log(
'isDeepDataStructureEquality(objB, objA) ?..',
isDeepDataStructureEquality(objB, objA)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Based on the above implementation of isDeepDataStructureEquality one can solve the OP's task, that actually looks for the intersection of two list structures, by additionally providing a getIntersectionOfDeeplyEqualDataStructures functionality ...
function getIntersectionOfDeeplyEqualDataStructures(a, b) {
return [...(a ?? [])]
.reduce((collector, sourceItem) => {
const { target, intersection } = collector;
const targetIndex = target.findIndex(targetItem =>
isDeepDataStructureEquality(targetItem, sourceItem)
);
if (targetIndex >= 0) {
// collect the intersection of
// both, source (a) and target (b).
intersection.push(target[targetIndex]);
}
return collector;
}, {
target: [...(b ?? [])],
intersection: [],
}).intersection;
}
const set1 = new Set([
{ name: 'a' },
{ name: 'b', lastname: 'bb' },
{ name: 'c' },
{ name: 'd' }
]);
const set2 = new Set([
{ name: 'b' },
{ name: 'd' },
]);
console.log(
"getIntersectionOfDeeplyEqualDataStructures(set1, set2) ...",
getIntersectionOfDeeplyEqualDataStructures(set1, set2)
);
const set3 = new Set([
{ name: 'a' },
{ name: 'b', lastname: 'bb' },
{ name: 'c' },
{
name: 'd',
list: ['foo', 1, null, false, 0, {
foo: { bar: { baz: 'bizz', buzz: '' } }
}],
},
]);
const set4 = new Set([
{
list: ['foo', 1, null, false, 0, {
foo: { bar: { buzz: '', baz: 'bizz' } }
}],
name: 'd',
},
{ name: 'C' },
{ lastname: 'bb', name: 'b' },
{ name: 'aa' }
]);
console.log(
"getIntersectionOfDeeplyEqualDataStructures(set3, set4) ...",
getIntersectionOfDeeplyEqualDataStructures(set3, set4)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
function isDeepDataStructureEquality(a, b) {
let isEqual = Object.is(a, b);
if (!isEqual) {
if (Array.isArray(a) && Array.isArray(b)) {
isEqual = (a.length === b.length) && a.every(
(item, idx) => isDeepDataStructureEquality(item, b[idx])
);
} else if (
a && b
&& (typeof a === 'object')
&& (typeof b === 'object')
) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
isEqual = (aKeys.length === bKeys.length) && aKeys.every(
(key, idx) => isDeepDataStructureEquality(a[key], b[key])
);
}
}
return isEqual;
}
</script>
Edit
As for Titus' approach ...
set1.forEach(value => {
if (
![...set2].some(o =>
Object.entries(o).every(([k, v], _, arr) =>
(Object.keys(value).length === arr.length && value[k] === v)
)
)
) {
set1.delete(value);
}
});
... which works for flat objects only, though already agnostic to key insertion order, one could optimize the code by ...
... not creating the keys array of the most outer currently processed object again and again with every nested some and every iteration.
thus, something like ... const valueKeys = Object.keys(value); ... before the if clause, already helps improving the code.
... inverting the nested some and every logic which does result in a more efficient way of ... deleting every flat data-item from the processed set which does not equal any flat data-item from the secondary set.
On top of that, one could implement a function statement which not only helps code-reuse but also makes the implementation independent from outer scope references.
For instance, the primary set which is operated and going to be mutated can be accessed as such a function's third parameter. But most important for outer scope independency is the also available thisArg binding for any set's forEach method. Thus any function statement or function expression can access e.g. the other/secondary set via this in case the latter was passed as the forEach's 2nd parameter.
Also an improved wording supports a better readability of the code ...
//the function naming of cause is exaggerated.
function deleteItemFromSourceWhichDoesNotEqualAnyItemFromBoundTarget(sourceItem, _, sourceSet) {
const targetSet = this;
const sourceKeys = Object.keys(sourceItem);
if (
// ... for any data-item from the (bound) target-set ...
[...targetSet].every(targetItem =>
// ... which does not equal the currently processed data-item from the source-set ...
Object.entries(targetItem).some(([targetKey, targetValue], _, targetEntries) =>
sourceKeys.length !== targetEntries.length || sourceItem[targetKey] !== targetValue
)
)
) {
// ... delete the currently processed data-item from the source-set.
sourceSet.delete(sourceItem);
}
}
const set1 = new Set([
{ name: 'a' }, // - to be kept.
{ name: 'b', lastname: 'bb' }, // - to be kept.
{ name: 'c' }, // - to be deleted.
{ name: 'd', nested: { name: 'a' } }, // - to be kept, but fails ...
]); // ... due to not being flat.
const set2 = new Set([
{ name: 'd', nested: { name: 'a' } }, // - should equal, but doesn't.
{ name: 'a' }, // - does equal.
{ lastname: 'bb', name: 'b' }, // - does equal.
{ name: 'e' }, // - doesn't equal.
]);
// `set1` is going to be mutated.
set1.forEach(deleteItemFromSourceWhichDoesNotEqualAnyItemFromBoundTarget, set2);
console.log(
'mutated `set1` now (almost) being equal to the intersection of initial `set1` and `set2` ...',
[...set1]
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
const set1 = new Set([
{name: 'a'},
{name: 'b', lastname: 'bb'},
{name: 'c'},
{name: 'd'}
]);
const set2 = new Set([
{name: 'b'},
{name: 'd'}
]);
const names = [...set2].map(s2 => s2.name);
console.log([...set1].filter(item => names.includes(item.name)));
const set1 = new Set([
{name: 'a'},
{name: 'b', lastname: 'bb'},
{name: 'c'},
{name: 'd'},
{name: 'e'}
]);
const set2 = new Set([
{name: 'c', lastname: 'ccc'},
{name: 'd'},
{name: 'b', lastname: 'cc'},
{name: 'e'}
]);
console.log([...set1].filter(item => {
const s2Arr = [...set2];
const itemKeys = Object.keys(item);
for(let i = 0; i < s2Arr.length; i++){
const s2Obj = s2Arr[i];
const s2ObjKeys = Object.keys(s2Obj);
if(s2ObjKeys.length == itemKeys.length){
let oneSame = true;
for(let j = 0; j < s2ObjKeys.length; j++){
const s2ObjKey = s2ObjKeys[j];
if(item[s2ObjKey] != s2Obj[s2ObjKey]){
oneSame = false;
}
}
if(oneSame)
return true;
}
}
return false;
}));
I have an array of arrays below. With ES6, how can I get a count of each value Good, Excellent & Wow into a new array e.g [{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}] in dynamic style. I am attempting to use Object.assign but I am failing to "unique" out the count of the key plus instead, I need to use an array as I am trying to render this out on the front end. Do I need to use reduce? how?
let k = 0
const stats = {}
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
]
remarks.forEach((arr) => {
arr.map((e) => {
Object.assign(stats, { [e.name]: k = k + 1 })
})
})
console.log(stats);
Output:
stats: {Good: 8, Excellent: 11, Wow: 9}
Which is Incorrect plus I need to use an array.
Expected output:
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
Flatten the array of arrays and reduce it starting with an object like : { Good: 0, Excellent: 0, Wow: 0}
then .map the Object.entries of the result to transform it to an array :
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const result = Object.entries(
remarks.flat().reduce(
(all, { name }) => {
all[name] += 1;
return all;
},
{ Good: 0, Excellent: 0, Wow: 0 }
)
).map(([name, count]) => ({ name, count }));
console.log(result);
You can try below logic:
var data = [[{name: "Good"}],[{name: "Good"}, {name:"Excellent"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name:"Excellent"}],[{name:"Excellent"}]]
var nData = [];
(data || []).forEach( e => {
(e || []).forEach(ei => {
var i = (index = nData.findIndex(d => d.name === ei.name)) >=0 ? index : nData.length;
nData[i] = {
name: ei.name,
count : (nData[i] && nData[i].count ? nData[i].count : 0)+1
}
});
});
console.log(nData);
Hope this helps!
You can use reduce, then convert the result into an array of objects:
const counts = remarks.reduce((result, list) => {
list.forEach(remark => {
result[remark.name] = (result[remark.name] || 0) + 1;
});
}, {});
const finalResult = [];
for (let name in counts) {
finalResult.push({name, count: counts[name]});
}
You could achieve this pretty easily by:
1) Flattening the nested array into 1 single level array.
2) Iterating over the flat array and create a "count map" by using Array.prototype.reduce
For example:
const remarks = [
[{
name: 'Good'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Excellent'
}],
[{
name: 'Excellent'
}]
]
const flatten = arr => arr.reduce((accum, el) => accum.concat(el), [])
const map = flatten(remarks).reduce((accum, el) => {
if (accum[el.name]) {
accum[el.name] += 1;
} else {
accum[el.name] = 1;
}
return accum;
}, {});
console.log(map)
First find the counts using reduce than pass that to another function to get the desired view structure:
const Good = 1,
Excellent = 2,
Wow = 3;
const remarks = [
[{name: Good}],
[{name: Good}, {name:Excellent}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name:Excellent}],
[{name:Excellent}]
];
/*
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
*/
function counts(remarks) {
return remarks.flat().reduce((acc, v) => {
const name = v.name;
let count = acc[name] || 0;
return {
...acc,
[name]: count + 1
}
}, {});
}
function view(counts) {
return Object.keys(counts).map(key => {
let count = counts[key];
return { name: key, count };
})
}
console.log(view(counts(remarks)));
Any time you are making a smaller set of data, or transforming data, in JavaScript reduce should be the first method you attempt to use. In this case, you may want to pair it with an indexer (hence preloading with an array of index and an array of result).
This works in one pass without needing to know the name values up front.
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
];
const stats = remarks.reduce((p,c) => (
c.forEach( ({name}) => {
if(!p[0].hasOwnProperty(name)){
p[1].push({name:name,count:0});
p[0][name] = p[1].length - 1;
}
p[1][p[0][name]].count++;
}),p),[{},[]])[1];
console.log(stats);
A slightly more concise and definitely less readable approach (but it's worth to mention) could be:
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const stats = Object.entries(
remarks
.flat()
.reduce((acc, {name}) => (acc[name] = -~acc[name], acc), {})))
).map(([name, count]) => ({ name, count }));
console.log(stats);
It uses the comma operator in the reducer to returns the accumulator; and the bitwise operator NOT to create a counter without the needs to initialize the object upfront with all the names.
const flattenedRemarks = _.flatten(remarks);
const groupedRemarks = _.groupBy(flattenedRemarks, (remark) => remark.name);
const remarkCounts = _.mapValues(groupedRemarks, (group) => group.length);
const data = {
"mchale": {
"classes":["ESJ030", "SCI339"], // get the length
"faculty":["Hardy", "Vikrum"] // get the length
},
"lawerence":{
"classes":["ENG001"], // get the length
"faculty":["Speedman", "Lee", "Lazenhower"] // get the length
}
};
const count = Object.keys(data).map(campusName => {
const campus = data[campusName];
return Object.keys(campus).map(key => campus[key].length).reduce((p, c) => p + c, 0);
}).reduce((p, c) => p + c, 0);
console.log(count);
Giving an array (fixed length) of objects with the following structures:
{type: 'A', value: 1}
or
{type: 'B', text: 'b'}
What is the easiest way to find all the sequences of objects of type 'A' and return their indices?
An example:
For the following array:
[
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
]
The output should be the following array:
[
{startIndex: 0, startValue: 1, length: 2},
{startIndex: 3, startValue: 11, length: 3},
{startIndex: 7, startValue: 10, length: 1},
]
I guess the naive implementation would be to iterate with forEach and have many complex conditions but is there a simpler technique?
Thanks.
You could use reduce like this. Add a variable prev to keep track of what the previous type was. If the current type is the type you are looking for: Add an object if the previous item had a different type. Else, just increment the length property
let input = [{type:'A',value:1},{type:'A',value:2},{type:'B',text:'b1'},{type:'A',value:11},{type:'A',value:12},{type:'A',value:13},{type:'B',text:'b2'},{type:'A',value:10},{type:'B',text:'b3'}],
prev;
const output = input.reduce((acc, { type, value }, i) => {
if (type === 'A') {
if (prev !== type) {
acc.push({ startIndex: i, startValue: value, length: 1 })
} else {
acc[acc.length - 1].length++
}
}
prev = type
return acc;
}, [])
console.log(output)
You could reduce the array and chweck the value and the type for creating a new group.
var array = [{ type: 'A', value: 1 }, { type: 'A', value: 2 }, { type: 'B', text: 'b1' }, { type: 'A', value: 11 }, { type: 'A', value: 12 }, { type: 'A', value: 13 }, { type: 'B', text: 'b2' }, { type: 'A', value: 10 }, { type: 'B', text: 'b3' }],
result = array.reduce((r, { type, value }, i, a) => {
var previous = a[i - 1] || {},
last = r[r.length - 1];
if (!isFinite(value) || type !== 'A') return r;
if (previous.type !== type) {
r.push({ startIndex: i, startValue: value, length: 1 });
return r;
}
if (value === a[last.startIndex].value + last.length) last.length++;
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Just use a forEach loop. its simple to read and doesnt complicate your code.
let arr = [
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
]
function findSequences(arr, character) {
let seq = []
let seqStarted = false;
let seqI = 0;
arr.forEach((el, i) => {
if (el.type == character) {
if (seqStarted == true) {
seq[seqI].length += 1;
} else {
seqStarted = true;
seq.push({
startIndex: i,
startValue: el.value,
length: 1
});
}
} else {
if (seqStarted) {
seqStarted = false;
seqI++;
}
}
})
return seq;
}
console.log(findSequences(arr, 'A'))
I don't think it can get any simpler that just iterating over the array using a for loop and constructing your desired answer. Also, this solution has linear complexity, as you're traversing the array just once. Not sure why you'd need "many complex conditions" though.
Something like this seems pretty OK to me:
const arr = [
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
];
const result = [];
for (let [index, el] of arr.entries()) {
if (el.type === 'A') {
// account for the first entry found
if (result.length === 0) {
result.push({startIndex: index, length: 1, startValue: el.value});
} else {
const lastSequence = result[result.length - 1];
// check if the we are in a sequence
if (lastSequence.startIndex + lastSequence.length === index) {
lastSequence.length += 1;
} else {
// if we are not in a sequence - create a new one
result.push({startIndex: index, length: 1, startValue: el.value});
}
}
}
}
console.log(result);
For your specified array:
var arr =[
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
];
arr.reduce((result, current, index) => {
if(current.type == 'A'){
if(result.length == 0 || index - result[result.length - 1].length != result[result.length - 1]. startIndex){
result.push({startIndex: index, startValue: current.value, length: 1})
}
else{
result[result.length - 1].length++
}
}
return result;
}, [])
You can use declarative approach something like this instead imperative with forEach: You can use declarative approach something like this instead imperative with forEach:
const a = [
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
];
var filtered = a.map((v,i) => {
return v.type == 'A' ? {startIndex: i,startValue: v.value} : null
}).filter(x => x).reduce((acc,v) => {
if (acc.filter(x => x.startIndex + x.length == v.startIndex).length > 0){
acc[acc.length-1].length ++;
} else {
v.length = 1;
acc.push(v);
}
return acc;
},[]);
console.log(filtered);
Though there are already lots of good answers here, I thought I'd add one which addresses a more general case of segmenting a list. With this code, it's possible to specify a segmenter function, which compares two items and determines whether a new segment should be started.
Once you have these segments, getting to the final answer you require is quite simple.
const data = [
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
]
const segmentBy = segmenter => items => {
const segmentReducer = (prev = [], curr) => {
let lastSegment = [];
let lastItem = null;
try {
lastSegment = prev[prev.length - 1];
lastItem = lastSegment[lastSegment.length - 1];
} catch (e) {
return [...prev, [curr]];
}
const requiresNewSegment = segmenter(lastItem, curr);
if (requiresNewSegment) {
return [...prev, [curr]];
}
return [...prev.slice(0, prev.length - 1), [...lastSegment, curr]];
};
return items.reduce(segmentReducer, []);
};
const addIndex = a => a.map((x, i) => ({...x, index: i}))
const segmentByType = segmentBy((a, b) => a.type !== b.type);
const segments = segmentByType(addIndex(data));
const result = segments
.map(segment => ({
startIndex: segment[0].index,
startValue: segment[0].value || null,
length: segment.length
}))
.filter(x => x.startValue !== null)
console.dir(result);
I have an array of Objects as below.
var options = [
{value: 'b', label: "test1"},
{value: 'a', label: "test12"},
{value: 'c', label: "test123"}
]
and an array of ranks as below.
var ranks = [
{rank: 1, value: "a"},
{rank: 2, value: "b"},
{rank: 3, value: "c"}
]
Now I need to sort the options array according to the rank property in ranks array
Try this:
var options = [
{value: 'b', label: "test1"},
{value: 'a', label: "test12"},
{value: 'c', label: "test123"}
]
var ranks = [
{rank: 1, value: "a"},
{rank: 2, value: "b"},
{rank: 3, value: "c"}
]
options.sort((a, b) => {
return ranks.find(_ => _.value === a.value).rank - ranks.find(_ => _.value === b.value).rank
})
console.log (options)
With ES6, you could use a Map and take this as closure for sorting.
var options = [{ value: 'b', label: "test1" }, { value: 'a', label: "test12" }, { value: 'c', label: "test123" }],
ranks = [{ rank: 1, value: "a" }, { rank: 2, value: "b" }, { rank: 3, value: "c" }];
options.sort(
(m => ({ value: a }, { value: b }) => m.get(a) - m.get(b))
(ranks.reduce((m, { rank, value }) => m.set(value, rank), new Map))
);
console.log(options);
To bring down the complexity and improve performance, you can first create a temp object/map with key as obj.value and rank as value. Then simply use Array.sort to sort on the basis of rank stored in the temp object.
let options = [{value: 'b', label: "test1"},{value: 'a', label: "test12"},{value: 'c', label: "test123"}];
let ranks = [{rank: 1, value: "a"},{rank: 2, value: "b"},{rank: 3, value: "c"}];
let temp = ranks.reduce((a,c) => Object.assign(a,{[c.value]:c.rank}), {});
options.sort((a,b) => temp[a.value] - temp[b.value]);
console.log(options);
I have an array of objects like so:
[{state: CA, value: 1}, {state: CA, value: 1}, {state: NY, value: 10}]
I would like the final result to be an object that contains the aggregates of the value for the same states:
{CA: 2, NY: 10}
I have this code below to do that:
let arr = [{state: 'CA', value: 1}, {state: 'CA', value: 1}, {state: 'NY', value: 10}];
let finalObj = {};
arr.map(function(item) {
let state = item.state;
if (!finalObj[state]) {
finalObj[state] = item.value;
}else {
finalObj[state] += item.value;
}
});
console.log(finalObj);
Is there a better way to do this rather than having those if and else checks inside of the map function?
First I recommend forEach instead of map if you want a simple loop. However in this case reduce is what you really want. I think this is the shortest way to do this I can think of:
const arr = [{state: 'CA', value: 1}, {state: 'CA', value: 1}, {state: 'NY', value: 10}];
const finalObj = arr.reduce((acc, item) => {
acc[item.state] = (acc[item.state] || 0) + item.value;
return acc;
}, {});
You can just replace the if/else with always using an assignment and just doing (acc[item.state] || 0) to always get a valid number.
Another option could be to only do the if part (which I think is a pretty clean solution):
const arr = [{state: 'CA', value: 1}, {state: 'CA', value: 1}, {state: 'NY', value: 10}];
const finalObj = arr.reduce((acc, item) => {
if(!acc[item.state]) {
acc[item.state] = 0;
}
acc[item.state] += item.value;
return acc;
}, {});