Iterate an array with content but with length 0 - javascript

I've got a list of calls with phone numbers and numbers of times that I called these numbers.
Every time I call I do something like this:
const pos = '.' + this.number; // avoid arrays large as a mobile number but keeping the number for indexing
if (typeof(this.list[pos]) === 'undefined') {
this.list[pos] = 1;
} else {
this.list[pos] = this.list[pos] + 1;
}
So I can get how many times did I call that number doing easily something like this.list[number].
The problem comes when I want to iterate this array:
<div *ngFor="let calls of callsListProvider.list">{{calls}}</div>
The length of the array is always 0 because I didn't push the content.
I don't want to use objects. Is this possible to iterate and also get the number (the index) as well?

If this.list is an Array having property names set to values other than indexes you can substitute using for..in loop for for..of loop.
let arr = [];
arr["a"] = 1;
arr["b"] = 2;
for (let prop of arr) {
console.log("for..of loop", prop);
}
for (let prop in arr) {
console.log("for..in loop", prop);
}

Related

Keep taking new element from an array

I have an array that whenever I fire a function called X I want to get random element from that array. The function will be called many times and every time I call that function I want to get element I didn't get before. I also need a reference to all elements so I can't be just removing from array Here are my 2 solutions I came up with:
Make a function that gets a random index number excluding some numbers. Problem with function like this is that it basically works that whenever the number is equal to the excluded it just generates again until it gets the right one. So when array has like a 1000 elements but only like 10 are left it will be generating forever.
Make a second array both would be the same at beginning. however I only remove from the second, and I can get random index from the second. Problem is whenever I remove that element using splice() from second array he is actually removed from both arrays. Is there a way to remove just from 1 array?
Am I just making some stupid mistakes and there is some simple solution that I am missing?
This is a perfect use case for a generator function. It will return undefined when you've exhausted the shuffled input array.
const input = [1,2,3]
function* shuffle(arr) {
arr = [...arr] // make a shallow copy, to avoid mutating the original
while(arr.length) yield arr.splice(Math.random()*arr.length|0, 1)[0]
}
let generator = shuffle(input);
console.log(generator.next().value)
console.log(generator.next().value)
console.log(generator.next().value)
console.log(generator.next().value)
You could generate a second array of indexes, shuffle it, and pop an index from it.
Once the index array is empty, refill it.
Something like
array = ['my', 'items', 'to', 'select']
// Fisher-Yates shuffle
function shuffle(ar) {
for(let i = ar.length - 1; i >= 1; i--) {
let temp = Math.floor(Math.random() * (i + 1)); // select an item to swap with
[ar[temp], ar[i]] = [ar[i], ar[temp]]; // do the swap
}
}
let indices = [];
function nextItem() {
if(indices.length == 0) {
// generate the array of indexes
indices = array.map( (item, index) => index );
shuffle(indices);
}
return array[indices.pop()];
}
// Call it a few times
for(let i = 0; i < 10; i++) {
console.log( nextItem() );
}
If you were concerned at all about performance, instead of popping from indices and regenerating a new array, you could instead have a "current index" that walks through the array, and when it gets to the end reshuffle.
For solution 2, you need your second array to be copy of the original, not a reference to the original. Try this:
const originalArray = ['one', 'two', 'three']
let copyArray = [...originalArray]

Most efficient way to update an object property within an array of objects

I am wondering what is the most efficient way to update an object property that is stored in an array with 10k+ items.
For example if I have an array that holds objects like this {name:"", price:"")
I want to replace or more like update the price values if the array contains that element already.
Check if Array contains object with name = x, if so replace the price with the newest price.
I dont want to have duplicate elements in that array so its not getting to big, i figured I should update it if a property value already exists in it.
So far I have tried several ways like with using indexOf, splice, or just for loops. I am wondering what is the best way performance wise to deal with big arrays.
let array = [
{name:"abc", price: 24},
{name:"cde", price: 25},
{name:"fgh", price: 22},
{name:"gfds", price: 21},
]
function addToArray(elem){
//check if array contains elem by checking for name property value
if(array.filter(el => el.name === elem.name).length > 0){
//update the array element that has the name of the passed elem
}
}
You've said your starting point is an array, but the most efficient way to do this is with a Map, not an array, where the key is the name and the value is either the price or an object containing the price (depending on whether you need other information).
With an Unsorted Array
But if you're doing this with an array, unless we can build / maintain the array in sorted order (see "With a Sorted Array" below), there's nothing more efficient than just looping through it looking for a previous element with the given name. filter isn't the right tool for that (you don't need the array it creates). You'd either write your own loop:
let element;
for (let index = 0, length = array.length; index < length; ++index) {
const thisElement = array[index];
if (thisElement.name === name) {
// Already have one
element = thisElement;
break;
}
}
if (element) {
element.price += price;
} else {
array.push({name, price});
}
With some JavaScript engines, you might get just that teensy bit more speed if you declared index, length, and thisElement before the loop:
let element, index, length, thisElement;
for (index = 0, length = array.length; index < length; ++index) {
thisElement = array[index];
// ...
but with others it could be the opposite. (It's not likely to be a big difference either way.)
Or use find:
const element = array.find(e => e.name === name);
if (element) {
element.price += price;
} else {
array.push({name, price});
}
Either of those provides linear lookup time. But if you were using a Map, you'd get sublinear lookup time.
With a Map
If using an object as the value:
const element = map.get(name);
if (element) {
element.price += price;
} else {
map.set(name, {name, price});
}
Or if using the price as the value:
const currentPrice = map.get(name) ?? 0; // If not found, `get` returns undefined; convert it to 0
map.set(currentPrice + price);
With a Sorted Array
If we can build / maintain the array in sorted order (you've said you can't, but maybe others finding this later can), we can do better than linear lookup by using a binary search (at the cost of slightly more overhead when inserting a new element, because all the ones after the insertion point have to be moved). It's more code, but if the search time is the main issue, it cuts search time.
const upsert = (array, name, price) => {
let left = 0;
let right = array.length;
while (left < right) {
let guess = Math.floor((left + right) / 2);
let element = array[guess];
if (element.name === name) {
// Found! Update it
element.price += price;
return;
}
if (element.name < name) {
left = guess + 1;
} else {
right = guess - 1;
}
}
// Not found, insert it
array.splice(left, 0, {name, price});
};

Sort array by key OR: Why is my for loop executing out of order?

I have an array of objects which I need placed in a certain order, depending on some configuration data. I am having a problem with itterating through the array in the proper order. I thought that if I made the array, and then stepped through with a for loop, I would be able to execute the code correctly. It is working great except in one use case, in which I add the fourth item to the array and then go back to the third.
links[0] = foo
links[1] = bar
links[2] = foobar
links[3] = a_herring
links[4] = a_shrubery
order = [] //loaded from JSON, works has the numbers 1 2 3 or 4 as values
//in this case:
order[0] = 1
order[1] = 2
order[2] = 4
order[3] = false
order[4] = 3
for(x in order){
if(order[x]){
printOrder[order[x]]=links[x]
//remember that in this case order[0] would
}
This should give me an array that looks like this:
//var printOrder[undefined,foo,bar,a_shrubbery,foobar]
But when I try to itterate through the array:
for(x in printOrder){
printOrder[x].link.appendChild(printOrder[x].image)
printOrder[x].appendChild(printOrder[x].link)
printOrder[x].appendChild(printOrder[x].text)
document.getElementById("myDiv").appendChild(printOrder[x]);
}
I get foo, bar, foobar, a_shrubbery as the output instead.
I need to either sort this array by key value, or step through it in the correct order.
Iterating over the numerically-index properties of Array instances should always be done with a numeric index:
for (var x = 0; x < printOrder.length; ++x) {
// whatever with printOrder[x]
}
Using the "for ... in" form of the statement won't get you predictable ordering, as you've seen, and it can have other weird effects too (particularly when you mix in JavaScript frameworks or tool libraries or whatever). It's used for iterating through the property names of an object, and it doesn't treat Array instances specially.
You need to create a function for finding values in an array like this:
Array.prototype.indexOf = function(value)
{
var i = this.length;
while ( i-- )
{
if ( this[ i ] == value ) return i;
}
return -1;
};
You can then use it like this:
//NOTICE: We're looping through LINKS not ORDER
for ( var i = 0; i < links.length; i++ )
{
var index = order.indexOf( i );
//It's in the order array
if ( index != -1 ) printOrder[ i ] = links[ i ];
}
REMEMBER: You need to make sure the values returned in json are integers. If they're strings, then you'll need to convert the integers to string when passed to indexOf.
The function you have in your question works as you suggest it should.
http://jsfiddle.net/NRP2D/8/ .
Clearly in this simplified case you have removed whatever error you are making in the real case.

Javascript / JQuery - How do I find the size of this array of objects?

I have this code to iterate through an array of objects:
for (vehicleIndex in scenes[sceneID].vehicles) {
vehicleID = scenes[sceneID].vehicles[vehicleIndex];
...
}
but I need to know how to determine the number of items being iterated through so that on the final item, I can execute a particular function. How do I do this?
Example in ES5:
Object.keys( scenes[sceneID].vehicles ).forEach(function( vehicle, index, arr ) {
if( index === arr.length - 1 ) {
// do something on last entry
}
});
Even tho, "last" just means the last element which was looped over. Since there is specific order within a Javascript object (they are infact unordered). However, we could sort the object key names manually by just chaining a .sort() before the .forEach()
var arraySize = scenes[sceneID].vehicles.length;
var i;
var currentItem;
for (i = 0; i < arraySize; i++) {
currentItem = scenes[sceneID].vehicles[i];
if (i == arraySize - 1) {
// do something special
} else {
// do something not so special ;-)
}
}
scenes[sceneID].vehicles should have a length property.
for (vehicleIndex in scenes[sceneID].vehicles) {
vehicleID = scenes[sceneID].vehicles[vehicleIndex];
...
}
doSomethingWithLastItem(vehicleId);
Because JS does not have block scope, by the time your loop finished vehicleId will be the last item's id.
In generic terms you can get the size of an array by accessing the .length property. You can also get the size of an object using Object.keys(obj).length.
Use this to find the length:
scenes[sceneID].vehicles.length
length is a built-in property in arrays. However, if you want to check for the last item, you have to check
scenes[sceneID].vehicles.length - 1
as arrays are zero-indexed.
Also, you should not use for...in to loop on arrays - if someone extends Array.prototype (or worse, Object.prototype), then you will not be happy. Use a normal for loop (which also allows you to use the final item easily):
var len = scenes[sceneID].vehicles.length;
for (var vehicleIndex = 0; vehicleIndex < len; vehicleIndex++) {
vehicleID = scenes[sceneID].vehicles[vehicleIndex];
//...
}
//Do something with the final item here
//Similar to this: itemFunc(vehicleID);
See this SO question for more details.

Get first element of a sparse JavaScript array

I have an array of objects in javascript. I use jquery.
How do i get the first element in the array? I cant use the array index - as I assign each elements index when I am adding the objects to the array. So the indexes arent 0, 1, 2 etc.
Just need to get the first element of the array?
If you don't use sequentially numbered elements, you'll have to loop through until you hit the first one:
var firstIndex = 0;
while (firstIndex < myarray.length && myarray[firstIndex] === undefined) {
firstIndex++;
}
if (firstIndex < myarray.length) {
var firstElement = myarray[firstIndex];
} else {
// no elements.
}
or some equivalently silly construction. This gets you the first item's index, which you might or might not care about it.
If this is something you need to do often, you should keep a lookaside reference to the current first valid index, so this becomes an O(1) operation instead of O(n) every time. If you're frequently needing to iterate through a truly sparse array, consider another data structure, like keeping an object alongside it that back-maps ordinal results to indexes, or something that fits your data.
The filter method works with sparse arrays.
var first = array.filter(x => true)[0];
Have you considered:
function getFirstIndex(array){
var result;
if(array instanceof Array){
for(var i in array){
result = i;
break;
}
} else {
return null;
}
return result;
}
?
And as a way to get the last element in the array:
function getLastIndex(array){
var result;
if(array instanceof Array){
result = array.push("");
array.pop;
}
} else {
return null;
}
return result;
}
Neither of these uses jquery.
Object.keys(array)[0] returns the index (in String form) of the first element in the sparse array.
var array = [];
array[2] = true;
array[5] = undefined;
var keys = Object.keys(array); // => ["2", "5"]
var first = Number(keys[0]); // => 2
var last = Number(keys[keys.length - 1]); // => 5
I was also facing a similar problem and was surprised that no one has considered the following:
var testArray = [];
testArray [1245]= 31;
testArray[2045] = 45;
for(index in testArray){
console.log(index+','+testArray[index])
}
The above will produce
1245,31
2045,45
If needed you could exist after the first iteration if all that was required but generally we need to know where in the array to begin.
This is a proposal with ES5 method with Array#some.
The code gets the first nonsparse element and the index. The iteration stops immediately with returning true in the callback:
var a = [, , 22, 33],
value,
index;
a.some(function (v, i) {
value = v;
index = i;
return true;
});
console.log(index, value);
If you find yourself needing to do manipulation of arrays a lot, you might be interested in the Underscore library. It provides utility methods for manipulating arrays, for example compact:
var yourArray = [];
yourArray[10] = "foo";
var firstValue = _.compact(yourArray)[0];
However, it does sound like you are doing something strange when you are constructing your array. Perhaps Array.push would help you out?

Categories