I'm using lodash mapKeys to take my array of objects and convert it to a mapped object using the id property. That's simple enough, but the problem is that it's sorting the new object by id.
For example if I had three objects in my array:
let myArray = [
{
id: 3,
name: 'Number Three'
},
{
id: 1,
name: 'Number One'
},
{
id: 2,
name: 'Number Two'
}
];
Then I map the keys by id:
_.mapKeys(myArray, 'id')
It returns the following:
{
1: {
id: 1,
name: 'Number One'
},
2: {
id: 2,
name: 'Number Two'
},
3: {
id: 3,
name: 'Number Three'
}
}
My server returns the array in a specific order, so I would like the objects to remain the same, so that when I loop over the object properties, they are in the correct order.
Is that possible with this method? If not, is there a possible alternative to achieve the results?
Use a Map because each item has a custom key (like objects), but the order of insertion will be the order of iteration (like arrays):
const myArray = [
{
id: 3,
name: 'Number Three'
},
{
id: 1,
name: 'Number One'
},
{
id: 2,
name: 'Number Two'
}
];
const map = myArray.reduce((map, item) => map.set(item.id, item), new Map());
map.forEach((item) => console.log(item));
As pointed out in the comments, looping over an object doesn't guarantee order. If you want an ordered list, you need an array.
However, you could apply the iterator pattern. In this pattern, it's up to you to decide what “next” element is. So, you could have a set with the objects (in order to get them in constant time) and an array to store the order. To iterate, you'd use the iterator.
This code could be used as example.
Hope it helps.
let myArray = [{
id: 3,
name: 'Number Three'
}, {
id: 1,
name: 'Number One'
}, {
id: 2,
name: 'Number Two'
}];
let myIterator = ((arr) => {
let mySet = _.mapKeys(arr, 'id'),
index = 0,
myOrder = _.map(arr, _.property('id'));
return {
getObjById: (id) => mySet[id],
next: () => mySet[myOrder[index++]],
hasNext: () => index < myOrder.length
};
})(myArray);
// Access elements by id in constant time.
console.log(myIterator.getObjById(1));
// Preserve the order that you got from your server.
while (myIterator.hasNext()) {
console.log(myIterator.next());
}
<script src="https://cdn.jsdelivr.net/lodash/4.16.6/lodash.min.js"></script>
Like mentioned in the comments, the best would be to keep the object references both in an array to keep the order and in a hash to ease updating.
Backbone's collection (source) works like this. It keeps objects in an array (models), but automatically updates a hash (_byId) when adding and removing models (objects) or when a model's id changes.
Here's a simple implementation of the concept. You could make your own implementation or check for a collection lib.
// a little setup
var array = [];
var hash = {};
var addObject = function addObject(obj) {
hash[obj.id] = obj;
array.push(obj);
}
// Create/insert the objects once
addObject({ id: 3, name: 'Number Three' });
addObject({ id: 1, name: 'Number One' });
addObject({ id: 2, name: 'Number Two' });
// Easy access by id
console.log("by id with hash", hash['1']);
// updating is persistent with the object in the array
hash['1'].name += " test";
// keeps the original ordering
for (var i = 0; i < 3; ++i) {
console.log("iterating", i, array[i]);
}
Related
So I have a series of objects that are pulled from an API and inputted into an array, something like such:
array = [
{id: 0, name: "First", relationship: "Friend"},
{id: 1, name: "Second", relationship: "Friend"}
]
The user is allowed to add and remove objects to the list freely (they will appear within a Vue.JS DataTable), and said user is allowed a maximum of 4 objects within the array (lets say 4 "friends")
How should I go about implementing a function that searches the existing array (say, if its populated from the API), and inputs the new object with the corresponding ID that is missing (so if the user deletes the object with the id 2, and adds another, it will search said array with objects, find the missing id 2 slot in the array, and input the object in its place)?
Previously I have gone about it via implement array.find() with conditionals to see if the array contains or does not contain the certain id value, however, it searches through each entry and can end up inserting the same object multiple times. Another method I haven't attempted yet would be having a separate map that contains ids, and then when a user removes an object, having it correspond with the map, and vice versa when adding.
Any suggestions? Thanks
Instead of an array, I'd keep an object in data. Have it keyed by id, like this:
let objects = {
0: { id: 0, name: 'name0', relationship: 'relationship0' },
1: { id: 1, name: 'name1', relationship: 'relationship1' },
}
Integer keys in modern JS will preserve insertion order, so you can think of this object as ordered. The API probably returns an array, so do this...
// in the method that fetches from the api
let arrayFromApi = [...];
this.objects = array.reduce((acc, obj) => {
acc[obj.id] = obj; // insertion order will be preserved
return acc;
}, {});
Your UI probably wants an array, so do this (refer to "array" in the markup):
computed: {
array() {
return Object.values(this.objects);
},
To create a new object, insert it in order, minding the available keys. Note this is a linear search, but with small numbers of objects this will be plenty fast
methods: {
// assumes maxId is const like 4 (or 40, but maybe not 400)
createObject(name, relationship) {
let object = { name, relationship };
for (let i=0; i< maxId; i++) {
if (!this.objects[i]) {
object.id = i;
this.objects[i] = object;
break;
}
}
try this,
let array = [
{id: 0, name: "First", relationship: "Friend"},
{id: 4, name: "Second", relationship: "Friend"},
{id: 2, name: "Second", relationship: "Friend"},
]
const addItem = (item) => {
let prevId = -1
// this is unnecessary if your array is already sorted by id.
// in this example array ids are not sorted. e.g. 0, 4, 2
array.sort((a, b) => a.id - b.id)
//
array.forEach(ob => {
if(ob.id === prevId + 1) prevId++
else return;
})
item = {...item, id: prevId + 1 }
array.splice(prevId+1, 0, item)
}
addItem({name: "x", relationship: "y"})
addItem({name: "a", relationship: "b"})
addItem({name: "c", relationship: "d"})
console.log(array)
You can simply achieve this with the help of Array.find() method along with the Array.indexOf() and Array.splice().
Live Demo :
// Input array of objects (coming from API) and suppose user deleted 2nd id object from the array.
const arr = [
{id: 0, name: "First", relationship: "Friend" },
{id: 1, name: "Second", relationship: "Friend" },
{id: 3, name: "Fourth", relationship: "Friend" }
];
// find the objects next to missing object.
const res = arr.find((obj, index) => obj.id !== index);
// find the index where we have to input the new object.
const index = arr.indexOf(res);
// New object user want to insert
const newObj = {
id: index,
name: "Third",
relationship: "Friend"
}
// Insert the new object into an array at the missing position.
arr.splice(index, 0, newObj);
// Output
console.log(arr);
I can't understand how the map () method works because all the examples are with numbers and to understand I need an example with something more specific.
so I made this
I have an array of objects:
let people = [
{
id: 1,
name: 'jhon',
last_name: 'wilson'
},
{
id: 2,
name: 'maria',
last_name: 'anyway'
},
{
id: 3,
name: 'lastOne',
last_name: 'example'
}
];
I want to understand how with people.map(); i can change the idk, name?? of the 2nd element.
this is how i think map() work:
people.map(() => {
people[1].name = prompt()
// At this point i don't know how continue
})
I'm studying on my own, so I will be very grateful to you :)
The .map() function will go through the entire array, and on each step of that process it will take the current item that we are looking at and will pass it as a parameter into the function. You can then do whatever you want to that item, and whatever you return from your function will replace what is in that position in the array.
Say for example, with the array you gave in your question, we wanted to remove the name and last_name properties, and combine them into a full_name property. We can do the following:
let people = [
{
id: 1,
name: 'jhon',
last_name: 'wilson'
},
{
id: 2,
name: 'maria',
last_name: 'anyway'
},
id: 3,
name: 'lastOne',
last_name: 'example'
}
];
people = people.map((person) => {
return {
id: person.id,
full_name: `${person.name} ${person.last_name}`
}
});
After this code runs, our people array would look like this:
[
{
id: 1,
full_name: 'jhon wilson'
},
{
id: 2,
full_name: 'maria anyway'
},
id: 3,
name: 'lastOne example'
}
];
You can think of it as doing something very similar to this:
function transformPerson(person) {
return {
id: person.id,
full_name: `${person.name} ${person.last_name}`
}
}
let newPeople = [];
for (let i = 0; i < people.length; i++) {
newPeople[i] = transformPerson(people[i])
}
people = newPeople;
Array.map() takes in a function as a parameter, passes each item of the array into the function, and returns an array of the result.
For example, if I wanted to multiply each of the items in the array by 2:
const x = [1, 2, 3, 4, 5]
const y = x.map(v => v * 2) // result: [2, 4, 6, 8, 10]
Note: Array.map does not affect the original array; it creates a new array of the results.
You could change your code to
let people = [{id:1,name:'john',last_name:'wilson'},{id:2,name:'maria',last_name:'anyway'},{id:3,name:'lastOne',last_name:'example'}];
people = people.map((p,i) =>({...p,name: i===1?prompt("New name"):p.name}))
console.log(people);
This will prompt the user only for a new name when i===1. The expression will create a new array that will be stored under the variable name people again. If you wanted people to remain unchanged you could assign the return value of the people.map()-call to a different variable (or constant).
I have two arrays in javascript, where i want to do two different operations
Map an attribute upon each element in each lists
filter out none unique values based on an attribute
I have this function so far
export function generateDisplayedLabels(systemLabels, masterState){
const mappedSystemlabels = systemLabels.map(label => Object.assign(label, {type: "system"}))
const mappedMasterlabels = masterState.map(label => Object.assign(label, {type: "master"}))
const displayedLabels = _.union(mappedSystemlabels, mappedMasterState);
return displayedLabels
}
This would work except for the fact, that whenever i map over the objects in the beginning, the "unique" elements are no longer unique, because they have another attribute mapped upon it. Is there a time efficient way, that i can filter out the none unique elements, ignoring the attribute, that have been mapped onto it.
let ar1 = [
{
id: 1,
name: 'stack',
},
{
id: 2,
name: 'react',
},
];
let ar2 = [
{
id: 1,
name: 'javascript',
},
{
id: 2,
name: 'overflow',
},
{
id: 2,
name: 'react',
},
];
console.log(_.unionBy(ar1, ar2, 'id'));
console.log("======");
console.log(_.unionBy(ar1, ar2, 'name'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
My question is, how can i access each object inside an Array? or
perhaps, how am I supposed to solve this problem? Like I know in head,
i have to compare categories and then push into new const array that
category. So far, i get each object in array wrote down, but I need to
do an push method after category is same, and also after that to
splice the category from each object.
My solution so far:
export const convert = inside => {
inside(({id,name,category}) => {
outside[category].push({id,name});
});
console.log(outside);
return outside;
}
Sorry for messed code, could not load here.
You could take the category as key for the object and push a new object.
No need for an array for every category, because this appoach uses the result object with dynamic keys.
const
inside = [{ id: 1, name: "orange", category: "fruits" }, { id: 2, name: "apple", category: "fruits" }, { id: 3, name: "carrot", category: "vegetable" }],
outside = {};
inside.forEach(({ id, name, category }) => {
outside[category] = outside[category] || [];
outside[category].push({ id, name });
});
console.log(outside);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm not fully sure i understand the question but from what i think is needed, you want to take all the items from the outside constant and take their respective category, apply it to the food Object and then add that Object to the inside variable.
const outside = {
fruits: [{
id: 1,
name: "orange"
}, {
id: 2,
name: "apple"
}],
vegetable: [{
id: 3,
name: "carrot"
}]
}
const categories = Object.keys(outside)
let inside = []
categories.forEach(category => {
const categorizedFood = outside[category].map(f => ({...f, category }) )
inside = [...inside, ...categorizedFood]
})
console.log(inside)
.as-console-wrapper {
background: #FFF;
filter: invert(1) hue-rotate(210deg);
}
How can I display multiple values of an array to the console that match the condition (e.g: === "McDonalds")?
I only managed to display one item. But I don't know how i can display all the value of my array.
public products: product[] = [
{ id: 1, name: "McFlurry", price: 2, enseigne:"McDonalds" },
{ id: 2, name: "Potatoes", price: 3, enseigne:"McDonalds" },
{ id: 3, name: "BigMac", price: 4, enseigne:"KFC" },
{ id: 4, name: "Nuggets", price: 3, enseigne:"KFC" }
];
searchEnseigne(){
let server = this.products.find(x => x.enseigne === "McDonalds");
console.log(server);
}
let server = this.products.filter(x => x.enseigne === "McDonalds");
console.log(server);
Use filter instead of find:
The filter() method creates a new array with all elements that pass the test. While The find() method returns the value of the first element
searchEnseigne(){
let server = this.products.filter(x => x.enseigne === "McDonalds");
console.log(server);
}