I'm having trouble figuring out how to access data in an array of objects. I've spent hours trying to find some example of this, but all I find is you must reference arrays by an index number, which doesn't seem efficient.
For example, I have a table of animals and the number of legs on that animal. How do I access the value (number of legs) for that animal based on the name of the animal. If I pass "human" to a function I want to be able to return "2".
Is this concept called something I'm not yet familiar with yet? Is it just not possible to use a "key" to access data in an array? Do I really have to use a loop to search through the entire array to fine the right entry if I don't know the index number?
What is the simplest way to do this?
let animalsLegs = [{animal: "human", legs: 2},
{animal: "horse", legs: 4},
{animal: "fish", legs: 0}]
function findLegs(animalToFind) {
return animalLegs[animalToFind];
}
console.log(findLegs("human"));
I would expect an output of 2.
Use an object instead of an array:
const animalLegsByAnimalName = {
human: 2,
horse: 4,
fish: 0
};
function findLegs(animalToFind) {
return animalLegsByAnimalName[animalToFind];
}
console.log(findLegs("human"));
If you want to keep using the array, but also use an object for quick, easy lookup, just reduce the initial array into the above object first:
const animalsLegs = [{animal: "human", legs: 2},
{animal: "horse", legs: 4},
{animal: "fish", legs: 0}];
const animalLegsByAnimalName = animalsLegs.reduce((a, { animal, legs }) => {
a[animal] = legs;
return a;
}, {});
function findLegs(animalToFind) {
return animalLegsByAnimalName[animalToFind];
}
console.log(findLegs("human"));
If the object in the array is more complicated than that, and you (for example) want access to additional properties, you can have the object's values be the object in the array, instead of just the value of the leg property:
const animalsLegs = [{animal: "human", legs: 2},
{animal: "horse", legs: 4},
{animal: "fish", legs: 0}];
const animalLegsByAnimalName = animalsLegs.reduce((a, animalObj) => {
a[animalObj.animal] = animalObj;
return a;
}, {});
function findLegs(animalToFind) {
const foundAnimal = animalLegsByAnimalName[animalToFind];
if (!foundAnimal) {
return 'No animal found with that name!';
}
return foundAnimal.legs;
}
console.log(findLegs("human"));
If you want to keep the Array data structure:
function findLegs(animalToFind) {
const animal = animalsLegs.find(animal => animal.name === animalToFind);
return animal.legs;
}
PS: An array data structure is more inefficient than an object, it will take O(n) to find an item, while an object is like a hash table, you can find values in O(1). You can read more about Big O notation here.
I think an array is best for this kind of data, you simply have to write a small function to retrieve what you want.
const animals = [
{ animal: "human", legs: 2 },
{ animal: "horse", legs: 4 },
{ animal: "fish", legs: 0 }
]
function getAnimalByName(name) {
return animals.reduce((a, b) => b.animal !== name ? a : b, null)
}
// Human?!
console.log(getAnimalByName('fish'))
Related
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();
If given an array of ids [1,2,3,4,5]
And an object array:
[{animal:tiger, id:1}, {animal:"fish", id:2}]
What would be the suggested way to return 'tiger, fish'. Would that be through using .map or would a for loop be better for constructing the sentence?
What you need is just go through the list of ids and find corresponding animal in the animals list.
Note, that in case animals list is not expected to store all the animals and some of them are missing, you will need to add additional filter step to be sure that no undefined values appear on the last map step.
const ids = [1,5,2,4,3,6]; // added 6 which is missing in animals
const animals = [
{name:'Tiger',id:1},
{name:'Horse',id:2},
{name:'Mouse',id:3},
{name:'Elephant',id:4},
{name:'Cat',id:5}
];
const result = ids
.map(id => animals.find(a => a.id === id))
.filter(Boolean) // this will exclude undefined
.map(a => a.name)
.join(',');
console.log(result);
var ids = [1,2,3,4,5];
var objects = [{ animal:"tiger", id: 1 }, { animal: "fish", id: 2 }];
objects.map(function(o) { if(ids.includes(o.id)) return o.animal }).join();
I'm guessing you only want the animal names who's id appears in your array. If so, you could filter the array of objects first, followed by a map and a join.
let idarr = [1, 2, 3, 4];
let objarr = [{
animal: "tiger",
id: 1
}, {
animal: "fish",
id: 2
}];
console.log(objarr.filter(x => idarr.includes(x.id)).map(x => x.animal).join(', '))
I suggest two options, depending on your data & use cases.
1. map + find if the animal kingdoms are not too many to loop through.
const animals = [
{animal:tiger, id:1},
{animal:"fish", id:2}
]
const ids = [1,2,3,4,5];
const names = ids.map(id =>
animals.find(animal => animal.id === id));
2. convert animals array to object first, for easier frequent access later. One upfront loop, then easier to access by id later.
const animals = [
{animal: "tiger", id:1},
{animal: "fish", id:2}
]
/*
Convert
[{animal:tiger, id:1}, {animal:"fish", id:2}]
to
{
1: { animal: "tiger", id: 1 },
2: { animal: "fish", id: 2 },
}
*/
const animalsObj = animals.reduce((acc, animal) => {
return {
...acc,
[animal.id]: animal,
}
}, {});
const ids = [1,2,3,4,5];
const names = ids.map(id => animalsObj[id].animal)
I have an array of nested objects, like so:
const objArr = [{obj, obj, objIWant}, {obj, obj, objIWant}, {obj, obj, objIWant}]
Is there a way to get to objIWant without having to loop twice like:
ObjArr.map((obj)=> obj.map(({ objIWant }) => myFunc(objIWant)))
I was hoping I could perhaps leverage destructuring but trying something like [{ objIWant }] = objArr only returns the first objIWant. Am I missing something about destructuring syntax that would allow for this? Many thanks!
No - the only way to do it is with nested map calls.
ObjArr.map(obj => obj.map(({ objIWant }) => myFunc(objIWant));
If you are able to do so, you could change myFunc:
myFunc({ objIWant }) {...}
And then change your code to do this:
ObjArr.map(obj => obj.map(myFunc));
But there's no way using destructuring to do what you're asking.
Destructuring will not make it look cleaner. This is the only way to destructure the properties you want. Using the map function as you are now is a cleaner way of doing it.
const objArr = [{obj:1, obj2:2, objIWant:3}, {obj:4, obj2:5, objIWant:6}, {obj:7, obj2:8, objIWant:9}];
const [{objIWant}, {objIWant:test2}, {objIWant:test3}] = objArr;
window.console.log(objIWant);
window.console.log(test2);
window.console.log(test3);
Not sure if your structure is expected to return an array of objects - or an array of values from each object. But the solution would be the same - use .map() on the original array to return the values (or objects) that you want.
const objArr = [
{id: 1, name: 'first item', value: 1},
{id: 2, name: 'second item', value: 2},
{id: 3, name: 'third item', value: 3}
];
const objIWant = 'value';
const result = objArr.map(item => item[objIWant]);
console.log(result);
// expected output: Array ["1", "2", "3"]
if its a nested object then same deal - use .map() on the original array to construct a new array of the desired objects.
const objArr = [
{"obj": {id: 1}, "obj": { id:1 } , "objIWant": { id:1 }},
{"obj": {id: 1}, "obj": { id:1 } , "objIWant": { id:2 }},
{"obj": {id: 1}, "obj": { id:1 } , "objIWant": { id:3 }}
];
const objIWant = 'objIWant';
const result = objArr.map(item => item[objIWant]);
console.log(result);
// expected output: Array [{"id": 1},{"id": 2},{"id": 3}]
I have this (says it's Object A)
[{grade:1},{grade:2},{grade:3}] till 100th.
how to map this existing data (says its name is object B) into them?
[
{grade:1,name:'alice',address:{poscode:123},tel:324}
{grade:5,name:'wonder',address:{poscode:123},tel:1234223},
{grade:90,name:'james',address:{poscode:234},tel:324}]
]
Says grade 50, it doesn't match any of Object B, the final result should also have the name and address property, assign the value null to them to make the array consistent. I issue is what if there's other property in Object B if I loop using Object A?
If I understand the issue correctly, you can use Object A as your map source, then use Object.assign to copy individual properties from Object B as well as adding in some defaults:
const objA = [{grade:1},{grade:2},{grade:3}];
const objB = [
{grade:1,name:'alice',address:{poscode:123},tel:324},
{grade:5,name:'wonder',address:{poscode:123},tel:1234223},
{grade:90,name:'james',address:{poscode:234},tel:324}
];
const objC = objA.map(rootObject => Object.assign(
// A new object with some defaults
{ name: null, address: null, tel: null },
// The source object from objA
rootObject,
// The data object from objB, if any
objB.find(detail => detail.grade === rootObject.grade)
)
);
console.log(objC);
You can achieve that using convenient Array methods. See snippet below:
const a = [
{grade: 1},
{grade: 2},
{grade: 3},
{grade: 4},
{grade: 5},
{grade: 90},
{grade: 100}
];
const b = [
{grade:1,name:'alice',address:{poscode:123},tel:324},
{grade:5,name:'wonder',address:{poscode:123},tel:1234223},
{grade:90,name:'james',address:{poscode:234},tel:324}
];
let result = a.map(aItem => {
let match = b.find( bItem => aItem.grade === bItem.grade );
if(!match){
return Object.assign({}, aItem, { name: null, address: {}, tel: null });
} else {
return match;
}
});
console.log(result);
I created an immutable map (with Immutable-JS) from a list of objects:
var result = [{'id': 2}, {'id': 4}];
var map = Immutable.fromJS(result);
Now i want to get the object with id = 4.
Is there an easier way than this:
var object = map.filter(function(obj){
return obj.get('id') === 4
}).first();
Essentially, no: you're performing a list lookup by value, not by index, so it will always be a linear traversal.
An improvement would be to use find instead of filter:
var result = map.find(function(obj){return obj.get('id') === 4;});
The first thing to note is that you're not actually creating a map, you're creating a list:
var result = [{'id': 2}, {'id': 4}];
var map = Immutable.fromJS(result);
Immutable.Map.isMap(map); // false
Immutable.List.isList(map); // true
In order to create a map you can use a reviver argument in your toJS call (docs), but it's certainly not the most intuitive api, alternatively you can do something like:
// lets use letters rather than numbers as numbers get coerced to strings anyway
var result = [{'id': 'a'}, {'id': 'b'}];
var map = Immutable.Map(result.reduce(function(previous, current) {
previous[ current.id ] = current;
return previous;
}, {}));
Immutable.Map.isMap(map); // true
Now we have a proper Immutable.js map which has a get method
var item = Map.get('a'); // {id: 'a'}
It may be important to guarantee the order of the array. If that's the case:
Use an OrderedMap
Do a set method on the OrderedMap at each iteration of your source array
The example below uses "withMutations" for better performance.
var OrderedMap = Immutable.OrderedMap
// Get new OrderedMap
function getOm(arr) {
return OrderedMap().withMutations(map => {
arr.forEach(item => map.set(item.id, item))
})
}
// Source collection
var srcArray = [
{
id: 123,
value: 'foo'
},
{
id: 456,
value: 'bar'
}
]
var myOrderedMap = getOm(srcArray)
myOrderedMap.get(123)
// --> { id: 123, value: 'foo' }
myOrderedMap.toObject()
// --> { 123: {id: 123, value: 'foo'}, 456: {id: 456, value: 'bar'} }
myOrderedMap.toArray()
// --> [ {id: 123, value: 'foo'}, { id: 456, value: 'bar' } ]
When using fromJS for array, you'll get List not map. It will be better and easier if you create a map. The following code will convert the result into Immutable map.
const map = result.reduce((map, json) =>
map.set(json.id, Immutable.fromJS(json))
, Map());
Now, you can
map.get('2'); //{'id': 2}
Note, if the result has nested structure and if that has array, it will be a List with the above code.
With ES2015 syntax (and constants):
const result = map.find(o => o.get('id') === 4);
Is there already a way thats easier? I don't know. but you can write your own function. Something like this should work:
var myFunc = function(id){
var object = map.filter(function(obj){return obj.get('id') === id}).first();
return object;
}
Then you would just do:
var myObj = myFunc(4);