I have an array of objects. Each object has two properties, id
and quantity
.
I wrote a function to add an object to the array. But the function must check if the object already exists within the array. If it does exist the function must increment the quantity
property. Otherwise it must add a new object.
Is there an idiomatic way to achieve the same result? I feel that looping over the array to compare the id of the object at index against the id passed as argument is not the right approach.
I am also suspicious of the found = false
temporary variable.
Finally, in Ruby we are encouraged to break down long methods into smaller ones. I am aware that JavaScript is not the same, but I feel that this could be further refactored.
addItem = function(id, items) {
var found, i;
found = false;
i = 0;
while (i < items.length) {
if (id === items[i].id) {
items[i].quantity++;
found = true;
break;
}
i++;
}
if (!found) {
return items.push({
id: id,
quantity: 1
});
}
};
4 Answers 4
I also don't like the found
variable, I think you should extract that part to a indexOf
method to return the index of the item you want to change, as you only seem to want to do items[i].quantity++;
once. This way you can return
the index directly. I would also use a for
loop rather than while
, as you're looping through a fixed number of items.
indexOf = function(id, items) {
var i = 0;
var len = items.length;
for (i = 0; i < len; i++) {
if (id === items[i].id) {
return i;
}
}
return -1;
}
This way you can call your indexOf
function from your addItem
function and check if the return value is -1, if it is then you call items.push
, if it's not -1 then you perform items[index].quantity++;
.
Instead of using an array structure, you could use more of a Set
structure, which can be implemented in JavaScript as seen in this StackOverflow question, assuming that your id
can easily be converted to a string.
-
\$\begingroup\$ Thanks.
id
is already a string. Can I ask you why afor
loop is preferred to awhile
loop? \$\endgroup\$Jumbalaya Wanton– Jumbalaya Wanton2014年03月04日 21:01:24 +00:00Commented Mar 4, 2014 at 21:01 -
\$\begingroup\$ Also, doesn't adding this method now require two loops? I would still need the loop inside the
addItem
method to increment the quantity at index, right? \$\endgroup\$Jumbalaya Wanton– Jumbalaya Wanton2014年03月04日 21:04:55 +00:00Commented Mar 4, 2014 at 21:04 -
\$\begingroup\$ @JumbalayaWanton When you iterate over an array index-by-index, programmers are more used to for-loops. Technically they have about the same performance, but for-loop is more compact, as the initialization, condition-check and increment is done on the same line. \$\endgroup\$Simon Forsberg– Simon Forsberg2014年03月04日 21:06:36 +00:00Commented Mar 4, 2014 at 21:06
-
1\$\begingroup\$ @JumbalayaWanton No, you don't need to use a loop inside
addItem
, just increment the quantity at the index stored in theindex
variable (that you retreived from theindexOf
function), as long as that index is >= 0 (meaning that a match was found). \$\endgroup\$Simon Forsberg– Simon Forsberg2014年03月04日 21:07:54 +00:00Commented Mar 4, 2014 at 21:07
Why are you using an array to do the job of a map? Use the right data structures....
... instead, use a hashmap (associative array) of [id, quantity]
. If id
exists in hashmap, then increment up the value at hashmap[id]
.
-
2\$\begingroup\$ Brief answers which do not explain why a suggestion is better are often converted to comments on CodeReview. I have taken the liberty of expressing why this 'obvious' suggestion is better than what the oP has... please feel free to revise if you want to. \$\endgroup\$rolfl– rolfl2014年03月04日 22:09:55 +00:00Commented Mar 4, 2014 at 22:09
-
\$\begingroup\$ I'm not sure I understand this answer. Do you mean to create a hash or arrays? Like
{[1, 1], [2, 1]}
etc...? \$\endgroup\$Jumbalaya Wanton– Jumbalaya Wanton2014年03月04日 22:53:45 +00:00Commented Mar 4, 2014 at 22:53 -
\$\begingroup\$ @JumbalayaWanton - see this quick blog about it \$\endgroup\$rolfl– rolfl2014年03月04日 23:09:39 +00:00Commented Mar 4, 2014 at 23:09
-
\$\begingroup\$ @rolfl OK. I think I understand. I could use
{ id: [id, quantity] }
... so, assuming ids 1 and 2, with values 1 and 1 respectively, I would do{ 1: [1, 1], 2: [2, 1] }
. Then I can check for the presence of the id in the hash... sorry to sound like a dunce, but this is new stuff for me. \$\endgroup\$Jumbalaya Wanton– Jumbalaya Wanton2014年03月04日 23:45:14 +00:00Commented Mar 4, 2014 at 23:45 -
1\$\begingroup\$ This is essentially the same thing that I say in the bottom of my answer, where I link to this SO question that shows how it can be done. \$\endgroup\$Simon Forsberg– Simon Forsberg2014年03月05日 07:24:05 +00:00Commented Mar 5, 2014 at 7:24
I don't really like that found flag. Also using while or for loops ads a bit of extra code to write to get the value of an arrays items. I would use the filter method for this case.
addItem = function(id, items) {
var foundItem = items.filter(function(item) {
return item.id === id;
})[0];
if (foundItem) {
foundItem.quantity++;
} else {
//return the new length here cause that is what you did
return items.push({
id: id,
quantity: 1
});
}
};
You don't need to use the found
variable. Use return
when done.
You can also use for
instead of while
.
addItem = function(id, items) {
for (var i = 0; i < items.length; i++) {
if (id === items[i].id) {
items[i].quantity++;
return;
}
}
return items.push({
id: id,
quantity: 1
});
};