Changing part of an array object's property in javascript - javascript

I have an array of objects similar to:
[
{
number: 1,
name: "A"
},
{
number: 2,
name: "e",
},
{
number: 3,
name: "EE",
}
]
I need to be able to insert an object to the array at a particular position, but then I have to shift all the numbers of rest of the object, so the numbers are in sequence order.
Similarly I need to be able to remove an object, but be able to shift all the numbers back.
i.e. If I insert at location 1 with name: "F", the array became:
[
{
number: 1,
name: "A"
},
{
number: 2,
name: "F"
},
{
number: 3,
name: "e",
},
{
number: 4,
name: "EE",
}
]
I know a few ways that can do it, but none of them looks pretty.
Post some of my thoughts here:
To insert I did this.arr.splice(1, 0, newObj), then tried to loop through this.arr, for any index greater than 2, I did ++number, this works, but ugly.
To insert I did this.arr.splice(1, 0, newObj), then split this.arr with let newArr = this.arr.split(2), then newArr.map(a => {...}), use splice to replace part of original arr with newArr.
New Edit:
Share some of my code here, this works, but I'd like to simply it or make it prettier if possible. Please share thoughts.
const newObj = {
number: obj.number + 1,
name: 'S',
}
this.arr.splice(obj.number, 0, newObj)
if (this.arr.length > obj.number) {
const remaining = this.arr.slice(obj.number + 1).map( (t) => ({...t, ...{number: t.number + 1}}))
this.arr.splice(newObj.number, remaining.length, ...remaining)
}

To achieve expected result , use splice(index, deleteCount, item) and use index in map to assign to number of each object to get numbers in sequence after you and delete
let arr = [
{
number: 1,
name: "A"
},
{
number: 2,
name: "e",
},
{
number: 3,
name: "EE",
}
]
let newObj = {
name: "F",
}
arr.splice(1,0, newObj)//To add
// console.log(arr)
//arr.splice(2,1) //To delete
//console.log(arr);
console.log(arr.map((v,i) => {
v.number = i;
return v
}));
Reference link for Array.splice - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice

You can use ... spread syntax and slice.
operation is used to decide whether the value is to be deleted or added.
if operation is del than we slice the first from 0 upto index and than from index+1 to end of array.
else we slice from 0 upto index and add the desired value and than add the remaining part back ( from index to end )
Finally map over the values to adjust number property accordingly
let arr = [{ number: 1,name: "A"},{number: 2,name: "e", }, {number: 3,name: "EE",}]
let handleArray = (array,operation,index,value) => {
if(operation === 'del') {
return [...array.slice(0, index), ...array.slice(index+1,)].map((value,index)=> (value.number = index+1, value))
} else {
return [...array.slice(0,index),value,...array.slice(index,)].map((value,index)=> (value.number = index+1,value))
}
}
console.log(handleArray(arr,'add',0,{a:1}))
console.log(handleArray(arr,'del',0))

Related

Data from the object doesn't convert when using JSON.stringify

I'm stuck in this weird issue that I'm having hard time in understanding what's doing on. When a button is clicked it calls the function onSubmit. What onSubmit function should do is stringify is the object to JSON however for me this doesn't happen. The result I get when I console.log(JSON.stringify(obj)); is [[]]. When I console.log(obj); I can see the object.
I was not able to replicate the same issue in playcode.io and codesandbox.io
async function onSubmit() {
let l = [];
l["Channel"] = undefined;
l["MetricsData"] = [
{ id: 1, name: "CPA", year: "" },
{ id: 2, name: "Average Gift", year: "" },
{ id: 3, name: "Average Gift Upgrade %", year: "" }
];
let obj = [];
l.Channel = 1;
obj.push(l);
console.log(obj);
console.log(JSON.stringify(obj)); //[[]]
}
As others have pointed in comments, you're instantiating l as an array and then attempt populating named keys (Channel, Metricsdata).
Note: Technically, arrays are objects, but they're special objects (their keys are intended to be numeric and they also have a few extra props and methods, specific to arrays - e.g: length, pop, push, slice, etc...). Use the link above to read more about arrays.
What you need to do is use l as an object (e.g: {}):
const l = {
Channel: 1,
MetricsData: [
{ id: 1, name: "CPA", year: "" },
{ id: 2, name: "Average Gift", year: "" },
{ id: 3, name: "Average Gift Upgrade %", year: "" }
]
};
// Uncomment any of the following:
// console.log('object:', l);
// console.log('stringified object:', JSON.stringify(l));
const items = [];
items.push(l);
// console.log('items:', items);
// console.log('first item:', items[0]);
console.log(JSON.stringify(items));

Find duplicate names within an array of different files

A bit of a different use case from the ones I was suggested above.
I need to loop through and check each file name within an array of files and push the files that have the same name into a new array so that I can upload them later separately.
This is my code so far, and surely I have a problem with my conditional checking, can somebody see what I am doing wrong?
filesForStorage = [
{id: 12323, name: 'name', ...},
{id: 3123, name: 'abc', ...},
{id: 3213, name: 'name', ...},
...
]
filesForStorage.map((image, index) => {
for (let i = 0; i < filesForStorage.length; i++) {
for (let j = 0; j < filesForStorage.length; j++) {
if (
filesForStorage[i].name.split(".", 1) ===. //.split('.', 1) is to not keep in consideration the file extension
filesForStorage[j].name.split(".", 1)
) {
console.log(
"----FILES HAVE THE SAME NAME " +
filesForStorage[i] +
" " +
filesForStorage[j]
);
}
}
}
Using map without returning anything makes it near on pointless. You could use forEach but that is equally pointless when you're using a double loop within - it means you would be looping once in the foreach (or map in your case) and then twice more within making for eye-wateringly bad performance.
What you're really trying to do is group your items by name and then pick any group with more than 1 element
const filesForStorage = [
{id: 12323, name: 'name'},
{id: 3123, name: 'abc'},
{id: 3213, name: 'name'}
]
const grouped = Object.values(
filesForStorage.reduce( (a,i) => {
a[i.name] = a[i.name] || [];
a[i.name].push(i);
return a;
},{})
);
console.log(grouped.filter(x => x.length>1).flat());
JavaScript has several functions which perform "hidden" iteration.
Object.values will iterate through an object of key-value pairs and collect all values in an array
Array.prototype.reduce will iterate through an array and perform a computation for each element and finally return a single value
Array.prototype.filter will iterate through an array and collect all elements that return true for a specified test
Array.prototype.flat will iterate through an array, concatenating each element to the next, to create a new flattened array
All of these methods are wasteful as you can compute a collection of duplicates using a single pass over the input array. Furthermore, array methods offer O(n) performance at best, compared to O(1) performance of Set or Map, making the choice of arrays for this kind of computation eye-wateringly bad -
function* duplicates (files) {
const seen = new Set()
for (const f of files) {
if (seen.has(f.name))
yield f
else
seen.add(f.name, f)
}
}
const filesForStorage = [
{id: 12323, name: 'foo'},
{id: 3123, name: 'abc'},
{id: 3213, name: 'foo'},
{id: 4432, name: 'bar'},
{id: 5213, name: 'qux'},
{id: 5512, name: 'bar'},
]
for (const d of duplicates(filesForStorage))
console.log("duplicate name found", d)
duplicate name found {
"id": 3213,
"name": "foo"
}
duplicate name found {
"id": 5512,
"name": "bar"
}
A nested loop can be very expensive on performance, especially if your array will have a lot of values. Something like this would be much better.
filesForStorage = [
{ id: 12323, name: 'name' },
{ id: 3123, name: 'abc' },
{ id: 3213, name: 'name' },
{ id: 3123, name: 'abc' },
{ id: 3213, name: 'name' },
{ id: 3123, name: 'random' },
{ id: 3213, name: 'nothing' },
]
function sameName() {
let checkerObj = {};
let newArray = [];
filesForStorage.forEach(file => {
checkerObj[file.name] = (checkerObj[file.name] || 0) + 1;
});
Object.entries(checkerObj).forEach(([key, value]) => {
if (value > 1) {
newArray.push(key);
}
});
console.log(newArray);
}
sameName();

How can I push data to an array with fixed length in js

I have an array of object something like this.
[
{
channelName: "WhatsApp"
count: 1
date: "2021-06-05"
},{
channelName: "RCS"
count: 1
date: "2021-06-09"
}
]
There are two types of channel names 1. WhatsApp and 2nd are RCS. I want to filter out count with specific channel names and store it in a separate array. But the problem here is I want both the array length should be the same. If there is data for WhatsApp then it will add the count otherwise it will add 0 in place of it.
For that, I did something like this but this does not work .
const filterData = (data: any) => {
const category: any = [];
const whatsAppCount: any = [];
const rcsCount: any = [];
data.filter((item: any, i: number) => {
if (item.channelName === "WhatsApp") {
whatsAppCount[i] = item.count;
} else if (item.channelName === "RCS") {
rcsCount[i] = item.count;
}
category.push(item.date);
});
setGraphData({
category: category,
whatsApp: whatsAppCount,
rcs: rcsCount,
});
console.log("handleRun", { category, whatsAppCount, rcsCount });
};
Here the console log gives something like this.
whatsAppCount: [1, 2, 13, 21, empty × 2, 8, 5, empty, 18, empty, 12, 4]
rcsCount: [empty × 4, 1, 12, empty × 2, 1, empty, 8]
Here in the place of empty, I want 0. I am not sure how to do that any help would be great.
When you create the arrays, but before populating them, there are two functions that can help with initialization:
// create an array with 10 slots preallocated but empty (not `undefined`)
let arr = new Array(10);
// set all allocated slots to a value (`0` in this case)
arr = arr.fill(0);
Since you know the lengths you want ahead of time, you can use that to pre-size the arrays on construction. Then use .fill to initialize the values to 0. Once, that's done, you can continue with your counting and updating the arrays.
Reference:
Array constructor
Array.prototype.fill()
I would suggest you use the map-function, mapping the unwanted values to undefined, letting the other values "pass through" (unmodified), eg.:
const filtered = data.map((each) => {
if (wantToKeep) {
return each;
} else {
return undefined;
}
});
Note, this is not the exact solution - but a general idea.
You can use forEach and push(0) for the empty records.
const data = [
{
channelName: "WhatsApp",
count: 1,
date: "2021-06-05",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-01",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-06",
},
{
channelName: "WhatsApp",
count: 5,
date: "2021-06-11",
},
{
channelName: "WhatsApp",
count: 7,
date: "2021-06-23",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-09",
},
];
const category = [];
const whatsAppCount = [];
const rcsCount = [];
data.forEach(x => {
if (x.channelName === "WhatsApp") {
whatsAppCount.push(x.count);
rcsCount.push(0);
} else if (x.channelName === "RCS") {
whatsAppCount.push(0);
rcsCount.push(x.count);
}
category.push(x.date);
});
console.log({ whatsAppCount });
console.log({ rcsCount });
console.log({ category });

create element at index position if index does not exist in array

I have an array objects that hold an id and a name
const stages = [{
id: 1,
name: ''
}, {
id: 2,
name: ''
}, {
id: 3,
name: ''
}, {
id: 4,
name: ''
}, {
id: 5,
name: ''
}, {
id: 6,
name: ''
}, {
id: 7,
name: ''
}, {
id: 8,
name: ''
}];
Further I have an array that holds numbers.
const indexPositions = [0, 1, 2, 2, 2, 3, 2, 0];
I want to create a third array that holds arrays. Each number in distances represents the index of the current array within the array.
If the current array does not exist yet I want to create it first. Obviously I have to create new arrays until I get to this index position.
Example:
My array is empty at start. The first index position is 0 so I have to create a new array for this. The next index position is 3 so I have to create more arrays until I have 4 arrays.
All I want to do is to push the stage to its correct level index position. The result of this example would be
const levels = [
[stage1, stage8],
[stage2],
[stage3, stage4, stage5, stage7],
[stage6]
];
Currently my code looks this
$(document).ready(() => {
const levels = []; // the array containing the arrays
stages.forEach((stage, stageIndex) => {
const indexPosition = indexPositions[stageIndex];
const positionDifference = indexPosition - levels.length;
if (positionDifference > 0) {
for (let i = 0; i < positionDifference; i++) { // fill up with empty arrays
levels.push([]);
}
}
levels[indexPosition].push(stage);
});
});
I get this error Uncaught TypeError: Cannot read property 'push' of undefined and this happens because the indexPosition is out of bounds. If the positionDifference is 0 no array gets created but in the beginning the array is empty.
I tried setting levels.length to -1 if it is 0 but I still get the error if the difference is 1, I create one array at position 0 and want to access position 1.
How can I create an empty array if it does not exist?
While I do not fully understand what you want to do, checking existence of an array element is simple, one way of doing that is coercing it to boolean:
const thing=[];
function addElem(where,what){
if(!thing[where]) // <- here
thing[where]=[];
thing[where].push(what);
}
addElem(2,1);
addElem(2,2);
addElem(2,3);
addElem(5,1);
console.log(thing);
(The indices are deliberately non-continuous, because that does not matter: JavaScript arrays are sparse)
You could use a single loop and add an array for the index if not exists. Then push the wanted value.
var stages = [{ id: 1, name: '' }, { id: 2, name: '' }, { id: 3, name: '' }, { id: 4, name: '' }, { id: 5, name: '' }, { id: 6, name: '' }, { id: 7, name: '' }, { id: 8, name: '' }],
indexPositions = [0, 1, 2, 2, 2, 3, 2, 0],
result = stages.reduce((r, o, i) => {
var index = indexPositions[i];
r[index] = r[index] || []; // take default value for falsy value
r[index].push('stage' + o.id); // instead of string take object
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You actually were very close! You have a very small issue in your code.
$(document).ready(() => {
const levels = []; // the array containing the arrays
stages.forEach((stage, stageIndex) => {
const indexPosition = indexPositions[stageIndex];
const positionDifference = indexPosition - levels.length + 1; //YOU DID NOT ADD 1 HERE
if (positionDifference > 0) {
for (let i = 0; i < positionDifference; i++) { // fill up with empty arrays
levels.push([]);
}
}
levels[indexPosition].push(stage);
});
});
When you were calculating the positionDifference, you did not add 1 causing the problem when indexPosition equaled 0 and the for loop did not run and no new arrays were pushed. Just adding one fixed the problem :-)

How to combine _.map and _.filter in a more efficient way?

I am using Lodash in my Angular project and I was wondering if there is a better way to write the following code:
$scope.new_arr = _.map(arr1, function(item){
return _.assign(item, {new_id: _.find(arr2, {id: item.id})});
});
$scope.new_arr = _.filter($scope.new_arr, function (item) {
return item.new_id !== undefined;
});
I am trying to combine values from one array to same objects in other array, and I want to ignore the objects that not appear in both arrays (it is something like join or left outer join in the sql language).
Here is a fiddle with an example of this code: Click me!
i think is better to use chaining
$scope.new_arr = _.chain(arr1)
.map(function(item) {
return _.merge(
{}, // to avoid mutations
item,
{new_id: _.find(arr2, {id: item.id})}
);
})
.filter('new_id')
.value();
https://jsfiddle.net/3xjdqsjs/6/
try this:
$scope.getItemById = (array, id) => {
return array.find(item => item.id == id);
};
$scope.mergeArrays = () => {
let items_with_ids = arr1.filter(item => !_.isNil($scope.getItemById(arr2,item.id)));
return items_with_ids.map(item => _.assign(item, {new_id: $scope.getItemById(arr2,item.id)}));
};
The answers provided here are all runtime of O(n^2), because they first run an outer loop on the first array, with an inner loop on the second array. You can instead run this in O(n). First, create a hashmap of all the ids in arr2 in a single loop; this will allow us an order 1 lookup. In the second loop on arr1, check this hashmap to determine if those items exist with O(n). Total Complexity is n + n = 2n, which is just O(n).
// provision some test arrays
var arr1 = [
{
id: 2
},
{
id: 4
},
{
id: 6
}
]
var arr2 = [
{
id: 3
},
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
// First, we create a map of the ids of arr2 with the items. Complexity: O(n)
var mapIdsToArr2Items = _.reduce(arr2, function(accumulator, item) {
accumulator[item.id] = item;
return accumulator;
}, {});
// Next, we use reduce (instead of a _.map followed by a _.filter for slightly more performance.
// This is because with reduce, we loop once, whereas with map and filter,
// we loop twice). Complexity: O(n)
var combinedArr = _.reduce(arr1, function(accumulator, item) {
// Complexity: O(1)
if (mapIdsToArr2Items[item.id]) {
// There's a match/intersection! Arr1's item matches an item in arr 2. Include it
accumulator.push(item);
}
return accumulator;
}, []);
console.log(combinedArr)
You could first make a Map with arr1 and then map the items of arr2 with the properties of arr1.
var arr1 = [{ id: 1, title: 'z' }, { id: 2, title: 'y' }, { id: 3, title: 'x' }, { id: 4, title: 'w' }, { id: 5, title: 'v' }],
arr2 = [{ id: 2, name: 'b' }, { id: 3, name: 'c' }, { id: 4, name: 'd' }, { id: 5, name: 'e' }],
map = new Map(arr1.map(a => [a.id, a])),
result = arr2.map(a => Object.assign({}, a, map.get(a.id)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources