I have an array of objects and I want to add an element to specific index when a certain attribute changes compared to the previous one.
We have:
const arr = [
{ num: 1 },
{ num: 1 },
{ num: 1 },
{ num: 3 },
{ num: 3 },
{ num: 4 },
{ num: 5 },
];
I want it to become
const arr = [
{ separator:true }
{ num: 1 },
{ num: 1 },
{ num: 1 },
{ separator:true }
{ num: 3 },
{ num: 3 },
{ separator:true }
{ num: 4 },
{ separator:true }
{ num: 5 },
];
I did this:
const getIndexes = (myArr) => {
let indexes = [];
let previousValue = null;
myArr.forEach((el, idx) => {
if (el.num !== previousValue) {
indexes.push(idx);
previousValue = el.num;
}
});
return indexes;
};
const insertSeparator = (arr) => {
let result = arr;
getIndexes(arr).forEach((position) => result.splice(position, 0, { separator: true }));
return result
};
and it returns:
[
{ separator: true },
{ num: 1 },
{ num: 1 },
{ separator: true },
{ num: 1 },
{ separator: true },
{ separator: true },
{ num: 3 },
{ num: 3 },
{ num: 4 },
{ num: 5 }
]
Maybe because of the "new" size of the array, because it is getting bigger and changes its dimension.
What do you think is the best way to solve this?
Run it through .flatMap()
const result = arr.flatMap((obj, idx, arr) => {...
.flatMap() is .map() and .flat() combined, so it transforms the contents of a copy of the given array and removes the brackets []. Next, we return the first object with a separator:
if (idx == 0) {
// returns are wrapped in brackets because they'll be removed before being returned
return [{separator: true}, obj];
}
The next step is to compare the current value with the previous value:
obj.num == arr[idx - 1].num ? // current value vs previous value
[arr[idx - 1]] : // if they are the same value return previous value
[{separator: true}, obj]; /* if they are not the same then return that separator
and current */
const arr = [
{ num: 1 },
{ num: 1 },
{ num: 1 },
{ num: 3 },
{ num: 3 },
{ num: 4 },
{ num: 5 },
];
const result = arr.flatMap((obj, idx, arr) => {
if (idx == 0) {
return [{
separator: true
}, obj];
}
return obj.num == arr[idx - 1].num ? [arr[idx - 1]] : [{
separator: true
}, obj];
});
console.log(JSON.stringify(result, null, 2));
I propose this solution which would consume only one iteration with a reduce :
const arr = [{
num: 1
},
{
num: 1
},
{
num: 1
},
{
num: 3
},
{
num: 3
},
{
num: 4
},
{
num: 5
},
];
let prev_value = arr[0];
const result = arr.reduce((acc, val) => {
const insert = (val.num !== prev_value.num) ? [{
separator: true
}, val] : [val];
prev_value = val;
return acc.concat(insert)
}, [{
separator: true
}, ])
console.log(result)
There must be other ways to do it too. But with a simple modification to your code it can be done. You just need to keep track of the offset with a new variable, incrementing it in the loop:
const arr = [
{ num: 1 },
{ num: 1 },
{ num: 1 },
{ num: 3 },
{ num: 3 },
{ num: 4 },
{ num: 5 },
];
const getIndexes = (myArr) => {
let indexes = [];
let previousValue = null;
myArr.forEach((el, idx) => {
if (el.num !== previousValue) {
indexes.push(idx);
previousValue = el.num;
}
});
return indexes;
};
const insertSeparator = (arr) => {
let result = [...arr];
let offset = -1;
getIndexes(arr).forEach((position) => {
offset++;
return result.splice(position+offset, 0, { separator: true });
});
return result
};
console.log(insertSeparator(arr));
Note: If you want to start with 0 you can do the increment in the .splice() itself : result.splice(position+(offset++),
const positions = [];
//arr.sort((a, b) => a.num - b.num); You can uncomment this line to ensure that the array will always sorted based on num property
arr.forEach((item, index) => {
if (index < arr.length - 1 && item.num != arr[index + 1].num) {
positions.push(index + 1);
}
});
let counter = 0;
positions.forEach((pos) => {
arr.splice(pos + counter++, 0, { separator: true });
});
console.log(arr);
You want to:
Do something which each item in a list
Want to return something other than a list of the same size.
Then I would suggest the good all-round Array.prototype.reduce() function.
const separator = {separator: true};
arr.reduce((result, item) => {
if (result.at(-1)?.num === item.num) {
return [...result, separator, item];
}
return [...result, item]
}, [])
This is (according to me) easier, cleaner and safer since it doesn't mutate variables.
Note
Array.prototype.at() is at the time of writing a new function. If you are using an ancient browser that doesn't support it you can use arr[arr.length -1] to get the last item instead.
Related
I am trying to get the sum of all (a) properties and console.log says NAN
const numbers = [{ a: 1 }, { a: 6 }, { a: 3 }, { d: 4 }, { e: 5 }, { f: 5 }];
const filterNumbers = numbers.reduce((currenttotal, item) => {
return item.a + currenttotal;
}, 0);
console.log(filterNumbers);
is there something wrong?
trying to get the sum of only a keys
Hate to be the person that takes someone's comment and makes an answer out of it, but as #dandavis has suggested, you need to, in someway, default to a value if the 'a' key doensn't exist.
This can be done in a variety of ways:
Short circuit evaluation:
const filterNumbers = numbers.reduce((currenttotal, item) => {
return (item.a || 0) + currenttotal;
}, 0);
The in operator:
const filterNumbers = numbers.reduce((currenttotal, item) => {
return ("a" in item ? item.a : 0) + currenttotal;
}, 0);
Nullish coalescing operator:
const filterNumbers = numbers.reduce((currenttotal, item) => {
return (item.a ?? 0) + currenttotal;
}, 0);
Checking for falsey values of item.a (basically longer short circuit evaluation):
const filterNumbers = numbers.reduce((currenttotal, item) => {
return (item.a ? item.a : 0) + currenttotal;
}, 0);
Since not all fields have an a key the reduce does not find a value and return NAN.
The solution it`s the same as the previous answer, but i rather use immutability in variables since prevents future erros.
const numbers = [{ a: 1 }, { a: 6 }, { a: 3 }, { d: 4 }, { e: 5 }, { f: 5 }];
const filterNumbers = numbers.reduce((currenttotal, item) => {
const keys = Object.keys(item);
return item[keys[0]] + currenttotal;
}, 0);
console.log(filterNumbers);
and in the case u need to sum only with the specific key:
const filterNumbers = numbers.reduce((currenttotal, item) => {
return (item.a ?? 0) + currenttotal;
}, 0);
console.log(filterNumbers);
Not all elements have an a key. If you want to dynamically get whichever key is present, this should work:
const numbers = [{ a: 1 }, { a: 6 }, { a: 3 }, { d: 4 }, { e: 5 }, { f: 5 }];
const filterNumbers = numbers.reduce((currenttotal, item) => {
let keys = Object.keys(item);
return item[keys[0]] + currenttotal;
}, 0);
console.log(filterNumbers);
how to sort highest value in this data using javascript?
data = [{a: [{num:31}, {num:10}]},{a: [{num:4}, {num:9}]},{a: [{num:5}, {num:9}]}]
Expected
data = [{a: [{num:31}]},{a: [{num:9}]},{a: [{num:9}]}]
I try like this but never happen :)
const data_sort = data.sort((a, b) => {
let abc
if (a.a.length > 0) {
abc = a.a.sort((x, y) => x.a - x.a);
}
return a - b
})
let data = [{a: [{num:31}, {num:10}]},{a: [{num:4}, {num:9}]},{a: [{num:5}, {num:9}]}]
data = data.map(item => ({a:[item.a.sort((a, b) => b.num-a.num)[0]]})).sort((a, b) => b.a[0].num-a.a[0].num)
console.log(data)
Assuming this is the correct syntax, all you need is to map every item in the array to it's largest item, then sort that.
var data = [{
a: [{num:31}, {num:10}]
}, {
a: [{num:4}, {num:9}]
}, {
a: [{num:6}, {num:11}]
}, {
a: [{num:5}, {num:9}]
}];
var result = data.map(function(item) {
return {
a: [item.a.sort(function(a,b) {
return a.num - b.num
}).reverse()[0]]
};
}).sort(function(a, b) {
return a.a[0].num - b.a[0].num
}).reverse();
console.log(result)
.as-console-wrapper {
max-height: 100% !important;
}
Map and Reduce
const data = [{ a: [{ num: 31 }, { num: 10 }] }, { a: [{ num: 4 }, { num: 9 }] }, { a: [{ num: 5 }, { num: 9 }] }];
data.map((value) => {
value.a = [
{
num: value.a.reduce((accumulatedValue, currentValue) => {
return Math.max(accumulatedValue.num, currentValue.num);
}),
},
];
return value;
});
console.log(data)
This question already has answers here:
Sort Array Elements (string with numbers), natural sort
(8 answers)
Closed 11 months ago.
I am trying to arrange given values in ascending orders
const value = [
{ val: "11-1" },
{ val: "12-1b" },
{ val: "12-1a" },
{ val: "12-700" },
{ val: "12-7" },
{ val: "12-8" },
];
I am using code below to sort this in ascending order:
value.sort((a,b)=>(a.val >b.val)? 1:((b.val>a.val)?-1:0));
The result of this sort is in the order 11-1,12-1a, 12-1b, 12-7, 12-700, 12-8. However, I want the order to be 11-1,12-1a, 12-1b, 12-7, 12-8, 12-700.
How can I achieve that?
If you're only interested of sorting by the value after the hyphen you can achieve it with this code:
const value = [
{val:'12-1'},
{val:'12-700'},
{val:'12-7'},
{val:'12-8'},
];
const sorted = value.sort((a,b) => {
const anum = parseInt(a.val.split('-')[1]);
const bnum = parseInt(b.val.split('-')[1]);
return anum - bnum;
});
console.log(sorted);
updated the answer as your question update here's the solution for this:
const value = [{ val: '11-1' }, { val: '12-1b' }, { val: '12-1a' }, { val: '12-700' }, { val: '12-7' }, { val: '12-8' }];
const sortAlphaNum = (a, b) => a.val.localeCompare(b.val, 'en', { numeric: true });
console.log(value.sort(sortAlphaNum));
You can check the length first and then do the sorting as follow:
const value = [
{ val: "12-1" },
{ val: "12-700" },
{ val: "12-7" },
{ val: "12-8" },
];
const result = value.sort(
(a, b)=> {
if (a.val.length > b.val.length) {
return 1;
}
if (a.val.length < b.val.length) {
return -1;
}
return (a.val >b.val) ? 1 : ((b.val > a.val) ? -1 : 0)
}
);
console.log(result);
little change's to #Christian answer it will sort before and after - value
const value = [{ val: '12-1' }, { val: '12-700' }, { val: '11-7' }, { val: '12-8' }];
const sorted = value.sort((a, b) => {
const anum = parseInt(a.val.replace('-', '.'));
const bnum = parseInt(b.val.replace('-', '.'));
return anum - bnum;
});
console.log(sorted);
If you want to check for different values both before and after the hyphen and include checking for letters, the solution at the end will solve this.
Here's what I did:
Created a regex to split the characters by type:
var regexValueSplit = /(\d+)([a-z]+)?-(\d+)([a-z]+)?/gi;
Created a comparison function to take numbers and letters into account:
function compareTypes(alpha, bravo) {
if (!isNaN(alpha) && !isNaN(bravo)) {
return parseInt(alpha) - parseInt(bravo);
}
return alpha > bravo;
}
Split the values based on regexValueSplit:
value.sort((a, b) => {
let valuesA = a.val.split(regexValueSplit);
let valuesB = b.val.split(regexValueSplit);
This produces results as follows (example string "12-1a"):
[
"",
"12",
null,
"1",
"a",
""
]
Then, since all the split arrays should have the same length, compare each value in a for loop:
for (let i = 0; i < valuesA.length; i++) {
if (valuesA[i] !== valuesB[i]) {
return compareTypes(valuesA[i], valuesB[i]);
}
}
// Return 0 if all values are equal
return 0;
const value = [{
val: "11-1"
},
{
val: "12-1b"
},
{
val: "12-1a"
},
{
val: "12-700"
},
{
val: "12-7"
},
{
val: "12-8"
},
];
var regexValueSplit = /(\d+)([a-z]+)?-(\d+)([a-z]+)?/gi;
function compareTypes(alpha, bravo) {
if (!isNaN(alpha) && !isNaN(bravo)) {
return parseInt(alpha) - parseInt(bravo);
}
return alpha > bravo;
}
value.sort((a, b) => {
let valuesA = a.val.split(regexValueSplit);
let valuesB = b.val.split(regexValueSplit);
for (let i = 0; i < valuesA.length; i++) {
if (valuesA[i] !== valuesB[i]) {
return compareTypes(valuesA[i], valuesB[i]);
}
}
return 0;
});
console.log(JSON.stringify(value, null, 2));
Since you are sorting on string values, try using String.localeCompare for the sorting.
Try sorting on both numeric components of the string.
const arr = [
{val:'12-1'},
{val:'11-900'},
{val:'12-700'},
{val:'12-7'},
{val:'11-1'},
{val:'12-8'},
{val:'11-90'},
];
const sorter = (a, b) => {
const [a1, a2, b1, b2] = (a.val.split(`-`)
.concat(b.val.split(`-`))).map(Number);
return a1 - b1 || a2 - b2; };
console.log(`Unsorted values:\n${
JSON.stringify(arr.map(v => v.val))}`);
console.log(`Sorted values:\n${
JSON.stringify(arr.sort(sorter).map(v => v.val))}`);
I am having two array with the same length and format given at the end.
assume the last element on each array is the score if either array has zero values in other elements.
Let's say we have array p1 and p2 each have 7 elements. If either p1 or p2 first 6 elements has zero value then it means the game is over and we sum up all other elements and add to last element(mail_hole) which define its score. Then compare each score to find the winner.
Here is my code:
function checkWinner(holes, status = "incomplete", winner = "none") {
const p1MainHole = holes["p1"].pop(); // check if all holes has zero stone.(Except main hole)
const p2MainHole = holes["p2"].pop(); // check if all holes has zero stone.(Except main hole)
if (holes["p1"].every((hole) => hole.value === 0)) {
const sumOfAllStone = this.countAllStone(holes, "p2", p2MainHole);
holes["p2"].push(sumOfAllStone);
holes["p1"].push(p1MainHole);
status = "complete";
} else if (holes["p2"].every((hole) => hole.value === 0)) {
const sumOfAllStone = this.countAllStone(holes, "p1", p1MainHole);
holes["p1"].push(sumOfAllStone);
holes["p2"].push(p2MainHole);
status = "complete";
} else {
holes["p1"].push(p1MainHole);
holes["p2"].push(p2MainHole);
}
if (status === "complete") {
winner = holes["p1"][holes["p1"].length - 1].value > holes["p2"][holes["p2"].length - 1].value ? "p1" : "p2";
}
return {
holes,
status,
winner
};
}
function countAllStone(holes, player, mainHole) {
for (let i = 0; i < holes[player].length; i++) {
mainHole.value += holes[player][i].value;
}
return mainHole;
}
console.log(
checkWinner({
p1: [
{
name: "hole_0",
value: 0,
},
{
name: "hole_1",
value: 0,
},
{
name: "hole_2",
value: 0,
},
{
name: "hole_3",
value: 0,
},
{
name: "hole_4",
value: 0,
},
{
name: "hole_5",
value: 0,
},
{
name: "main_hole",
value: 0,
},
],
p2: [
{
name: "hole_0",
value: 1,
},
{
name: "hole_1",
value: 1,
},
{
name: "hole_2",
value: 1,
},
{
name: "hole_3",
value: 1,
},
{
name: "hole_4",
value: 2,
},
{
name: "hole_5",
value: 0,
},
{
name: "main_hole",
value: 1,
},
],
})
);
At the end it compares each player's score(last elements) to find the winner.
I am not satisfied with the amount of code written and the efficiency of it. Any idea would be welcome, Thanks.
This may be one possible alternate solution to achieve the desired objective:
Code Sample
if (allZeroValues(p1) || allZeroValues(p2)) {
resObj.status = 'complete';
if (allZeroValues(p1)) updateTotal(p2);
else updateTotal(p1);
resObj.winner = getWinner(p1, p2);
};
Explanation
if either p1 or p2 are zero-valued (except 'main_hole'), then
set status to complete
if p1 is all zeroes, update p2's total
else, update p1's total
set winner based on the totals
There are several helper methods used which may be understood from perusing the snippet below.
Code Snippet
const checkWinner = (holes, status = "incomplete", winner = "none") => {
// first, declare few helper methods
// to get an array without the 'main_hole'
const skipMainHole = arr => ([
...arr.filter(el => el.name !== 'main_hole')
]);
// add total values except 'main_hole'
const sumValues = arr => (
skipMainHole(arr).reduce(
(tot, itm) => (tot + itm.value),
0
)
);
// check if array without 'main_hole' is all zeroes
// assumption: 'value' will always be non-negative integer
const allZeroValues = arr => (sumValues(arr) === 0);
// update 'main_hole' value
const updateTotal = arr => {
arr[arr.length - 1].value += sumValues(arr);
};
// get winner
const getWinner = (arr1, arr2) => (
arr1.slice(-1)[0].value === arr2.slice(-1)[0].value
? 'none'
: arr1.slice(-1)[0].value > arr2.slice(-1)[0].value
? 'p1'
: 'p2'
);
// now, de-structure holes to get the p1, p2 arrays
const {p1, p2} = holes;
// set-up a result-object
const resObj = {status, winner};
// now, for the actual logic
if (allZeroValues(p1) || allZeroValues(p2)) {
resObj.status = 'complete';
if (allZeroValues(p1)) updateTotal(p2);
else updateTotal(p1);
resObj.winner = getWinner(p1, p2);
};
// finally, return the updated result-object
return {...resObj, holes: {p1, p2}};
};
console.log(
checkWinner({
p1: [
{
name: "hole_0",
value: 0,
},
{
name: "hole_1",
value: 0,
},
{
name: "hole_2",
value: 0,
},
{
name: "hole_3",
value: 0,
},
{
name: "hole_4",
value: 0,
},
{
name: "hole_5",
value: 0,
},
{
name: "main_hole",
value: 0,
},
],
p2: [
{
name: "hole_0",
value: 1,
},
{
name: "hole_1",
value: 1,
},
{
name: "hole_2",
value: 1,
},
{
name: "hole_3",
value: 1,
},
{
name: "hole_4",
value: 2,
},
{
name: "hole_5",
value: 0,
},
{
name: "main_hole",
value: 1,
},
],
})
);
I have an array arr of objects, each object is of the form:
obj={id: /*some string*/, //id is unique
msgDetails: { content: /*some string*/,time : /*number*/ }
}
In order to get an index of a specific element by its id value ,I use the following:
var idIndex=Babble.messages.findIndex(function(element){
return element.id===num;
});
Is there a way to get all the indexes of the elements in arr that has an id>=num where num is a given number ,without for loop?
You can use filter instead of for:
data.filter(d => Number(d.id) > id);
var data = [{
id: "1",
msgDetails: {
content: "abc1",
time: 1
}
},{
id: "2",
msgDetails: {
content: "abc2",
time: 1
}
},{
id: "3",
msgDetails: {
content: "abc3",
time: 1
}
},{
id: "4",
msgDetails: {
content: "abc4",
time: 1
}
}];
var filterData = function(id) {
return data.filter(d => Number(d.id) > id);
};
console.log(filterData(2));
// Another way
var filterId = function(cond) {
return data.filter(d => cond(Number(d.id)));
};
console.log(filterId(id => id > 2));
You can .map() and .filter() the collection to get the indexes want.
var ids = Babble.messages.map((e, i) => [+e.id, i])
.filter(a => a[0] >= num)
.map(a => a[1]);
You would first use map to get the indexes and then chain filter to that:
var Babble = {
messages: [{ id: "1", msgDetails: { content: "abc", time: 10 }},
{ id: "3", msgDetails: { content: "word", time: 15 }},
{ id: "5", msgDetails: { content: "phrase", time: 12 }},
{ id: "7", msgDetails: { content: "test", time: 21 }}]
};
var num = 4;
var idIndexes = Babble.messages.map( (el, i) => el.id >= num ? i : null )
.filter(i => i !== null);
console.log('indexes with id-values greater or equal than ' + num + ':');
console.log(idIndexes);
This will log the indexes of the items with an ID equal to or larger than the specified ID.
var messages = [
{ id: 10 },
{ id: 12 },
{ id: 2 },
{ id: 20 },
{ id: 30 }
];
function getIndexesForId(id) {
// Create a result array
var indexes = [];
// Loop over all messages.
messages.forEach((item, index) => {
// Check if message ID is equal to or larger than requested ID.
if (item.id >= id) {
// Push the index of the current item into the result array.
indexes.push(index);
}
});
// Return the array.
return indexes;
}
console.log(getIndexesForId(10));
console.log(getIndexesForId(20));