IndexOf and .splice() equivalent for objects - javascript

I have the following code (jsfiddle):
var obj = {
x: 48,
y: 13
};
var main = [{
x: 8,
y: 3
}, {
x: 82,
y: 31
}, {
x: 48,
y: 13
}, {
x: 28,
y: 31
}];
var result = $.grep(main, function (e) {
return ((e.x == obj.x) && (e.y == obj.y));
});
var index = main.indexOf(obj);
if (result.length > 0)
main.splice(index, 1);
I understand it's an array of objects. Is there any other way besides iterating it myself to retrieve the index and then splice it?

You actually already have the index. The callback of the $.grep() method takes as second argument the index. So you could write something like this:
var obj = {
x: 48,
y: 13
};
var main = [{
x: 8,
y: 3
}, {
x: 82,
y: 31
}, {
x: 48,
y: 13
}, {
x: 28,
y: 31
}];
var index;
var result = $.grep(main, function (e, i) {
var res = (e.x == obj.x) && (e.y == obj.y);
if (res) {
index = i;
}
return res;
});
if (result.length > 0)
main.splice(index, 1);
This will give you the last index, if there are multiple occurances. If you want the first index (as you would get it using indexOf on an array) you need to make sure that once index is set, it doesn't get overriden.
FIDDLE

var index = main.indexOf(obj);
The indexOf Array method does compare by equality, which for objects means their identity. You can only use it if your main array was like:
var main = [{x:8,y:3}, {x:82,y:31}, obj, {x:28,y:31}];
// ^^^
Is there any other way besides iterating it myself to retrieve the index?
If you search for something that does not compare by equality, then no. Of course you can write a special helper function for that purpose (just like indexOf is one). Don't fear to do so (and you're not missing a native alternative)!

Apparently what you are looking for is an associative array. You could rewrite your main array as an "associative array" (actually an object):
var main = {
"8": {
"3": {...}
},
"82": {
"31": {...}
},
// etc.
};
Then what you are looking for is simply:
main[obj.x][obj.y]

Related

How to stop for in loop on if statement [duplicate]

This question already has answers here:
Searching array reports "not found" even though it's found
(1 answer)
How to stop a JavaScript for loop?
(5 answers)
Closed 4 years ago.
var enemies = [
{
nick: "Bob1",
x: 12,
y: 21
},
{
nick: "Bob2",
x: 20,
y: 21
},
{
nick: "Bob3",
x: 12,
y: 21
}
]
var me = {
x: 19,
y: 20
}
for (var x in enemies) {
var enemy = enemies[x];
if ((Math.abs(me.x - enemy.x) <= 1 && Math.abs(me.y - enemy.y) <= 1)) {
console.log("Enemy In range");
} else {
console.log("Enemies not in range");
}
}
Hello everyone. I have an array of enemies, and i am checking if some enemy is 1 field away from my x or y position. And i want to log only once if it is or it's not. As you can see now, it check for every enemy and it logs for every enemy. Which is is not what i want. I just want to simply check if there is any enemy 1 field away of my x or y position or not, and get simple response yes, or no. Im totally newbie, but if you have any hint for me, that would be awesome!
Have a boolean such as found that starts off as false. When you find it, set it to true and break. Then after the loop have if (found) ... else .... This moves the printing out of the loop, ensuring you only get it once.
Furthermore, you can compress this a lot by using the new some method, which internally does basically the same thing (just faster):
let found = enemies.some(enemy => enemyIsClose(enemy, me));
If you actually need to find which enemy is close, find instead of some will return the first one, and filter will return all of them.
You can use Array#find instead to early return from the loop if a matching element is found.
var enemies = [{
nick: "Bob1",
x: 12,
y: 21
},
{
nick: "Bob2",
x: 20,
y: 21
},
{
nick: "Bob3",
x: 12,
y: 21
}
]
var me = {
x: 19,
y: 20
}
var enemy = enemies.find(enemy => (Math.abs(me.x - enemy.x) <= 1 && Math.abs(me.y - enemy.y) <= 1));
if (typeof enemy !== undefined) {
console.log("Enemy In range");
} else {
console.log("Enemies not in range");
}
You can simply filter the object using Array.filter and find your collection.
var enemies = [
{
nick: "Bob1",
x: 12,
y: 21
},
{
nick: "Bob2",
x: 20,
y: 21
},
{
nick: "Bob3",
x: 12,
y: 21
}
]
var me = {
x: 19,
y: 20
}
var enemy=enemies.filter(item=>{
return me.x-item.x<=1
})[0];
console.log(enemy);
In order for your code to check if an enemy is nearby, it would need to iterate through all of their positions, there's no way around this but if you only want to reduce the amount of console logs you can use this code:
var isEnemyFound = true;
for (let x of enemies) {
var enemy = enemies[x];
if ((Math.abs(me.x - enemy.x) <= 1 && Math.abs(me.y - enemy.y) <= 1)) {
console.log("Enemy In range");
break;
} else {
isEnemyFound = false;
}
}
if(!isEnemyFound){
console.log("Enemies not in range");
}
Here is a better soultion, overall. The previous one I did on the fly.
Try playing around with it and see if this is what you need. I truly hope it is :)
If you change your (me - values) to '13' it's still going to be the same result, but try changing it to '14' for example, and see the how the logic plays out. This is a overkill, but if you want to advance your game, it's a great solution, because you can create as many variations and possibilities as you want, on the fly, just calling next(); in order to create the next value. Just a suggestion. Good luck!
let enemies = [
{nick: "Bob1", x: 12, y: 21},
{nick: "Bob2", x: 12, y: 21},
{nick: "Bob3", x: 12, y: 21}
];
let me = {
x: 12,
y: 21
}
function* range() {
for (let x in enemies) {
let enemy = enemies[x];
while(Math.abs(me.x - enemy.x) <=1 && Math.abs(me.y - enemy.y) <= 1) {
yield "Enemy is in range";
break; // closes iterator, triggers return
}
yield "Enemy is NOT in range";
break;
}
}
var gen = range(); // Creating a generator
console.log(gen.next().value); // Then we use it to get the next value

Recreating a lookup table in Javascript

So I have a spreadsheet for retrieving membership rates, the columns are Age, Duration & Rate. You simply look down the age column to find the age of the customer, then when you find that age you keep heading down to match it to the correct Duration, then in the final column will be the rate. A (very) small version of that might look like this;
Age,Duration,Rate
18,10,1.33
18,11,1.5
18,12,1.8
19,10,1.4
19,11,1.65
19,12,1.88
20,10,1.48
20,11,1.73
20,12,1.98
So someone age 19, duration 11 has a rate of 1.65. Someone age 20 with a duration of 12 has a rate of 1.98 - easy!
My question is two parts, I want to convert this into a web page where someone enters the age and duration to retrieve the rate. I'm pretty sure my best option for this is a two dimensional array like so;
var array = [[18,10,1.33],[18,11,1.5],[18,12,1.8] .. and so on];
are there any better options for this?
The second question is how do I best iterate over a two dimensional array (if that ends up being the best solution)? As I touched upon before I would need to be able to have an iteration that returns the rate based on a two criteria search. I believe this would consist of a two part iteration but iteration is such a weak spot for me that trying to grasp where in the loops to put my iterations is just brain melting. I think it would look something like so;
for (var i = 0; i < array.length; i++){
for (var j = 0; j < array[i].length; j++){
//Do something... I think something like this
If array[][j] == ageImLookingFor && array[][j+1] == durationImLookingFor
then return array[][j+2] (which is the rate)
}
}
Any help, advice or ideas I would be super grateful
A better option than using an array is to use an object (or Map) with properties (keys) that correspond to valid combinations of age and duration, effectively indexing your data by that key:
var list = {
'18_10': { age: 18, duration: 10, rate: 1.33 }
'18_11': { age: 18, duration: 11, rate: 1.5 },
'18_12': { age: 18, duration: 11, rate: 1.8 },
// .. and so on
};
This way you do not have to iterate over an array (cf. your question #2), but given an age and a duration (let's say in variables that have those names), you can write this to get the matching item:
var item = list[age + '_' + duration];
Of course, you should check that age and duration are valid integer numbers and that the item could be undefined when the combination is not known.
Here is a simple snippet (without any checks) you could use to base your web form on. It builds the above mentioned object from an array having the data.
// Data in array -- will be keyed later
var arr = [
{ age: 18, duration: 10, rate: 1.33 },
{ age: 18, duration: 11, rate: 1.5 },
{ age: 18, duration: 12, rate: 1.8 },
{ age: 19, duration: 10, rate: 1.4 },
{ age: 19, duration: 11, rate: 1.65 },
{ age: 19, duration: 12, rate: 1.33 },
{ age: 20, duration: 10, rate: 1.48 },
{ age: 20, duration: 11, rate: 1.73 },
{ age: 20, duration: 12, rate: 1.98 },
];
// Build map, keyed by age/duration. It will look like:
// {
// '18_10': { age: 18, duration: 10, rate: 1.33 },
// '18_11': { age: 18, duration: 11, rate: 1.33 },
// ...etc
// }
mapByAgeDuration = {};
for (var i=0; i < arr.length; i++) {
mapByAgeDuration[arr[i].age + '_' + arr[i].duration] = arr[i];
}
// Fast retrieval function:
function getItemFor(age, duration) {
return mapByAgeDuration[age + '_' + duration];
}
// I/O
var button = document.getElementById('findRate');
var inputAge = document.getElementById('age');
var inputDuration = document.getElementById('duration');
var outputRate = document.getElementById('rate');
button.onclick = function() {
var age = inputAge.value;
var duration = inputDuration.value;
// Retrieve item for this age and duration
var item = getItemFor(age, duration);
// Output rate
outputRate.textContent = item !== undefined ? item.rate
: 'not a valid combination';
}
Age (18 - 20): <input id="age"><br>
Duration (10 - 12): <input id="duration"><br>
<button id="findRate">Find Rate</button><br>
Rate: <span id="rate"></span><br>
Q1: You can use a hash table for your lookup.
var data = [[18, 10, 1.33], [18, 11, 1.5], [18, 12, 1.8], [19, 10, 1.4], [19, 11, 1.65], [19, 12, 1.88], [20, 10, 1.48], [20, 11, 1.73], [20, 12, 1.98]],
object = {};
data.forEach(function (a) {
object[a[0]] = object[a[0]] || {};
object[a[0]][a[1]] = a[2];
});
// usage
document.write(object[19][11] + '<br>');
document.write(object[20][12] + '<br>');
document.write('<pre>' + JSON.stringify(object, 0, 4) + '</pre>');
Q2: A proposal with Array#some()
If you have sorted data, you could insert a short circuit, if the values are greater then needed.
var data = [[18, 10, 1.33], [18, 11, 1.5], [18, 12, 1.8], [19, 10, 1.4], [19, 11, 1.65], [19, 12, 1.88], [20, 10, 1.48], [20, 11, 1.73], [20, 12, 1.98]],
object = {};
function getValue(p1, p2) {
var result;
data.forEach(function (a) {
if (a[0] === p1 && a[1] === p2) {
result = a[2];
return true;
}
// short circuit for not found values
return a[0] > p1;
});
return result;
}
// usage
document.write(getValue(19, 11) + '<br>');
document.write(getValue(20, 12) + '<br>');
Another approach is to leverage on the array.filter function.
You have to reshape your data into an objects array:
var rates = [
{'age':'18','duration':'10','rate':'1.33'},
{'age':'18','duration':'11','rate':'1.5'},
{'age':'19','duration':'12','rate':'1.8'}
];
function filterRate(item){
if(item.age == this.age && item.duration == this.duration)
return item;
}
function getRateByAgeDuration(age, duration){
res = null;
try{
res = rates.filter(filterRate, {'age':age, 'duration':duration})[0].rate;
}
catch(ex){ console.log(ex);}
return res;
}
document.write(getRateByAgeDuration('18', '10'));
It depends. If you use hashes, you will have O(1) time on average, but O(n) on worst case.
If you prefer to optimize the worst case, you can use binary search to achieve O(lg n) both on average and worst cases.
function binarySearch(array, data, from=0, to=array.length) {
if(from >= to) return -1; // not found
var m = Math.floor((from+to)/2);
for(var i=0; i<data.length; ++i) {
if(data[i] < array[m][i]) return binarySearch(array, data, from, m);
if(data[i] > array[m][i]) return binarySearch(array, data, m+1, to);
}
return m;
}
var idx = binarySearch(array, [18,12]);
if(idx > -1) array[idx];

How to convert array of objects to array of arrays in js

var ph = [{x:1231,y:121},{x:131,y:11},{x:231,y:21},{x:123,y:12}]
I want to convert that to
[[1231,121],[131,11],..]
So far I have tried Array.prototype.slice.call but it is not working for me.
Use Array.prototype.map method. It iterates over an array and creates new one with the items returned by each iteration:
var ph = [{x:1231,y:121},{x:131,y:11},{x:231,y:21},{x:123,y:12}];
var result = ph.map(function(el) {
return [el.x, el.y];
});
document.body.innerHTML = '<pre>' + JSON.stringify(result, null, 4) + '</pre>'
ES6 syntax would also allow more concise notation:
var result = ph.map(el => [el.x, el.y]);
You can use map() to iterate and generate new array based on old array elements.
var arr = [{
x: 1231,
y: 121
}, {
x: 131,
y: 11
}, {
x: 231,
y: 21
}, {
x: 123,
y: 12
}];
var res = arr.map(function(v) {
return [v['x'], v['y']];
});
document.write('<pre>' + JSON.stringify(res, null, 3) + '</pre>');
You can use Array.prototype.map. In ES6 it can done like
var ph = [{x:1231,y:121},{x:131,y:11},{x:231,y:21},{x:123,y:12}];
var arr = ph.map(elem => [elem.x, elem.y]);
document.write(JSON.stringify(arr));

passing parameter to jquery $.grep

If I have this pice of code which checks is something already exist inside array
var array = [ 1, 5, 12, 31, 7, 69 ];
var array = $.grep(array, function(n, i) {
return (n == 112);
});
alert(array.length);
my question is simple one: How can I pass variable to this grep function and to use it instead of hardcoded 112 value inside expression?
You can just pass a variable from the outside. JavaScript's lexical scope allows for variables from the outside scope to be passed into deeper functions.
http://jsfiddle.net/ag1djcjm/
var array = [ 1, 5, 12, 31, 7, 69];
var code = 112;
// No need to declare variables twice
array = $.grep(array, function(n, i) {
return (n == code);
});
alert(array.length);
Try like this
var array = [ 1, 5, 12, 31, 7, 69 ];
var val=112;
// remove var from here it'll re-declare same variable
array = $.grep(array, function(n, i) {
return (n == val);
});
alert(array.length);
JSFIDDLE
You can do it by javascript's .filter() also
Like this
var array = [ 1, 5, 12, 31, 7, 69 ];
var val=112;
array = array.filter(function(n) { return n == val; });
alert(array.length);
JSFIDDLE
So, what you're basically trying to do, it determine if an array of numbers contains a certain number (variable).
There's no need to over-complicate this with grep. Just use indexOf:
var array = [ 1, 5, 12, 31, 7, 69 ],
search = 112,
hasNumber = array.indexOf(search) !== -1;
// Using this so that it's visible in the snippet.
document.body.textContent = hasNumber;
Just define the value you need to filter before calling $.grep function. See example below
var array = [1, 5, 12, 31, 7, 69],
filterValue = 5;
var newArray = $.grep(array, function (n, i) {
return n == filterValue;
});
Since you're redefining the array, I created a new variable newArray and assigned filtered values to that, but you can still assign it to array variable.
Code:
function filter ( data, val ) {
return $.grep(data, function ( n, i ) {
return (n == val);
});
}
Test:
var array = [ 1, 5, 12, 31, 7, 69];
var test = filter(array, 112);
alert(test.length);
Could create a function outside of $.grep() , to be called for each item in array ; could also pass additional parameters to function . Also try defining different variable names for input array , resulting array
var array = [ 1, 5, 12, 31, 7, 69 ], num = 112
, compare = function(n, i) { return n === num }
var res = $.grep(array, compare);

Pushing a values in a Particular format to an array

I want to create an array dynamically which should be having a value in the format of
var dat1 = [
{ x: 0, y: 32.07 },
{ x: 1, y: 37.69 },
{ x: 2, y: 529.49 },
{ x: 3, y: 125.49 },
{ x: 4, y: 59.04 }
];
I want to store the whole thing in data into an array dynamically. I am getting these values from the json data. And I want an array to be in this format. How can I create it?
I tried this:
$.each(r_data, function(key, val) {
data1.push([{
x : i,
y : parseFloat(val.something)
}]);
i++;
});
...but didn't get the result I wanted.
Assuming you have
var data1 = [];
...and probably
var i = 0;
...prior to your code, your code will produce this structure:
var data1 = [
[ { x: 0, y: 32.07 } ],
[ { x: 1, y: 37.69 } ],
[ { x: 2, y: 529.49 } ],
[ { x: 3, y: 125.49 } ],
[ { x: 4, y: 59.04 } ]
];
Note how you've ended up with an array where each entry is another array, which in turn contains the object with the x and y properties.
I suspect you want:
var data1 = [];
var i = 0;
$.each(resultBar_data, function(key, value) {
data1.push({
x : i,
y : parseFloat(value.averagePrice)
});
i++;
});
...which just pushes the objects directly on data1, without wrapping them in extra arrays (note I've removed the [] around what's being pushed). You would access those entries like this:
console.log("The first entry is " + data1[0].x + "," + data1[0].y);
console.log("The second entry is " + data1[1].x + "," + data1[1].y);
format is an array of objects. In your following code, you are trying to push an array [{x:i, y:parseFloat(value.averagePrice)}] to the format array:
$.each(resultBar_data, function(key, value) {
format.push([{ /*array start*/
x : i,
y : parseFloat(value.averagePrice)
}] /*array end*/
);
i++;
});
Remember square brackets denote an array.
I think to fix your problem it should be:
/*i just removed the square brackets so that push now pushes an object,
not an array with a single object*/
format.push({
x : i,
y : parseFloat(value.averagePrice)
});
Hope this helps, please ask if you need more information or if I misunderstood your question!

Categories