How to efficiently search specific values in array using JavaScript - javascript

Im wondering how to search big arrays using JavaScript.
Let's say we have an array consisting of elements that represent businesses. There are a couple thousand elements in the array, they are objects that sometimes also have arrays in them.
In each of those elements is a key e.g. called category that looks like this category: ["attr1", "attr2", "attr3"]. Now I only want to find elements of the array that have a category like this:
category: ["Restaurant"]
The problem is that other keys of the elements in the array sometimes also have a property where "Restaurant" is in the name followed by something else e.g. "RestaurantPrice" so
if(array[i].categories != null &&
array[i].categories.indexOf("Restaurant") != -1) {
do something
}
Would not work properly because it would return all occurences of the word "Restaurant" or am I wrong?
If not how should I search for specific things in an array?

If categories is an array like ["foobar", "foo"]
categories.indexOf("foo") would return 1, foobar would be ignored.
The other question was how to find elements efficient?.
It depends, if your set is static, you can prebuilt and cache some sort of index like:
var categoriesMap = {
foobar: [1, 2, 4],
foo: [1, 5, 7]
}
The array would represent the indexes of items, which includes the given categories.

Try using HashMaps if your keys are unique.

Related

Forcing string representation of an array key

I am trying to maintain the order of an array with mixed key types. The array contains mostly keys represented by string values -- but if you enter a numbered key it goes to the front. How can I force a key which is a number to be a string type?
E.g.
array = [];
array["one"] = "some data";
array["two"] = "some more data";
array["3"] = "this should not be the first element";
How can I make "3" a string type to prevent it from moving to the top of the index?
Oh wow did you ever open multiple cans of worms.
Javascript arrays are a special type of Javascript objects, and like all Javascript objects they can have arbitrary string properties:
const foo = [];
foo["bar"] = "hi";
However that string is a property of the array object, not an item in the array:
foo.forEach(console.log); // logs nothing
You can still access it like any other object property:
console.log(foo["bar"]); // "hi"
But it won't show up in the usual iterative constructs like c-style for loops or the map/forEach array methods.
The line in your example
array["3"] = "this should not be the first element";
is very different however, because of Javascript's playing fast and loose with type conversions this actually sets the string to the 4th slot in the array:
const bar = [];
bar["3"] = "oops!"; // equivalent to bar[3] = "oops!"
console.log(bar); // [empty x 3, "oops!"]
This piece of it is actually a good thing (other than the implicit conversion part) rather than a problem: sometimes you need a sparse array and JS supports those. Iterating it will only produce the one element:
bar.forEach((item, index) => console.log(item, index)); // ["oops", 3]
Note though that the string has the correct index of 3, and can be accessed that way even though there's nothing "in front" of it:
bar[3]; // "oops"
So the first two assignments in your example create properties on the array object, and the third assignment is the only one that actually adds an item to the array, at the 4th index (there's nothing at the first 3).
What you seem to want as Reese Casey suggests, is a plain object:
const foo = {}; // curly
foo["some string"] = "whatever";
However now the properties are basically unordered. If you want them to be in a guaranteed specific order you do want an array, but all your indicies will need to be integers, and should be sequential. You can achieve this easily by using the .push method:
foo = [];
foo.push("something");
foo.push("something else");
Now foo will have two elements, in the correct order, and index 0 and 1 respectively.
Update based on comment on the other answer:
I want some of the data to be ordered, and the rest of the data to follow
This can be accomplished through object destructuring:
const responseFromDB = {
oneKeyICareAbout: 3,
anotherKeyICareAbout: 2,
foo: 6,
bar: 7,
};
const {
oneKeyICareAbout,
anotherKeyICareAbout,
*rest,
} = responseFromDB;
const stuffToDisplay = [
oneKeyICareAbout,
anotherKeyICareAbout,
...Object.values(rest),
]; // [3, 2, 6, 7]
And at least the destructured stuff you put in the array will be ordered because by doing so you've ordered it.
Javascript arrays cannot have string indexes. This is actually working incorrectly as the index is adding a property to the array object.
Changing to an object makes more sense for this.
EDIT: Whilst below its mentioned you can have string indexes you are not actually using the array by doing so. The answer by Jared Smith goes into much more detail as to why.
The other answers explain what is happening with your array-object mixture. For having an indexable thing which can reproduce the original order, you can use a Map:
The Map object holds key-value pairs and remembers the original insertion order of the keys.
array = new Map();
array.set("one","some data");
array.set("two","some more data");
array.set("3","this should not be the first element");
console.log("Test of get:",array.get("two"));
console.log("Test of order:");
for(let entry of array)
console.log(entry);

How to use an array of object Id's to find those objects in another array? Must find all objects based on the given array of Id's

I have an array of object Id's. The contents of the array will vary based on the user interaction, we could have just one ID in the array or 15 different ids (there will never be duplicate ids). How can I use each of these ids to then find the matching object or objects in a different array?
I have tried used filter, includes and find_by. I have also attempted to do nested loops to no avail.
The ids are in an array as such [1, 2, 3, 4]
The objects I am attempted to search through is an array of hashes. The hashes key value pairs are as such
name: Mike Bill
email: "mikeybilly#gmail.com"
id: 5715
What I would like to do is find the objects in this array that have the id of 1, 2, 3 and 4
If I understood your question, you should have two arrays like those:
const ids = [1, 10, 2];
const objects = [{name: 'foo', id: 2}, {name:'bar', id:3}, {name: 'baz', id: 1} ];
The actual props in objects' elements doesn't matter as soon as they're objects and have id prop.
Then, you can create a new array from the objects' array, using filter and includes:
const filtered = objects.filter(({id}) => ids.includes(id)))
This code also use arrow functions and destructuring assignment.

JS Splice Array of Nulls Removes Last Index Regardless of Parameters

For a reason specific to this application an array of data or nulls is used to display a list of forms. The difference is based on whether data was provided by a service or manually added, where null indicates everything was manually added via
a button not a service.
So ignoring the use case of an array of nulls it turns out that [null, null, null].splice(0, 1); removes the null at index 2 instead of 0 based on entering values into the different forms displayed based on the array length, and then seeing who disappears on delete.
It can be made to work by adding something unique like { index: theIndex } to the array instead of null. So splice now works correctly removing the item at index 0 instead of 2, but now I'm curious what splice does under the covers that it can't remove an index regardless of its value compared to the other indices.
Can anyone explain what splice is doing? Based on the spec for splice I don't really see why this happens.
(This follows from the comments in the question but is too large to be a comment).
So I think you are having a conceptual misconception (^^). Look at this examples:
let a = [1, 2, 3]
a.splice(0, 1) // => 1
a // => [2, 3]
let b = [1, 2, 3]
delete b[0] // => true
b // => [<1 empty slot>, 2, 3]
The splice function modifies the array in-place. Note that, although we spliced the first element, we got as a result an array of two elements.
Look now at this example
let a = [1, 1, 1]
a.splice(0, 1)
a // => [1, 1]
let b = [1, 1, 1]
b.splice(2, 1)
b // => [1, 1]
We are deleting the first element from a and the last from b, but of course there's no way of telling so just looking at the result.
In the case with the nulls, the same thing is happening. Some library (Angular) is trying to figure out which element you deleted, but there's no way of knowing. This is because null === null.
Now if you use an array of empty objects, for example, there would be a way of knowing. Since {} !== {}---because each time you cast a {} you are creating a unique object---, then you could know which element is missing in an array of empty objects. The case is similar with the array [1, 2, 3].
let a = [{}, {}, {}] // lets imagine that each object has a unique id
// and we have [{}#1, {}#2, {}#3]
let [obj1, obj2, obj3] = a
obj1 === obj2 // => false, because they have different ids.
a.splice(0, 1)
a // => [{}#2, {}#3]
a.includes(obj1) // => false, because {}#1 is no longer in a
So an alternative to using an array of nulls, would be to use an array of empty objects. I think that is why the code works for you when you use objects like { index: theIndex }. But of course all depends on how smart Angular is. I bet there is a more native way of deleting an element, as #KaiserKatze points out, "it's always a bad idea to directly remove or add elements in the array if it maps to your model."
You have to understand that when you are splicing the array you're only doing that--removing an element from an array. You're not removing the "form element" when splicing the array. Instead, some foreign code is reading the array and trying to figure out --under the hood-- what you intended to do.

How to rank array elements?

I tried searching using many different keywords but I'm unable to get the result I'm looking for.
I've created a table with five values as such: 10, 3, 5, 4, 2
I have a row on top of the table which shows the rank.
So, above 10, it should say "5"
Above 3, it should say "2"
Above 5, it should say "4"
Above 4, it should say "3"
Above 2, it should say "1"
I can take care of the tabular column. It is the main script that I don't understand.
I thought I can create a duplicate array of the original one and sort the duplicate - Then compare both original and duplicate to rank them. It turns out that when I sort the duplicate array, the original is also sorted.
I thought I can create 2 duplicate arrays and not touch the original one. It still sorts all of the arrays. How can I do this then?
It sounds like you are looking for how to clone or deep copy an array. When you do something like:
var a = [5,4,3,2,1]
var b = a;
Array b is set to refer to array a. If you, however, take a slice or do something else to get a copy of the data (not something refering to the actual structure), you can clone the array. So something like:
var a = [5,4,3,2,1]
var b = a.slice(0);
will give you an array b that won't change if you change a. See articles like this for more information.
You can duplicate the array using spread. From there you can do a simple sort() to get the rank you're trying to achieve:
const arr = [10, 3, 5, 4, 2];
const sortedArr = [...arr].sort((a,b) => a > b);
console.log(sortedArr);

Find if already exists, JS and Lodash

So I have an interesting issue I am not sure how to follow, I need to use lodash to search two arrays in an object, looking to see if x already exists, lets look at a console out put:
There are two keys I am interested in: questChains and singleQuests, I want to write two seperate functions using lodash to say: find me id x in the array of objects where questChains questChainID is equal to x.
The second function would say: Find me a quest in the array of objects where singleQuests questTitle equals y
So if we give an example, you can see that questChainId is a 1 so if I pass in a 1 to said function I would get true back, I don't actually care about the object its self, else I would get false.
The same goes for singleQuests, If I pass in hello (case insensitive) I would get back true because there is a quest with the questTitle of 'Hello'. Again I don't care about the object coming back.
The way I would write this is something like:
_.find(theArray, function(questObject){
_.find(questObject.questChains, function(questChain){
if (questChain.questChainId === 1) {
return true;
}
});
});
This is just for the quest chain id comparison. This seems super messy, why? Because I am nesting lodash find, I am also nesting if. It gets a bit difficult to read.
Is this the only way to do this? or is there a better way?
Yeah it can be expressed more simply.
Try something like this:
var exampleArray = [{
questChains: [{
questChainId: 1,
name: 'foo'
}, {
questChainId: 2,
name: 'bar'
}],
singleQuests: [{
questTitle: 'hello world'
}]
}, {
questChains: [{
questChainId: 77,
name: 'kappa'
}]
}];
var result = _.chain(exampleArray)
.pluck('questChains')
.flatten()
.findWhere({ questChainId: 2 })
.value();
console.log('result', result);
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
Using chain and value is optional. They just let you chain together multiple lodash methods more succinctly.
pluck grabs a property from each object in an array and returns a new array of those properties.
flatten takes a nested array structure and flattens it into flat array structure.
findWhere will return the first element which matches the property name/value provided.
Combining all of these results in us fetching all questChain arrays from exampleArray, flattening them into a single array which can be more easily iterated upon, and then performing a search for the desired value.
Case-insensitive matching will be slightly more challenging. You'd either need to either replace findWhere with a method which accepts a matching function (i.e. find) or sanitize your input ahead of time. Either way you're going to need to call toLower, toUpper, or some variant on your names to standardize your search.

Categories

Resources