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

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

Related

Pay with least change Javascript

I'm trying to guide my users with making cash payments. Let's say the user has two 200 dollar bills, one 100 dollar bill, one 50 dollar bill, two 10 dollar bills and three 5 dollar bill.
The user wants to pay amount of 61. The function should tell the user to pay with the bills that require the least change. This means the function should tell the user to pay with one 50, one 10 and one 5. The user will receive a change of 4.
function payWithLeastChange(amount, bills) {
let billQuantities = organizeBills(bills);
billQuantities.sort((a, b) => a.value - b.value); // sort bills in ascending order
let result = [];
for (const bill of billQuantities) {
while (amount >= bill.value && bill.quantity > 0) {
let foundBill = result.find((b) => b.value === bill.value);
if (foundBill) {
foundBill.quantity++;
} else {
result.push({ value: bill.value, quantity: 1 });
}
amount -= bill.value;
bill.quantity--;
}
}
if (amount > 0) {
return "Error: Not enough bills to make the payment.";
}
return { payment: result, change: amount };
}
function organizeBills(bills) {
let billQuantities = [];
let billValues = Array.from(new Set(bills));
for (const billValue of billValues) {
billQuantities.push({
value: billValue,
quantity: bills.filter((bill) => bill === billValue).length,
});
}
return billQuantities;
}
let amount = 61;
let bills = [200, 100, 100, 100, 50, 50, 50, 20, 20, 20, 10, 10, 10, 5, 5, 5];
let payment = payWithLeastChange(amount, bills);
console.log(payment);
I call the function like so:
let amount = 61;
let bills = [200, 100, 100, 100, 50, 50, 50, 20, 20, 20, 10, 10, 10, 5, 5, 5];
let payment = payWithLeastChange(amount, bills);
console.log(payment);
I'm expecting an output of
{ payment: [
{ value: 50, quantity: 1 },
{ value: 10, quantity: 1 },
{ value: 5, quantity: 1 }
],
change: 4 }
but instead am getting "Error: Not enough bills to make the payment." I'm stuck and can't figure out what to change in the function.
I think the idea behind the code is faulty to begin with, but I can give you some pointers along the way.
What's messing up your code is the line while (amount >= bill.value && bill.quantity > 0) { where you will only add up bills that are lower than the value. But you can get a change on $100. In fact, below are possible combinations that renders change to $63.
200 (a change of $137)
100
50+50
50+20
50+10+10
50+10+5
50+5+5+5
20+20+20+10
20+20+20+5
20+20+10+10
20+20+10+5
20+10+10+10+5
20+10+10+5+5+5
So what you need is to find all these combinations (and more), and then find a combination that have a) the least amount of change, but also b) the least amount of bills, i.e. 50+10+5.
You don't need organizeBills(). It's better to just work with the bills array, but you will need to loop through that array multiple times in one way or another.

Alternative to if else chain with range

Please check the code below
if( x > 10 && x <= 100 ){
do.something(1)
}
else if ( x > 100 && x <= 1000 ) {
do.something(2)
}
else if ( x > 1000 && x <= 10000 ) {
do.something(3)
}
else if ( x > 10000 && x <= 100000 ) {
do.something(4)
}
Is the any better alternative to this if/else or switch chain?
Can we make it configurable - so that I can keep all the conditions and corresponding action in one data-structure and write code that can pick all the conditions from that structure and do actions accordingly? This will help me keep all the conditions in a separate conf file and it will be easy to just make the edits to that and not touch the code
NOTE: These ranges may be inconsistent like 100 - 200, 345 - 956, 1000 - 1200
You could take an array and use the shor circuit of Array#some for a found range.
var value = 300;
values = [
[10, () => undefined],
[100, () => do.something(1)],
[1000, () => do.something(2)],
[10000, () => do.something(3),
[100000, () => do.something(4)]
];
values.some(([v, f]) => {
if (value <= v) {
f();
return true;
}
});
This would make it configurable.
const cond = [
{min: 10, max: 100, val: 1},
{min: 100, max: 1000, val: 2},
{min: 1000, max: 10000, val: 3},
{min: 10000, max: 100000, val: 4},
]
do.something(cond.filter(c => x > c['min'] && x < c['max'])[0]['val'])
I think it is fastest way, but not for IE check here
Yes, you can write it for example in .json or .env file, and read it.

Jquery or PHP group array of objects by date field

So I have json response containing data formatted for creating a chart (using Canvas.js), it's an array of objects, each of which contains y value (which is int) and label (which is SQL date, like 2016-02-06).
What I need is to group these values by dates and get average from them. Data are sorted by date. The problem is, there can be random number of points for one day, and the other day may not have any points at all. So, I need kind of week index (calculating the week of the year is not a solution, because there can be multiple years). For example, I could have the following array:
[0] {y: 2; label: 2016-04-01} // this is week 1 from the start of the array
[2] {y: 6; label: 2016-04-02} // this is still week 1
[3] {y: 1; label: 2016-04-13} // this is week 2
[4] {y: 10; label: 2016-04-28} // this is week 3, if we count weeks only by appearance in the array, not by their actual position
[5] {y: 2; label: 2016-05-01} // this is week 4
[6] {y: 4; label: 2016-05-02} // still week 4
So I need to get these weeks somehow, and then find the average y value, so from the array above I would get:
[0] {y: 4; label: 2016-04-01}
[2] {y: 1; label: 2016-04-13}
[3] {y: 10; label: 2016-04-28}
[4] {y: 3; label: 2016-05-01}
How could this be handled? I suppose I should use PHP methods and give up trying make it on front-end, or may be there are more simple ways to handle this? Or maybe there are js chart plugins that allow automatic grouping? I would highly appreciate any possible help!
In Javascript, you could use an object for collecting and an array for the result.
This proposal utilised the answer of RobG to Get week of year in JavaScript like in PHP.
The later code is a grouping part, which takes the year and the week number and collect the data for the average calculation.
// taken from https://stackoverflow.com/a/6117889/1447675
Date.prototype.getWeekNumber = function () {
var d = new Date(+this);
d.setHours(0, 0, 0);
d.setDate(d.getDate() + 4 - (d.getDay() || 7));
return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 8.64e7) + 1) / 7);
};
var data = [{ y: 2, label: '2016-04-01' }, { y: 6, label: '2016-04-02' }, { y: 1, label: '2016-04-13' }, { y: 10, label: '2016-04-28' }, { y: 2, label: '2016-05-01' }, { y: 4, label: '2016-05-02' }],
avg = [];
data.forEach(function (a) {
var year = a.label.slice(0, 4),
week = new Date(a.label).getWeekNumber(),
key = year + '|' + week;
if (!this[key]) {
this[key] = { count: 0, sum: 0, result: { y: 0, label: a.label } };
avg.push(this[key].result);
}
this[key].count++;
this[key].sum += a.y;
this[key].result.y = this[key].sum / this[key].count;
}, Object.create(null));
console.log(avg);
You can try something like this:
$weeks = array();
foreach ($myData as $info) {
$info = json_decode($info, true);
$dateTime = new DateTime($info['label']);
$weeksKey = $dateTime->format('W-Y');
if (!isset($weeks[$weeksKey])) {
$weeks[$weeksKey] = array('y' => $info['y'], 'label' => $dateTime, 'count' => 1);
} else {
$weeks[$weeksKey]['y'] += $info['y'];
$weeks[$weeksKey]['count']++;
$weeks[$weeksKey]['label'] = $weeks[$weeksKey]['label'] > $dateTime
? $dateTime
: $weeks[$weeksKey]['label'];
}
}
$weeks = array_map(function($week) {
return json_encode(array(
'y' => $week['y'] / $week['count'],
'label' => $week['label']->format('Y-m-d')
));
}, $weeks);
var_dump($weeks); // or var_dump(array_values($weeks));

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];

IndexOf and .splice() equivalent for objects

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]

Categories