For eg:
var persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary":"1500" }];
And you want to change the value of "salary" of each person in an original array.
If you want to mutate the original array, you can use Array#forEach function.
const persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary": 1500 }];
persons.forEach(item => item.salary += 1000);
console.log(persons)
Array#map creates a new array of the created items and returns that. After you need to assign the returned result.
let persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary": 1500 }];
persons = persons.map(item => {
item.salary += 1000;
return item;
});
console.log(persons);
You can mutate the objects directly iterating with map. If I understood you correctly.
persons.map(i => { i.salary = i.salary * 1.25; return i; });
console.log(persons);
// [{ "name":"A", "salary": 1875 }, { "name":"B", "salary": 2343.75 }]
Though it's anti-pattern and you should avoid performing mutations of your original array in map().
Use forEach. If your array is full of objects, and you simply want to mutate those objects — like in the original question — then you can simply have your callback function mutate the item passed to it in its first argument.
If your array is full of primitives, which can only be changed by assigning something else to the array slots, thereby mutating the array — or if for some other reason you want to reassign things to the array slots — you can still use forEach. The second parameter and third parameters passed to the callback are the array index of the element currently being operated on, and a reference to the array itself. So within the callback function, you can assign to the relevant array slot.
you can use a simple for loop for that
var persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary":"1500" }];
for(let element of persons){
element.salary*=2;
}
console.log(persons);
.map() function takes third parameter in its callback thats the instance of original array.
You could do something like this also:
var persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary":1500 }];
persons.map(function(person, key, array) {
array[key].salary *= 2;
});
console.log(persons);
I read other answers, you can use any of them, but I see some problems there.
I will mention 2 methodologies I have used in many different languages, map and forEach. map is a functional way of traversing a collection and creating some new collection with new (different or same) elements, independent of languages. With map, it is expected to create a new collection that is created by some mapping from initial collection. On the other hand, forEach is a method that eases traversing a collection by not using usual for loop syntax for collections, and mutating (or changing) each item if desired.
If you use map on a collection that contains objects, and change those objects in the mapper function, you might face with unexpected behavior. Beacuse you are changing directly the object you are operating on, and do not mapping it to another object. This object might can be considered as a state and computers works based on the state transfers. If you want to change that object, i.e. some state, it is absolutely ok, but based on the description, you should not use map for such a case. Because you are not creating a new array with some new values, but instead, mutating provided elements. Use forEach for such a case.
I have added an example here. You can click the link and take a look at the console, and see my what I mean in a more clear way.
As far as I know, based on my experience, mutations in map method is considered as bad practice and discouraged.
These two are added for different purposes and it would be better to use them as expected.
For more, see Mozilla Web Docs page for Array.
JavaScript has an inbuilt Array method map that iterate the values of an Array
persons.map(person => person["salary"] + 1000)
var persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary":"1500" }];
var mutatedPersons = persons.map(function(obj){
return {name:obj.name,salary:parseInt(obj.salary) + 100};
})
console.log(mutatedPersons);
try:
let persons = persons.map((person) => {person['salary'] = parseInt(person['salary']) + 1; return person})
If you have an array of primitives, you can use this function:
function mapInplace<T>(arr: T[], callback: (v: T, i: number) => T) {
for(const [i, v] of arr.entries()) {
arr[i] = callback(v, i)
}
}
Example usage:
mapInplace(weights, w => w / total)
There's no return value since it's mutating the array.
Related
I'm trying to figure out the cleanest way of using the string-similarity library in NodeJS with the 2 arrays used in my project.
The first is an array of objects that look something like this:
{
eventName: "Some event name",
tournamentName: "US Open",
city: "New York"
}
The second array contains objects that looks slightly different, for example:
{
eventName: "Some event name",
temperature: "28",
spectators: "15000"
}
What I'm trying to do is build something that iterates through the first array and finds the closest matching event name in the second array, based of course ONLY on the eventName property using the "string-similarity" NodeJS library.
The below method works really well:
stringSimilarity.findBestMatch(eventName, arrayOfEventNames)
But of course the 2nd parameter requires an array consisting only of event names. I don't have that. I have an array consisting of objects. It's true that one of the properties of these objects is the event name, so what I'm trying to figure out is the best way to pass that in to this function. I built the below function (calling it inside forEach on first array) which basically takes in the name of the event I want to search for and the second array of objects and then creates a new temporary array inside it of ONLY the event names. Then I have the 2 inputs I need to call the stringSimilarity.findBestMatch method.
function findIndexOfMatchingEvent(eventName, arrayToCompareAgainst) {
let onlyEventNames = [];
arrayToCompareAgainst.forEach(e => {
onlyEventNames.push(e.eventName);
});
if (arrayToCompareAgainst.length !== onlyEventNames.length) {
throw new Error("List of events array length doesn't match event names array length!");
}
const bestMatch = stringSimilarity.findBestMatch(eventName, onlyEventNames);
const bestMatchEventName = bestMatch.bestMatch.target;
const bestMatchAccuracyRating = bestMatch.bestMatch.rating;
const index = arrayToCompareAgainst.findIndex(e => {
return e.eventName === bestMatchEventName;
});
if (index === -1) {
throw new Error("Could not find matched event in original event list array");
} else if (bestMatchAccuracyRating >= 0.40) {
return index;
}
}
This works but it feels very wrong to me. I'm creating this new temporary array so many times. If my first array has 200 objects, then for each of those I'm calling my custom function which is then creating this temporary array (onlyEventNames) 200 times as well. And even worse, it's not really connected to the original array in any way, which is why I'm then using .findIndex to go back and find which object inside the array the found event refers to.
Would really appreciate some feedback/advice on this one. Thanks in advance!
In my earlier answer I misunderstood the question.
There's no need to recreate the array of event names for each entry in the other array you want to compare. Create the array of event names once, then reuse that array when looping through the other array's entries. You can create the array of event names the way you did in findIndexOfMatchingEvent, but the more idiomatic way is with map.
Assuming these arrays:
const firstArray = [
{
eventName: "Some event name",
tournamentName: "US Open",
city: "New York"
},
// ...
];
const secondArray = [
{
eventName: "Some event name",
temperature: "28",
spectators: "15000"
},
// ...
];
Then you can do this:
const onlyEventNames = secondArray.map(e => e.eventName);
let bestResult;
let bestRating = 0;
for (const {eventName} of firstArray) {
const result = stringSimilarity.findBestMatch(eventName, onlyEventNames)
if (!bestResult || bestRating < result.rating) {
// Better match
bestResult = secondArray[result.bestMatchIndex];
bestRating = result.rating;
}
}
if (bestRating >= 0.4) {
// Use `bestResult`
}
When done with the loop, bestResult will be the object from the second array that is the best match for the events in the first array, and bestRating will be the rating of that object. (That assumes there are entries in the arrays. If there are no entries in firstArray, bestResult will be undefined and bestRating will be 0; if there aren't any in the second array, I don't know what findBestMatch returns [or if it throws].)
About your specific concerns:
I'm creating this new temporary array so many times.
Yes, that's definitely not ideal (though with 200 elements, it's really not a big problem). That's why in the above I create it only once and reuse it.
...it's not really connected to the original array in any way...
It is: by index. You know for sure that if the match was found at index 2 of onlyEventNames, that match is for index 2 of secondArray. In the code above I grab the entry using the index returned by findBestMatch.
I have a very strange case which indicates that either map function mutates object or lodash incorrectly clones the object. This piece of code is just a sample: a function that takes an object as an argument and returns a copy of it using cloneDeep from lodash. Then I use this object to generate a chart.
const copyObject = data => {
const copy = _.cloneDeep(data);
const dataWithIndexes = copy.nodes.map((node, index) => {
node.index = index;
return node;
});
return copy;
};
export const copiedData = copyObject(sampleData);
Entry data in this case is an object with arrays of objects:
{
nodes: [
{ name: "transactions.main", layer: 0 },
...
],
links: [
{ source: 3, target: 3, value: 4 },
...
]
}
As you can see map inside function is not used at all and this is the point. When I am using original, unmodified object within my chart generate function it works fine, when I am copying the object with function shown above, it doesn't work, BUT when I comment this dataWithIndexes variable it starts working again. Is it possible in any way that map mutates the copied object? Or maybe it's lodash's fault? It may clone the object incorrectly, but on the other hand I only use the map on it, it doesn't modify it in any way.
Maybe anyone can help me solving this riddle T_T
Thanks
You are modifying the node object parameter in the map(...) callback by overwriting its index property: node.index = index. This way the original object in the array is getting mutated although it returns a new array.
This would still happen even though you are not using the dataWithIndexes that is because the map(...) is still run and when it does it mutates the objects in the copy.node array with the new values of the index in the callback.
To avoid it, make a copy of the node object parameter in the map call and assign the new index there and then return it from the callback:
const dataWithIndexes = copy.nodes.map((node, index) => {
return {...node, index};
});
I have two APIs to work with and they can't be changed. One of them returns type like this:
{
type: 25
}
and to other API I should send type like this:
{
type: 'Computers'
}
where 25 == 'Computers'. What I want to have is a map of numeric indices to the string value like this:
{
'1': 'Food',
'2': 'Something',
....
'25': 'Computers'
....
}
I am not sure why, but it doesn't feel right to have such map with numeric value to string, but maybe it is completely fine? I tried to Google the answer, but couldn't find anything specific. In one place it says that it is fine, in another some people say that it's better not to have numeric values as object keys. So, who is right and why? Could somebody help me with this question?
Thanks :)
There's nothing wrong with it, but I can understand how it might look a little hinky. One alternative is to have an array of objects each with their own id that you can then filter/find on:
const arr = [ { id: 1, label: 'Food' }, { id: 2, label: 'Something' }, { id: 25, label: 'Computers' } ];
const id = 25;
function getLabel(arr, id) {
return arr.find(obj => obj.id === id).label;
}
console.log(getLabel(arr, id));
You can use the Map object for this if using regular object feels "weird".
const map = new Map()
map.set(25, 'Computers');
map.set(1, 'Food');
// then later
const computers = map.get(25);
// or loop over the map with
map.forEach((id, category) => {
console.log(id, category);
});
Quick Update:
As mentioned by others, using objects with key=value pairs is OK.
In the end, everything in javascript is an object(including arrays)
Using key-value pairs or Map has 1 big advantage( in some cases it makes a huge difference ), and that is having an "indexed" data structure. You don't have to search the entire array to find what you are looking for.
const a = data[id];
is nearly instant, whereas if you search for an id in an array of objects...it all depends on your search algorithm and the size of the array.
Using an "indexed" object over an array gives much better performance if dealing with large arrays that are constantly being updated/searched by some render-loop function.
Map has the advantage of maintaining the insertion order of key-value pairs and it also only iterates over the properties that you have set. When looping over object properties, you have to check that the property belongs to that object and is not "inherited" through prototype chain( hasOwnProperty)
m = new Map()
m.set(5, 'five');
m.set(1, 'one');
m.set(2, 'two');
// some other function altered the same object
m.__proto__.test = "test";
m.forEach((id, category) => {
console.log(id, category);
});
/*
outputs:
five 5
one 1
two 2
*/
o = {};
o[5] = 'five';
o[1] = 'one';
o[2] = 'two';
// something else in the code used the same object and added a new property
// which you are not aware of.
o.__proto__.someUnexpectedFunction = () => {}
for (key in o) {
console.log(key, o[key]);
}
/*
Output:
1 one
2 two
5 five
someUnexpectedFunction () => {}
*/
Map and objects also have 1 very important advantage(sometimes disadvantage - depending on your needs ). Maps/objects/Sets guarantee that your indexed values are unique. This will automatically remove any duplicates from your result set.
With arrays you would need to check every time if an element is already in the array or not.
I'm trying to save every 'percentage' value from a .json into an array:
This code is working right now:
let percentage = []
for(var key in res['data']){
percentage.push(res['data'][key].percentage);
}
But I feel like I can improve it to only use one line. I tried mapping the values to a variable, as I did in the past like this:
let percentage = res['data'].map(res => res.percentage);
But instead I'm getting a 'TypeError: res.data.map is not a function' error. How can improve my code to map it in just one line like this?:
data is an object, not an array, so you can't call map on it. But, you could use Object.values instead:
const res = {
data: {
0: {
percentage: 8.1
},
1: {
percentage: 47
},
2: {
percentage: 87
}
}
};
const percentage = Object.values(res.data).map(({ percentage }) => percentage);
console.log(percentage);
map function you can use with Array. Since you input is an object, use Object.values.
Object.values(res.data).map(({ percentage }) => percentage);
One short and easy way is to use lodash library (use npm to get it)
Therein, you will have a _.map method which you can use straightaway, like this
_.map(res.data, (obj) => obj.percentage)
Another way which many people here are suggesting are and why you should use it is:
1- The first thing here is the use of for..in also iterates over the object's prototype chain properties as well.
Here on mozilla page :
The Object.values() method returns an array of a given object's own
enumerable property values, in the same order as that provided by a
for...in loop (the difference being that a for-in loop enumerates
properties in the prototype chain as well).
So, you should be using Object.values to get only its own enumerables.
2- Once you have the values as list, you can run a map function on it.
Here, in the arrow function passed to map, do object destructuring {propertyName} (ES6 specific) to extract the property to the propertyName variable.
So , the code will become :
Object.values(res.data)
.map( ({percentage}) => percentage)
I hope this clears it all.
EOD.
YOu can do Object.values and then use map on that
let resp = {
"data": {
"0": {
"percentage": 8.1
},
"1": {
"percentage": 9.1
},
"2": {
"percentage": 10.1
}
}
}
let perc = Object.values(resp.data).map(function(item) {
return item.percentage;
})
console.log(perc)
data is not an array and you can not implement map() on object other than array. You have to use Object.values() to implement map():
let percentage = Object.values(res.data).map(res => res.percentage);
res.data is an object, not an array, so no map method is available at this point.
Since your data object has numerical keys, you can easily transform it into an array with Object.assign, then map it. You can also shorten the mapping function a bit by using destructuring:
let percentage = Object.assign([], res.data).map(({percentage}) => percentage);
I created an array with many elements with a loop:
myArray = [c1, c2, c3...]
now I want to make each element into an object and assign different key values:
c1 = {image = path, value = number)
I tried to run forEach() but can't figure out the correct way to do so and I have not succeeded in finding the answer to it.
My guess was:
myArray.forEach(function() {
let name = {
image = path,
value = number,
}
return name;
});
but there's no change in the elements in the log.
Any help or link to an answer that can help me here. First time coding here.
UPDATE: an easier solution was to .push all the keys and values of the objects when I created the array with the loop in the first place.
array.push({image: pathx, value: numberx})
You can, but you'd be better off with a simple for loop:
for (let i = 0; i < myArray.length; ++i) {
let entry = myArray[i];
myArray[i] = {image: entry.path, value: entry.number};
}
Or making a new array with map.
newArray = myArray.map(entry => ({image: entry.path, value: entry.number}));
Or if you prefer non-arrow functions:
newArray = myArray.map(function(entry) {
return {image: entry.path, value: entry.number};
});
You could theoretically push to a new array but this is the exact usecase for Array#map. Array#map maps old values to new values. The returned object from the callback is the new object and the returned array is the new array containing the new objects.
Semantically, Array#forEach is to plainly iterate over each element in an array, and possibly execute something with side-effects (which may include pushing to another array). But with Array#map, it's specifically used to transform old array values to new ones. Use the one that is specifically designed because it conveys a clear message to anyone else who reads your code.
const newArray = myArray.map(({ path, number }) => ({
image: path,
value: number
}));
Array#map maps old values to new values. You may need to use the follwing instead of arrow functions as it is not supported in IE.
I just added dummy data in the object, you can change it.
myArray = ["c1", "c2", "c3"]
myArray = myArray.map(function(elem) {
return {"image":"path"+elem,"value":"value"+elem};
});
console.log(myArray);