Why would my find method return undefined? - javascript

I'm recreating a number of Underscore.js methods to study JavaScript and programming in general.
Below is my attempts to recreate Underscore's _.find() method.
var find = function(list, predicate) { // Functional style
_.each(list, function(elem){
if (predicate(elem)) {
return elem;
}
});
};
var find = function(list, predicate) { // Explicit style
if (Array.isArray(list)) {
for (var i = 0; i < list.length; i++) {
if (predicate(list[i])) {
return list[i];
}
}
} else {
for (var key in list) {
if (predicate(list[key])) {
return list[key];
}
}
}
};
My second find method, which is using for loop and for in loop works. Whereas, my first find method would return undefined. I believe both should do the same work. However, they don't. Would someone please point what is going on?

Your return is only returning from the inner (nested) function and your find function is indeed not returning anything, hence the undefined.
Try this instead:
var find = function(list, predicate) { // Functional style
var ret;
_.each(list, function(elem){
if (!ret && predicate(elem)) {
return ret = elem;
}
});
return ret;
};

Related

Javascript: move objects from one array to another: Best approach?

I have two arrays, called 'objects' and 'appliedObjects'. I'm trying to come up with an elegant way in Javascript and/or Angular to move objects from one array to another.
Initially I did something like this:
$scope.remove = function () {
angular.forEach($scope.appliedObjects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.objects.push(element);
$scope.appliedObjects.splice(index, 1);
}
});
}
$scope.add= function () {
angular.forEach($scope.objects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.appliedObjects.push(element);
$scope.objects.splice(index, 1);
}
});
}
But then I realized that when the value was removed from the looping array, and it would not add or remove every other item, since it went by index.
Then I tried using a temporary array to hold the list of items to be added or removed, and I started getting strange referential issues.
I'm starting to spin a bit on what the best solution to this problem would be...any help and/or guidance would much appreciated.
function moveElements(source, target, moveCheck) {
for (var i = 0; i < source.length; i++) {
var element = source[i];
if (moveCheck(element)) {
source.splice(i, 1);
target.push(element);
i--;
}
}
}
function selectionMoveCheck(element) {
if (element.selected) {
element.selected = false;
return true;
}
}
$scope.remove = function () {
moveElements($scope.appliedObjects, $scope.objects, selectionMoveCheck);
}
$scope.add = function () {
moveElements($scope.objects, $scope.appliedObjects, selectionMoveCheck);
}
When a construct does too much automatically (like forEach, or even a for-loop, in this case), use a more primitive construct that allows you to say what should happen clearly, without need to work around the construct. Using a while loop, you can express what needs to happen without resorting to backing up or otherwise applying workarounds:
function moveSelected(src, dest) {
var i = 0;
while ( i < src.length ) {
var item = src[i];
if (item.selected) {
src.splice(i,1);
dest.push(item);
}
else i++;
}
}
You are altering the array while iterating on it, you will always miss some elements.
One way of doing it would be to use a third array to store the references of the objects that need to be removed from the array:
// "$scope.add" case
var objectsToRemove = [];
$scope.objects.forEach(function (value) {
if (value.selected) {
value.selected = false;
$scope.appliedObjects.push(value);
objectsToRemove.push(value);
}
});
objectsToRemove.forEach(function (value) {
$scope.objects.splice($scope.objects.indexOf(value), 1);
});
If you wish to move simply whole array you could do:
appliedObjects = objects;
objects = []
Of course it won't work if they were parameters of a function!
Otherwise I cannot see other way than copying in the loop, e.g.
while (objects.length) {
appliedObjects.push(objects[0]);
objects.splice(0,1);
}
or if you like short code :) :
while (objects.length) appliedObjects.push(objects.splice(0,1));
check fiddle http://jsfiddle.net/060ywajm/
Now this maybe is not a fair answer, but if you notice you are doing alot of complicated object/array manipulations, you should really check out lodash or underscore library. then you could solve this with on liner:
//lodash remove function
appliedObjects.push.apply( appliedObjects, _.remove(objects, { 'selected': true}));
//or if you want to insert in the beginning of the list:
appliedObjects.splice(0, 0, _.remove(objects, { 'selected': true}));
This is a first pass at what I think will work for you. I'm in the process of making a test page so that I can test the accuracy of the work and will update the tweaked result, which hopefully there will not be.
EDIT: I ran it and it seems to do what you are wanting if I understand the problem correctly. There were a couple of syntax errors that I edited out.
Here's the plunk with the condensed, cleaned code http://plnkr.co/edit/K7XuMu?p=preview
HTML
<button ng-click="transferArrays(objects, appliedObjects)">Add</button>
<button ng-click="transferArrays(appliedObjects, objects)">Remove</button>
JS
$scope.transferArrays = function (arrayFrom, arrayTo) {
var selectedElements;
selectedElements = [];
angular.forEach(arrayFrom, function(element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function(element) {
arrayTo.push(arrayFrom.splice(
arrayFrom.map(function(x) {
return x.uniqueId;
})
.indexOf(element.uniqueId), 1));
});
};
Old code
$scope.remove = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.appliedObjects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.objects.push($scope.appliedObjects.splice(
$scope.appliedObjects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
$scope.add = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.objects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.appliedObjects.push($scope.objects.splice(
$scope.objects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
You can use this oneliner as many times as many items you need to move from arr1 to arr2 just prepare check func
arr2.push(arr1.splice(arr1.findIndex(arr1El => check(arr1El)),1)[0])
You can use this to concat 2 arrays:
let array3 = [...array1, ...array2];

_.each function for looping in JS issue

We are asked to do the following:
Write a function called checkValue that searches an array for a value. It takes an array and a value and returns true if the value exists in the array, otherwise it returns false.
var helloArr = ['bonjour', 'hello', 'hola'];
var checkValue = function(arr, val) {
//checks if the val is in arr
}
Rewrite checkValue using _.each.
here is what I have to itterate over helloArr using _.each:
var helloArr = ['bonjour', 'hello', 'hola'];
var checkValue = function (num) {
return num;
}
checkValue('hola');
var output = us.each(helloArr, function(num){
if (checkValue())
{return true;}});
return output;
What am I doing wrong? When I run it with node, theres no errors but no output either. I know you can use _.find to do this but the spec is asking to itterate over the array and find the value using _.each.
In your second example, you're calling checkValue without a parameter, so it's going to return undefined, which is a falsey value, and the callback to each never returns anything.
Then again, it doesn't normally need to return anything anyway. _.each returns the list it operates on.
_.each is like a for-loop; consider treating it more like one.
function checkValue_original1(arr, val) {
for (var i = 0; i < arr.length; i++) {
if (val == arr[i]) return true;
}
return false;
}
function checkValue_original2(arr, val) {
return arr.indexOf(val) >= 0;
}
function checkValue_us_each(arr, val) {
var found = false;
_.each(arr, function(element, index, list) {
if (element == val) found = true;
});
return found;
}

How to filter an array?

Guys, please don't answer me to use a JavaScript library to solve this problem, I'm using VanillaJS.
Suppose I have an array with 10,000 string records, same as following:
var arr = [
'John',
'Foo',
'Boo',
...
'Some',
'Beer'
];
Please note that the array doesn't follow any sort.
Now, I want to find items with oo in the text, what is the best way to do? Should I populate a new array or just pop items that don't match with the criteria?
You can make use of the filter method, which will create a new array with all the elements that passes the condition.
arr.filter(function(x){ return x.indexOf ('oo') > -1});
If you want to use filter method in every browser you could add the polyfill method (see link) in your code.
Another option (slightly faster) with basic javascript would be:
Looping trough the array with a simple for loop and test on your condition.
var filtered = [];
for(var i=0, length=arr.length; i<length; i++){
var current = arr[i];
if(current.indexOf('oo') > -1){
filtered.push(current);
}
}
my approch
forEach function
function forEach(array, action) {
for(var i=0; i<array.length; i++)
action(array[i]);
}
partial function
function asArray(quasiArray, start) {
var result = [];
for(var i = (start || 0); i < quasiArray.length; i++)
result.push(quasiArray[i]);
return result;
}
function partial(func) {
var fixedArgs = asArray(arguments, 1);
return function() {
return func.apply(null, fixedArgs.concat(asArray(arguments)));
};
}
contains method for String obj
if (!String.prototype.contains) {
String.prototype.contains = function (arg) {
return !!~this.indexOf(arg);
};
}
filter function:
function filter(test, array) {
var result = [];
forEach(array, function(element) {
if (test(element))
result.push(element);
});
return result;
}
test function for test array items
function test(key, el) {
return el.contains(key);
}
finally
filter(partial(test, 'oo'), arr);
NO shortcuts ( p.s. you said I want to find items , not filter - hence my answer)
simple loop :
var g=arr.length; //important since you have big array
for( var i=0;i<g;i++)
{
if ( g[i].indexOf('oo')>-1)
{
console.log(g[i]);
}
}
If you want to filter (without polyfill)
var g=arr.length; //important since you have big array
var h=[];
for( var i=0;i<g;i++)
{
if ( g[i].indexOf('oo')>-1)
{
h.push(g[i]);
}
}
//do something with h
A very simple way, use forEach:
var result = [];
arr.forEach(function (value) {
if (value.indexOf('oo') !== -1) {
result.push(value);
}
});
or map:
var result = [];
arr.map(function (value) {
if (value.indexOf('oo') !== -1) {
result.push(value);
}
});

Function with forEach returns undefined even with return statement

I'm just making a function for checking a value of something in my object array, but for some reason it keeps returning undefined. Why is that?
Demo: http://jsfiddle.net/cNYwz/1/
var data = [{
"Key": "1111-1111-1111",
"Email": "test#test.com"
}, {
"Key": "2222-2222-2222",
"Email": "test#boo.com"
}];
function getByKey(key) {
data.forEach(function (i, val) {
if (data[val].Key === key) {
return data[val].Key;
} else {
return "Couldn't find";
}
});
}
var asd = getByKey('1111-1111-1111');
console.log(asd);
In your function, you're returning from the function passed to forEach, not from getByKey.
You could adapt it like this :
function getByKey(key) {
var found = null;
data.forEach(function (val) {
if (val.Key === key) {
found = val;
}
});
return found;
}
But this would iterate over all elements, even if the item is immediately found. That's why you'd better use a simple for loop :
function getByKey(key) {
for (var i=0; i<data.length; i++) {
if (data[i].Key === key) {
return data[i];
}
}
}
Note that I also adapted your code to return the value, not the key. I suppose that was the intent. You might also have been confused with another iteration function : the first argument passed to the callback you give to forEach is the element of the array.
Your function getByKey has no return statement. The two returns are for the anonymous function used by forEach.
You're not returning anything to the outer scope, try this alternative:
function getByKey(key) {
var result = data.filter(function (i, val) {
return data[val].Key == key;
});
return result.length ? result : 'Not found';
}
Try storing the positive result as a variable, and then returning that variable (or a "Couldn't find" in case nothing is written) at the end of the function after the forEach loop.
function getByKey(key) {
var result;
data.forEach(function (val, i) {
if (data[val].Key === key) {
result = data[val].Key;
}
});
return result || "Couldn't find";
}
In addition to the ideas in the other answers, you're better off using Array.prototype.some, rather than forEach. That will let you stop when you find the first match:
function getByKey(key) {
var found = null;
data.some(function (val) {
if (val.Key === key) {
found = val;
return true; //stop iterating
}
});
return found;
}
You might also consider using filter, which can return an array containing just the objects where key matches:
function filter_array_by_key(key){
return data.filter(function(v){
return v.Key===key;
};
}
To get the first matching object, you can then use filter_array_by_key(key)[0], which will yield undefined if there was no match.

How to convert javascript array to function

I have an array of arbitrary values. I Wrote a function that transforms the array to an array of functions that return the original values, so instead of calling a[3], I will call a3.
Here is my code which does not work? code. It gives this error Cannot call method '1' of undefined.
var numToFun = [1, 2, { foo: "bar" }];
var numToFunLength = numToFun.length;
function transform(numTo) {
for (var i = 0; i < numToFunLength; i++) {
(function(num){
numTo.unshift(function() {
return num;
});
}(numTo.pop()))
}
}
var b = transform(numToFun);
console.log(numToFun);
console.log(b[1]());​
Others have already answered your question while I was writing mine but I will post it anyway - this may be somewhat easier to follow without all of those popping and unshifting:
function transform(numTo) {
var r = [];
for (var i = 0; i < numTo.length; i++) {
r[i] = (function (v) {
return function() {
return v;
}
}(numTo[i]));
}
return r;
}
(I have also changed the hard-coded length from numToFunLength to numTo.length so the transform() function would work for other inputs than only the global numToFun variable.)
See DEMO.
UPDATE: even more elegant way to do it using the Sugar library:
function transform(array) {
return array.map(function (v) {
return function() {
return v;
}
});
}
I like this syntax because it makes it more explicit that you want to map an array of values to an array of functions that return those values.
See DEMO.
Your function transform does not return anything. That is why b is undefined.
return numTo;
jsFiddle Demo
On the other hand, the array will be passed to the function as a reference anyways, so the original array will be changed. It is not a problem if you don't return anything, just omit the var b = transform(numToFun); line and simply write transform(numToFun).
Your transform function isn't returning anything. So b is undefined

Categories

Resources