array.indexOf that matches with a function? - javascript

Trying to find a clean way to find the index of an array item in JS via a function (specifically for this case, regex-matching a string).
In Ruby, I've been using array.index {|x| fn_to_match x}, is there a JS equivalent of this? indexOf doesn't seem to be usable in this way, unless I'm doing something horribly wrong (very possible)
(FWIW, I'm running this in Node)

I'd make a helper function for that:
function indexOfMatch(array, fn) {
var result = -1;
array.some(function(e, i) {
if (fn(e)) {
result = i;
return true;
}
});
return result;
}

I know this is an old question, but thought I'd should update to say there's now the findIndex() method which returns the index of the first element in the array that satisfies the provided testing function (or -1 if no match found), so you can now use
let index = array.findIndex(fn_to_match);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

Since the normal .indexOf doesn't accept functions, you can overload it like this:
Array.prototype.indexOf = (function(){
var old = Array.prototype.indexOf;
return function(v){
var i, l;
if( typeof v != "function" ) {
return old.apply( this, arguments );
}
for( i = 0, l = this.length; i < l; ++i ) {
if( v.call( this, this[i], i, this ) === true ) {
return i;
}
}
return -1;
};
})();
Then:
var reTest = RegExp.prototype.test;
["ddd", "aaa", "asd"].indexOf( reTest.bind( /asd/ ) );
//2
["ddd", "aaa", "asd"].indexOf( reTest.bind( /ddd/ ) );
//0
Note that this is usable even if you don't wanna overload.. just do Array.prototype.index = ... or whatever name you want for it.

There is no native solution. You may use array.some like this:
var index = -1;
if (searchedArray.some(function(v, i) {
if (/*v is ok*/) { index = i; return true; }
})) {
/* use index */
} else {
/* not found */
}

The most efficient way:
var arr = ['bacon', 'blue', 'beach']
var i = 0
while (!/blue/.test(arr[i])) i++
var match = i < arr.length-1 ? i : null
console.log(i) // 1
Array.forEach could be used but you can't break the loop - it will go on regardless of a match. some() can be used but is likely slower.
You can also create a new method if you're comfortable doing that, or a standalone function:
Array.prototype.search = function(fn){
var index = -1
this.some(function(item, i){
return fn.call(item, item) ? (index = i, true) : false
})
return index
}
var n = arr.search(function(){
return this == 'blue'
}) // 1

Related

How to express .filter() function recursively in JavaScript?

Here is a mapping function I was exercising on;
var list = [1,2,3,4,5];
function isOdd(v) {
return v % 2 == 1;
}
function exclude(arr, fn) {
var myList = [];
for (var i = 0; i < arr.length; i++) {
if (fn(arr[i])) {
myList.push(arr[i]);
}
};
return myList;
}
I want to replace for loop with a recursive solution but it doesn't produce a proper array since I started a new list in each function call at line 2. How can I solve it with a recursion? Here is the closest I got;
function exclude(arr, fn) {
let myList = [];
if (arr.length = 1) {
if (fn(arr[0])) {
return myList.push(arr[0]);
}
} else {
return myList.push(exclude(arr.slice(1), fn));
}
}
console.log(exclude(list, isOdd));
Try this one solution. I have changed a bit your implementation.
function exclude(arr, fn, output) {
output || (output = []);
if(!arr.length) {
return output;
}
if (fn(arr[0])) {
output.push(arr[0]);
}
return exclude(arr.slice(1), fn, output);
}
console.log(exclude([1,2,3,4,5,6,7,8,9], function(i) { return i % 2; }));
if (arr.length = 1) {
This assigns 1 to arr.length, effectively trimming the array if there are more than 1 items in it. You probably meant arr.length ===.
Secondly, the Array#push method returns the new length of the array, not the array itself, so where you have:
return myList.push(arr[0]);
You probably want:
myList.push(arr[0]);
return myList;
If you don't want to modify the original array and make copy of the original array on each recursive call, here could be an ugly solution
function exclude(arr, fn, result, index) {
if (!result) {
result = [];
index = -1;
}
index++;
if (index < arr.length && fn(arr[inndex]))
result.push(arr[index]);
if (index === arr.length)
return result;
return exclude(arr, fn, result, index);
}
console.log(exclude([1,2,3,4], function(i){return i%2===0;})); // prints "2,4"
IMHO, your example doesn't really fit with recursion. It is much more readable and less prone to errors to use the for loop here. Recursion can easily mess things up in JS because of contexts and scopes. If you really want to do it as a test case, i recommend you not to transform the array, it will be slow and not of much use. Anyway at the end, you will end with same behaviour as a for loop, but less efficient. here is an example:
var list = [1,2,3,4,5];
function isOdd(v) {
return v % 2 == 1;
}
function exclude(arr, fn, optionalIndex) {
var myList = [];
if(!optionalIndex){
optionalIndex = 0;
}
if(optionalIndex < arr.length){
if(fn(arr[optionalIndex])){
myList.push(arr[optionalIndex]);
}
myList = myList.concat(exclude(arr, fn, optionalIndex + 1));
}
return myList;
}
console.log(exclude(list, isOdd));
It actually would be more interesting for you to try to do it with a real recursion case, for example, using your version with for loop to filter this array with sub-arrays in it:
var list = [1, 2, 3, [0, 1, 2, [0, 10, 20, 30], 4], 5, [1 ,2 ,3]];
Just for fun of it, one line solution:
function exclude(a, fn, c) {
return (c = c || (c !== 0 ? a.length - 1 : c)) >= a.splice(c, fn(a[c]) ? 0:1)*0 ? exclude(a, fn, c - 1) : a;
}
Snippet:
var list = [0,1,2,3,4,5];
function isOdd(v) {
return v % 2 != 0;
}
function exclude(a, fn, c) {
return (c = c || (c !== 0 ? a.length - 1 : c)) >= a.splice(c, fn(a[c]) ? 0:1)*0 ? exclude(a, fn, c - 1) : a;
}
console.log(exclude(list, isOdd));
Since JavaScript doesn't have tail call elimination a recursive solution over a large array will blow your stack. However a functional and recursive approach to the problem would look something like this:
function filter(arr, fn) {
var stopAt = arr.length;
function helper (index, acc) {
return index === stopAt ?
acc :
helper(index+1, fn(arr[index]) ?
acc.concat([arr[index]]) :
acc) ;
}
return helper(0, []);
}
Of course this should never be production code. Having a functional interface while allowing for mutating internal structures is probably the best approach. If you were to look at the source for the functional library Rambda you see that they do mutate internal state.

return object if list<object> value is exists (Javascript, jquery grep/map)

I'm using grep and map functions to get the object if the value is exists in the list, but it does not work well.
I have a list customList and the customObject has the int id property and List value properties.
customobject[0].id
customObject[0].value[]
What I want is check if in the List the value 5 exists.
The function what I'm using is:
var gettedcustomObject = $.grep(customList, function (e) {
var result = e.Value.map(function (a) { return a === 5;});
return result;
});
What am I doing wrong and what is the correct implementation?
Note: 2x foreach could be a solution, but customList has more than 1000 objects with 10000 values. I think that slow down the proces.
This should do it.
var gettedcustomObject = customList.filter(function(v){
var ln = v.Value.length;
for(var i = 0; i < ln; i++){
if(v.Value[i] == 5){
return true;
}
}
return false;
// Or simply:
// return v.Value.indexOf(5) != -1;
});
This will work if v.Value is an array.
You should look at some: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some#Polyfill
Way faster than the other methods like filter, map or sort as it was designed for this.
//List to work against
var arr = [];
//Populate
while (arr.length < 9999999) {
arr.push(Math.random() * 999999)
}
//Set element to find
arr[10] = "test";
//Begin timing
var d = new Date().getTime();
//Run filter
console.log(arr.filter(function(a){return a === "test"}).length > 0);
//How long did it take
console.log("`filter` took:",new Date().getTime() - d,"ms")
//Begin timing
d = new Date().getTime();
//Run filter
console.log(arr.some(function(a){return a === "test"}));
//How long did it take
console.log("`some` took:",new Date().getTime() - d,"ms")
<script>
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some#Polyfill
// Production steps of ECMA-262, Edition 5, 15.4.4.17
// Reference: http://es5.github.io/#x15.4.4.17
if (!Array.prototype.some) {
Array.prototype.some = function(fun/*, thisArg*/) {
'use strict';
if (this == null) {
throw new TypeError('Array.prototype.some called on null or undefined');
}
if (typeof fun !== 'function') {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++) {
if (i in t && fun.call(thisArg, t[i], i, t)) {
return true;
}
}
return false;
};
}
</script>
If your goal is to find the first object in the list that contains 5 in the Value property, then you're looking for Array#find and Array#indexOf:
var gettedcustomObject = customList.find(function(entry) {
return entry.Value.indexOf(5) != -1;
});
Note that Array#find was added relatively recently and so you may need a polyfill for it (which is trivial). MDN has one.
Or you could use Array#includes instead of indexOf, which will be in ES2017 and is also polyfillable:
var gettedcustomObject = customList.find(function(entry) {
return entry.Value.includes(5);
});
Live Example (using indexOf):
var customList = [
{
note: "I'm not a match",
Value: [2, 3, 4]
},
{
note: "I'm not a match either",
Value: [78, 4, 27]
},
{
note: "I'm a match",
Value: [889, 5, 27]
},
{
note: "I'm also a match, but you won't find me",
Value: [4, 6, 5]
}
];
var gettedcustomObject = customList.find(function(entry) {
return entry.Value.indexOf(5) != -1;
});
console.log(gettedcustomObject);
If your logic matching the item inside Value were more complicated, you'd use Array#some and a callback function rathe than indexOf. But when looking to see if an array for an entry in an array based on ===, indexOf or the new Array#includes are the way to go.
one approach using Array.some() and Array.indexOf(). some loop break once the element is found
var gettedcustomObject;
customobject.some(function(obj){
if(obj.value.indexOf(5) >-1){
gettedcustomObject = obj;
return true;
}
});

How to split an array into groups [duplicate]

This question already has answers here:
Split array into chunks
(73 answers)
Closed 7 years ago.
Please consider an array such as :
arrayAll = [1,2,3,4,5,6,7,8,9]
Is there a package that enable to do partitioning to obtain :
arrayALLPartionned = [[1,2,3],[4,5,6],[7,8,9]]
I can see how to do this with a for loop but would appreciate a "pre-made" function if existing.
I think you will have to use a for loop, don't know of any inbuilt functions...
Try this function:
function splitarray(input, spacing)
{
var output = [];
for (var i = 0; i < input.length; i += spacing)
{
output[output.length] = input.slice(i, i + spacing);
}
return output;
}
Here's a recursive solution:
function partition(array, n) {
return array.length ? [array.splice(0, n)].concat(partition(array, n)) : [];
}
This takes advantage of the fact that Array#splice destructively remove the specified items, and returns them as the function value. Note that this will destroy the input array, leaving it empty.
If using Underscore.js, you can implement this with groupBy() and values()
function partition(items, size) {
var result = _.groupBy(items, function(item, i) {
return Math.floor(i/size);
});
return _.values(result);
}
(This is less ugly in CoffeeScript.)
jsFiddle: http://jsfiddle.net/MW3BS/
I've added this solution to #dystroy's jspref here and it appears to run twice as fast as the other solutions. Edit: in Safari & Chrome but not Firefox
Here is functional style solution to add to the mix of answers here.
It is a higher order function called toPartitions which returns a callback for underscore's reduce method or the native array reduce method.
Example usage:
[1,2,3,4,5,6,7,8,9].reduce( toPartitions( 3 ), [] );
The function:
function toPartitions ( size ) {
var partition = [];
return function ( acc, v ) {
partition.push( v );
if ( partition.length === size ) {
acc.push( partition );
partition = [];
}
return acc;
};
}
Like Clojure's partition it will not include a tail partition when there are not enough elements.
In your example you could do:
arrayALLPartionned = arrayAll.reduce( toPartitions( 3 ), [] ) );
If you don't want to use this with reduce, but just have a function which takes an array and partition size you could do:
function partition ( arr, size ) {
return arr.reduce( toPartitions( size ), [] );
}
Therefore the solution would just be:
arrayALLPartionned = partition( arrayAll, 3 );
One more solution, with no external library :
function partition(items, size) {
var p = [];
for (var i=Math.floor(items.length/size); i-->0; ) {
p[i]=items.slice(i*size, (i+1)*size);
}
return p;
}
Demonstration : http://jsfiddle.net/dystroy/xtHXZ/
Prototype has an array.partition function as well as an eachSlice() function. Sounds like eachSlice() is what you're looking for. If you're using jquery, there's a plug in to be able to use prototype functions. Here's a link to it... http://www.learningjquery.com/2009/02/implementing-prototypes-array-methods-in-jquery
You can write your own prototype method to do this
Array.prototype.partition = function(length) {
var result = [];
for(var i = 0; i < this.length; i++) {
if(i % length === 0) result.push([]);
result[result.length - 1].push(this[i]);
}
return result;
};
If you prefer not to add to the native prototype, you can write a simple function:
var partition = function(arr, length) {
var result = [];
for(var i = 0; i < arr.length; i++) {
if(i % length === 0) result.push([]);
result[result.length - 1].push(arr[i]);
}
return result;
};
You can see it in action on this jsFiddle demo.

In Javascript, is there an equivalent of a "find if", or a compact way of doing what I'm trying to do?

I have an ugly piece of Javascript code
for (var k = 0; k < ogmap.length; ++k)
{
if (ogmap[k]["orgname"] == curSelectedOrg)
{
ogmap[k]["catnames"].push(newCatName);
break;
}
}
Actually, I have a lot of pieces like that in my web app.
I'm wondering if there's a way to make it prettier and compacter. I know there are nice ways of doing this in other languages, like using find_if in C++ (http://www.cplusplus.com/reference/algorithm/find_if/) or FirstOrDefault in C# or fancy LINQ queries in C#.
At the very least, help me make that slightly more readable.
I'd say that you can just write yourself a utility function and then use it whenever necessary.
// finds the first object in the array that has the desired property
// with a value that matches the passed in val
// returns the index in the array of the match
// or returns -1 if no match found
function findPropMatch(array, propName, val) {
var item;
for (var i = 0; i < array.length; i++) {
item = array[i];
if (typeof item === "object" && item[propName] === val) {
return i;
}
}
return -1;
}
And, then you can use it like this:
var match = findPropMatch(ogmap, "orgname", curSelectedOrg);
if (match !== -1) {
ogmap[match]["catnames"].push(newCatName);
}
var find_if = function (arr, pred) {
var i = -1;
arr.some(function (item, ind) {
if (pred(item)) {
i = ind;
return true;
}
});
return i;
}
Call it like
var item_or_last = find_if(_.range(ogmap.length), function (item) {
return item["orgname"] == curSelectedOrg
});
Or without underscore.js
var range = function (a, b) {
var low = a < b ? a : b;
var high = a > b ? a : b;
var ret = [];
while (low < high) {
ret.push(low++);
}
return ret;
}
var item_or_last = find_if(range(0, ogmap.length), function (item) {
return item["orgname"] == curSelectedOrg
});
This lets you declare what you are looking for instead of looping over items and checking each one.

How can I determine if an element is contained in an Array without looping?

How can I check whether a particular element is inside an array? I don't want to manually write a loop for this; instead I want to use a JavaScript built-in function, maybe something equivalent to
new Array(0,1,2,3,6,9,12,15,18).Contains(5) //return false
new Array(0,1,2,3,6,9,12,15,18).Contains(1) //return true
The Array object does have an indexOf function, that will return -1 if the object does not exist. However, IE does not support this function.
Regardless, how do you think it is going to locate the item under the scenes? It will have to loop! Just because there's a built-in function does not mean that it is magical.
You could also write an extension method, as explained in this thread.
Array.prototype.contains = function(obj) {
var i = this.length;
while (i--) {
if (this[i] === obj) {
return true;
}
}
return false;
}
And now you can simply use the following:
alert([1, 2, 3].contains(2)); // => true
alert([1, 2, 3].contains('2')); // => false
As #Josh Stodola said, the indexOf function is what you need, but this function was introduced on JavaScript 1.6, for compatibility you can use this implementation from the Mozilla team, is exactly the one used in Firefox and SpiderMonkey:
if (!Array.prototype.indexOf)
{
Array.prototype.indexOf = function(elt /*, from*/)
{
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
for (; from < len; from++)
{
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
}
Source: MDC
Looks like a job for jQuery.inArray
inArray: function( elem, array ) {
for ( var i = 0, length = array.length; i < length; i++ )
// Use === because on IE, window == document
if ( array[ i ] === elem )
return i;
return -1;
}
There is no such method in javascript.
Some library (e.g. jquery) have similar method, but they use loops internally.
Ah, there is a way not to loop and it is pretty simple, people just do not think outside the box.
Array.prototype.contains = function(){
var joined = this.join("-~-");
var re = new RegExp("(^|-~-)" + arguments[0] + "($|-~-)");
return joined.match(re) !== null;
}
var arr = ["a","b","c","d"];
alert(arr.contains("a"));
alert(arr.contains("b"));
alert(arr.contains("c"));
alert(arr.contains("d"));
alert(arr.contains("e"));
Loop mom, no loops!

Categories

Resources