Related
In this exercise, we're asked to write a function that replicates the behaviour of _.each (UnderscoreJS) and that passes a series of unit tests.
// COLLECTIONS
// _.each(collection, iteratee, [context])
// Iterates over a collection of elements (i.e. array or object),
// yielding each in turn to an iteratee function, that is called with three arguments:(element, index|key, collection), and bound to the context if one is passed.
// Returns the collection for chaining.
These are the unit tests it should pass (and its current status as per the code block below):
:x: - should iterate over an array
√ - should iterate over an object
√ - should ignore object prototype
:x: - should access the original collection
:x: - should bind to context if one is passed
√ - should return the collection
While I already tried several different methods(for loop, for...of), none has worked so far in successfully iterating over an array.
I also tried to implement two if statements - for the case the collection is an Array or an Object using either the Array.isArray() method, but also the Object.prototype.toString.call() to evaluate if the matches either '[object Object]' or '[object Array]'.
I wrote different variations using if-else statements, switch case and simple if statements. This is where I currently stand. I imagine that I might not be correctly referring to the context, so any hints or remarks would be highly appreciated.
Edit and taking in account #Bergi's comment:
"It should be iteratee.call(context, elem, index, collection) instead of iteratee.call(context, index, elem, collection); otherwise this looks fine"
It turns out this was actually correct, and the problem was due to a mutation in the mocks data from a previous exercise. Thank you both for your input and time spent on this. Cheers!
_.each = function (collection, iteratee, context) {
iteratee = iteratee.bind(context);
if (Array.isArray(collection)) {
for (let [index, elem] of collection.entries()) {
iteratee.call(context, elem, index, collection);
}
}
if (!Array.isArray(collection)) {
for ( let el in collection) {
if (collection.hasOwnProperty(el)) {
iteratee.call(context, collection[el], el, collection);
}
}
}
return collection;
};
"... We're not supposed to use the native forEach :) What puzzles me is that even with fewer redundancies, it doesn't pass the test of iterating over an array." – licvdom
Edit ... and taking into account Bergi's comment ...
"Object.entries does not produce numbers for array indices. Don't use it on arrays." – Bergi
One can choose a generic approach which enables the iteration of both objects (key and value) and list like structures (index and item) like Arrays, HTMLCollections, NodeLists or even Strings.
A list like structure can be successfully converted into an array. The custom each functionality for such an array gets implemented by a simple for statement.
Any other data structure will be passed to Object.entries. The return value then can be iterated with a for...of statement where the currently processed entry can be destructured into [key, value].
function isString(value) {
return (/\[object\s+String\]/)
.test(
Object.prototype.toString.call(value)
);
}
const _ = {
each(possiblyIterable, iteratee, target) {
if (typeof iteratee !== 'function') {
throw new ReferenceError('2nd argument needs to be a function type');
}
// returns an array for any array or any
// list like structures like `String`s
// `NodeList`s or `HTMLCollection`s.
const arr = Array.from(possiblyIterable ?? []);
const isListLike = (arr.length >= 1);
// assure a context or the null value.
target = target ?? null;
if (isListLike) {
// requirement: "should access the original collection"
// - tricky due to implemented list like string iteration,
// but solvable.
const list = isString(possiblyIterable) ? arr : possiblyIterable;
for (let idx = 0; idx < arr.length; ++idx) {
iteratee.call(target, arr[idx], idx, list);
}
} else {
const entries = Object.entries(possiblyIterable ?? {})
for (let [key, value] of entries) {
iteratee.call(target, value, key, possiblyIterable);
}
}
return possiblyIterable;
},
};
const object = { x: 'XX', y: 'YY' };
const array = [5, 3, 1];
const nodeList = document.body.querySelectorAll('em');
const string = 'test';
console.log('+++ object test ...function expression and bound target +++');
_.each(object, function (value, key, obj) {
const target = this;
console.log({ target, value, key, obj });
}, { foo: 'FOO' });
console.log('\n+++ array test ...function expression and bound target +++');
_.each(array, function (item, idx, list) {
const target = this;
console.log({ target, item, idx, list });
}, { foo: 'FOO' });
console.log('\n+++ nodeList test ...non bindable arrow function +++');
_.each(nodeList, (item, idx, list) =>
console.log({ item, idx, list })
);
console.log('\n+++ string test ...non bindable arrow function +++');
_.each(string, (item, idx, list) =>
console.log({ item, idx, list })
);
console.log('\n+++ return value test +++');
console.log(
'(_.each(object, (x) => x) === object) ?..',
(_.each(object, (x) => x) === object)
);
console.log(
'(_.each(array, (x) => x) === array) ?..',
(_.each(array, (x) => x) === array)
);
console.log(
'(_.each(nodeList, (x) => x) === nodeList) ?..',
(_.each(nodeList, (x) => x) === nodeList)
);
console.log(
'(_.each(string, (x) => x) === string) ?..',
(_.each(string, (x) => x) === string)
);
console.log('\n+++ 2nd argument function type test +++');
_.each(object, null);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<em>Node</em><em>List</em><em>Test</em>
Caution:
question still applies to for…of loops.> Don't use for…in to iterate over an Array, use it to iterate
over the properties of an object. That said, this
I understand that the basic for…in syntax in JavaScript looks like this:
for (var obj in myArray) {
// ...
}
But how do I get the loop counter/index?
I know I could probably do something like:
var i = 0;
for (var obj in myArray) {
alert(i)
i++
}
Or even the good old:
for (var i = 0; i < myArray.length; i++) {
var obj = myArray[i]
alert(i)
}
But I would rather use the simpler for-in loop. I think they look better and make more sense.
Is there a simpler or more elegant way?
In Python it's easy:
for i, obj in enumerate(myArray):
print i
for…in iterates over property names, not values, and does so in an unspecified order (yes, even after ES6). You shouldn’t use it to iterate over arrays. For them, there’s ES5’s forEach method that passes both the value and the index to the function you give it:
var myArray = [123, 15, 187, 32];
myArray.forEach(function (value, i) {
console.log('%d: %s', i, value);
});
// Outputs:
// 0: 123
// 1: 15
// 2: 187
// 3: 32
Or ES6’s Array.prototype.entries, which now has support across current browser versions:
for (const [i, value] of myArray.entries()) {
console.log('%d: %s', i, value);
}
For iterables in general (where you would use a for…of loop rather than a for…in), there’s nothing built-in, however:
function* enumerate(iterable) {
let i = 0;
for (const x of iterable) {
yield [i, x];
i++;
}
}
for (const [i, obj] of enumerate(myArray)) {
console.log(i, obj);
}
demo
If you actually did mean for…in – enumerating properties – you would need an additional counter. Object.keys(obj).forEach could work, but it only includes own properties; for…in includes enumerable properties anywhere on the prototype chain.
In ES6, it is good to use a for... of loop.
You can get index in for... of like this
for (let [index, val] of array.entries()) {
// your code goes here
}
Note that Array.entries() returns an iterator, which is what allows it to work in the for-of loop; don't confuse this with Object.entries(), which returns an array of key-value pairs.
How about this
let numbers = [1,2,3,4,5]
numbers.forEach((number, index) => console.log(`${index}:${number}`))
Where array.forEach this method has an index parameter which is the index of the current element being processed in the array.
Solution for small array collections:
for (var obj in arr) {
var i = Object.keys(arr).indexOf(obj);
}
arr - ARRAY,
obj - KEY of current element,
i - COUNTER/INDEX
Notice: Method keys() is not available for IE version <9, you should use Polyfill code.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
For-in-loops iterate over properties of an Object. Don't use them for Arrays, even if they sometimes work.
Object properties then have no index, they are all equal and not required to be run through in a determined order. If you want to count properties, you will have to set up the extra counter (as you did in your first example).
loop over an Array:
var a = [];
for (var i=0; i<a.length; i++) {
i // is the index
a[i] // is the item
}
loop over an Object:
var o = {};
for (var prop in o) {
prop // is the property name
o[prop] // is the property value - the item
}
As others have said, you shouldn't be using for..in to iterate over an array.
for ( var i = 0, len = myArray.length; i < len; i++ ) { ... }
If you want cleaner syntax, you could use forEach:
myArray.forEach( function ( val, i ) { ... } );
If you want to use this method, make sure that you include the ES5 shim to add support for older browsers.
Answer Given by rushUp Is correct but this will be more convenient
for (let [index, val] of array.entries() || []) {
// your code goes here
}
Here's a function eachWithIndex that works with anything iterable.
You could also write a similar function eachWithKey that works with objets using for...in.
// example generator (returns an iterator that can only be iterated once)
function* eachFromTo(start, end) { for (let i = start; i <= end; i++) yield i }
// convers an iterable to an array (potential infinite loop)
function eachToArray(iterable) {
const result = []
for (const val of iterable) result.push(val)
return result
}
// yields every value and index of an iterable (array, generator, ...)
function* eachWithIndex(iterable) {
const shared = new Array(2)
shared[1] = 0
for (shared[0] of iterable) {
yield shared
shared[1]++
}
}
console.log('iterate values and indexes from a generator')
for (const [val, i] of eachWithIndex(eachFromTo(10, 13))) console.log(val, i)
console.log('create an array')
const anArray = eachToArray(eachFromTo(10, 13))
console.log(anArray)
console.log('iterate values and indexes from an array')
for (const [val, i] of eachWithIndex(anArray)) console.log(val, i)
The good thing with generators is that they are lazy and can take another generator's result as an argument.
On top of the very good answers everyone posted I want to add that the most performant solution is the ES6 entries. It seems contraintuitive for many devs here, so I created this perf benchamrk.
It's ~6 times faster. Mainly because doesn't need to: a) access the array more than once and, b) cast the index.
That's my version of a composite iterator that yields an index and any passed generator function's value with an example of (slow) prime search:
const eachWithIndex = (iterable) => {
return {
*[Symbol.iterator]() {
let i = 0
for(let val of iteratable) {
i++
yield [i, val]
}
}
}
}
const isPrime = (n) => {
for (i = 2; i < Math.floor(Math.sqrt(n) + 1); i++) {
if (n % i == 0) {
return false
}
}
return true
}
let primes = {
*[Symbol.iterator]() {
let candidate = 2
while (true) {
if (isPrime(candidate)) yield candidate
candidate++
}
}
}
for (const [i, prime] of eachWithIndex(primes)) {
console.log(i, prime)
if (i === 100) break
}
To use for..of loop on array and retrieve index you can you use array1.indexOf(element) which will return the index value of an element in the loop. You can return both the index and the value using this method.
array1 = ['a', 'b', 'c']
for (element of array1) {
console.log(array1.indexOf(element), element) // 0 a 1 b 2 c
}
As mentionned in comments, this will return false index when the array contains non uniques values. (considering arr = ['a', 'b', 'c', 'a'], index of arr[3] will return 0 instead of 3)
// this loop is used in advanced javascript
//For Example I have an array:
let array = [1,2,3,4,5];
1) for(let key in array){
console.log(key);//this shows index of array {Result: 0,1,2,3,4}
console.log(array[key]);//this show values of array {Result: 1,2,3,4,5}
}
//Hopefully, You will quickly understand;
I've got an array:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}, etc.]
I'm unable to change the structure of the array. I'm being passed an id of 45, and I want to get 'bar' for that object in the array.
How do I do this in JavaScript or using jQuery?
Use the find() method:
myArray.find(x => x.id === '45').foo;
From MDN:
The find() method returns the first value in the array, if an element in the array satisfies the provided testing function. Otherwise undefined is returned.
If you want to find its index instead, use findIndex():
myArray.findIndex(x => x.id === '45');
From MDN:
The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned.
If you want to get an array of matching elements, use the filter() method instead:
myArray.filter(x => x.id === '45');
This will return an array of objects. If you want to get an array of foo properties, you can do this with the map() method:
myArray.filter(x => x.id === '45').map(x => x.foo);
Side note: methods like find() or filter(), and arrow functions are not supported by older browsers (like IE), so if you want to support these browsers, you should transpile your code using Babel (with the polyfill).
As you are already using jQuery, you can use the grep function which is intended for searching an array:
var result = $.grep(myArray, function(e){ return e.id == id; });
The result is an array with the items found. If you know that the object is always there and that it only occurs once, you can just use result[0].foo to get the value. Otherwise you should check the length of the resulting array. Example:
if (result.length === 0) {
// no result found
} else if (result.length === 1) {
// property found, access the foo property using result[0].foo
} else {
// multiple items found
}
Another solution is to create a lookup object:
var lookup = {};
for (var i = 0, len = array.length; i < len; i++) {
lookup[array[i].id] = array[i];
}
... now you can use lookup[id]...
This is especially interesting if you need to do many lookups.
This won't need much more memory since the IDs and objects will be shared.
ECMAScript 2015 (JavaScript ES6) provides the find()
method on arrays:
var myArray = [
{id:1, name:"bob"},
{id:2, name:"dan"},
{id:3, name:"barb"},
]
// grab the Array item which matchs the id "2"
var item = myArray.find(item => item.id === 2);
// print
console.log(item.name);
It works without external libraries. But if you want older browser support you might want to include this polyfill.
Underscore.js has a nice method for that:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'},etc.]
obj = _.find(myArray, function(obj) { return obj.id == '45' })
I think the easiest way would be the following, but it won't work on Internet Explorer 8 (or earlier):
var result = myArray.filter(function(v) {
return v.id === '45'; // Filter out the appropriate one
})[0].foo; // Get result and access the foo property
Try the following
function findById(source, id) {
for (var i = 0; i < source.length; i++) {
if (source[i].id === id) {
return source[i];
}
}
throw "Couldn't find object with id: " + id;
}
myArray.filter(function(a){ return a.id == some_id_you_want })[0]
A generic and more flexible version of the findById function above:
// array = [{key:value},{key:value}]
function objectFindByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
var array = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var result_obj = objectFindByKey(array, 'id', '45');
Performance
Today 2020.06.20 I perform test on MacOs High Sierra on Chrome 81.0, Firefox 77.0 and Safari 13.1 for chosen solutions.
Conclusions for solutions which use precalculations
Solutions with precalculations (K,L) are (much much) faster than other solutions and will not be compared with them - probably they are use some special build-in browser optimisations
surprisingly on Chrome and Safari solution based on Map (K) are much faster than solution based on object {} (L)
surprisingly on Safari for small arrays solution based on object {} (L) is slower than traditional for (E)
surprisingly on Firefox for small arrays solution based on Map (K) is slower than traditional for (E)
Conclusions when searched objects ALWAYS exists
solution which use traditional for (E) is fastest for small arrays and fast for big arrays
solution using cache (J) is fastest for big arrays - surprisingly for small arrays is medium fast
solutions based on find (A) and findIndex (B) are fast for small arras and medium fast on big arrays
solution based on $.map (H) is slowest on small arrays
solution based on reduce (D) is slowest on big arrays
Conclusions when searched objects NEVER exists
solution based on traditional for (E) is fastest on small and big arrays (except Chrome-small arrays where it is second fast)
solution based on reduce (D) is slowest on big arrays
solution which use cache (J) is medium fast but can be speed up if we save in cache also keys which have null values (which was not done here because we want to avoid unlimited memory consumption in cache in case when many not existing keys will be searched)
Details
For solutions
without precalculations: A
B
C
D
E
F
G
H
I
J (the J solution use 'inner' cache and it speed depend on how often searched elements will repeat)
with precalculations
K
L
I perform four tests. In tests I want to find 5 objects in 10 loop iterations (the objects ID not change during iterations) - so I call tested method 50 times but only first 5 times have unique id values:
small array (10 elements) and searched object ALWAYS exists - you can perform it HERE
big array (10k elements) and searched object ALWAYS exist - you can perform it HERE
small array (10 elements) and searched object NEVER exists - you can perform it HERE
big array (10k elements) and searched object NEVER exists - you can perform it HERE
Tested codes are presented below
function A(arr, id) {
return arr.find(o=> o.id==id);
}
function B(arr, id) {
let idx= arr.findIndex(o=> o.id==id);
return arr[idx];
}
function C(arr, id) {
return arr.filter(o=> o.id==id)[0];
}
function D(arr, id) {
return arr.reduce((a, b) => (a.id==id && a) || (b.id == id && b));
}
function E(arr, id) {
for (var i = 0; i < arr.length; i++) if (arr[i].id==id) return arr[i];
return null;
}
function F(arr, id) {
var retObj ={};
$.each(arr, (index, obj) => {
if (obj.id == id) {
retObj = obj;
return false;
}
});
return retObj;
}
function G(arr, id) {
return $.grep(arr, e=> e.id == id )[0];
}
function H(arr, id) {
return $.map(myArray, function(val) {
return val.id == id ? val : null;
})[0];
}
function I(arr, id) {
return _.find(arr, o => o.id==id);
}
let J = (()=>{
let cache = new Map();
return function J(arr,id,el=null) {
return cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
}
})();
function K(arr, id) {
return mapK.get(id)
}
function L(arr, id) {
return mapL[id];
}
// -------------
// TEST
// -------------
console.log('Find id=5');
myArray = [...Array(10)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
const mapK = new Map( myArray.map(el => [el.id, el]) );
const mapL = {}; myArray.forEach(el => mapL[el.id]=el);
[A,B,C,D,E,F,G,H,I,J,K,L].forEach(f=> console.log(`${f.name}: ${JSON.stringify(f(myArray, '5'))}`));
console.log('Whole array',JSON.stringify(myArray));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
This snippet only presents tested codes
Example tests results for Chrome for small array where searched objects always exists
As others have pointed out, .find() is the way to go when looking for one object within your array. However, if your object cannot be found using this method, your program will crash:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100').foo; // Uh oh!
/*
Error:
"Uncaught TypeError: Cannot read property 'foo' of undefined"
or in newer chrome versions:
Uncaught TypeError: Cannot read properties of undefined (reading 'foo')
*/
This can be fixed by checking whether the result of .find() is defined before using .foo on it. Modern JS allows us to do this easily with optional chaining, returning undefined if the object cannot be found, rather than crashing your code:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100')?.foo; // No error!
console.log(res); // undefined when the object cannot be found
If you do this multiple times, you may set up a Map (ES6):
const map = new Map( myArray.map(el => [el.id, el]) );
Then you can simply do a O(1) lookup:
map.get(27).foo
You can get this easily using the map() function:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var found = $.map(myArray, function(val) {
return val.id == 45 ? val.foo : null;
});
//found[0] == "bar";
Working example: http://jsfiddle.net/hunter/Pxaua/
Using native Array.reduce
var array = [ {'id':'73' ,'foo':'bar'} , {'id':'45' ,'foo':'bar'} , ];
var id = 73;
var found = array.reduce(function(a, b){
return (a.id==id && a) || (b.id == id && b)
});
returns the object element if found, otherwise false
You can use filters,
function getById(id, myArray) {
return myArray.filter(function(obj) {
if(obj.id == id) {
return obj
}
})[0]
}
get_my_obj = getById(73, myArray);
While there are many correct answers here, many of them do not address the fact that this is an unnecessarily expensive operation if done more than once. In an extreme case this could be the cause of real performance problems.
In the real world, if you are processing a lot of items and performance is a concern it's much faster to initially build a lookup:
var items = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var lookup = items.reduce((o,i)=>o[i.id]=o,{});
you can then get at items in fixed time like this :
var bar = o[id];
You might also consider using a Map instead of an object as the lookup: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map
Recently, I have to face the same thing in which I need to search the string from a huge array.
After some search I found It'll be easy to handle with simple code:
Code:
var items = mydata.filter(function(item){
return item.word.toLowerCase().startsWith( 'gk );
})
See https://jsfiddle.net/maheshwaghmare/cfx3p40v/4/
Iterate over any item in the array. For every item you visit, check that item's id. If it's a match, return it.
If you just want teh codez:
function getId(array, id) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i].id === id) {
return array[i];
}
}
return null; // Nothing found
}
And the same thing using ECMAScript 5's Array methods:
function getId(array, id) {
var obj = array.filter(function (val) {
return val.id === id;
});
// Filter returns an array, and we just want the matching item.
return obj[0];
}
You may try out Sugarjs from http://sugarjs.com/.
It has a very sweet method on Arrays, .find. So you can find an element like this:
array.find( {id: 75} );
You may also pass an object with more properties to it to add another "where-clause".
Note that Sugarjs extends native objects, and some people consider this very evil...
As long as the browser supports ECMA-262, 5th edition (December 2009), this should work, almost one-liner:
var bFound = myArray.some(function (obj) {
return obj.id === 45;
});
Here's how I'd go about it in pure JavaScript, in the most minimal manner I can think of that works in ECMAScript 3 or later. It returns as soon as a match is found.
var getKeyValueById = function(array, key, id) {
var testArray = array.slice(), test;
while(test = testArray.pop()) {
if (test.id === id) {
return test[key];
}
}
// return undefined if no matching id is found in array
return;
}
var myArray = [{'id':'73', 'foo':'bar'}, {'id':'45', 'foo':'bar'}]
var result = getKeyValueById(myArray, 'foo', '45');
// result is 'bar', obtained from object with id of '45'
More generic and short
function findFromArray(array,key,value) {
return array.filter(function (element) {
return element[key] == value;
}).shift();
}
in your case Ex. var element = findFromArray(myArray,'id',45) that will give you the whole element.
We can use Jquery methods $.each()/$.grep()
var data= [];
$.each(array,function(i){if(n !== 5 && i > 4){data.push(item)}}
or
var data = $.grep(array, function( n, i ) {
return ( n !== 5 && i > 4 );
});
use ES6 syntax:
Array.find, Array.filter, Array.forEach, Array.map
Or use Lodash https://lodash.com/docs/4.17.10#filter, Underscore https://underscorejs.org/#filter
Building on the accepted answer:
jQuery:
var foo = $.grep(myArray, function(e){ return e.id === foo_id})
myArray.pop(foo)
Or CoffeeScript:
foo = $.grep myArray, (e) -> e.id == foo_id
myArray.pop foo
Use Array.prototype.filter() function.
DEMO: https://jsfiddle.net/sumitridhal/r0cz0w5o/4/
JSON
var jsonObj =[
{
"name": "Me",
"info": {
"age": "15",
"favColor": "Green",
"pets": true
}
},
{
"name": "Alex",
"info": {
"age": "16",
"favColor": "orange",
"pets": false
}
},
{
"name": "Kyle",
"info": {
"age": "15",
"favColor": "Blue",
"pets": false
}
}
];
FILTER
var getPerson = function(name){
return jsonObj.filter(function(obj) {
return obj.name === name;
});
}
You can do this even in pure JavaScript by using the in built "filter" function for arrays:
Array.prototype.filterObjects = function(key, value) {
return this.filter(function(x) { return x[key] === value; })
}
So now simply pass "id" in place of key and "45" in place of value, and you will get the full object matching an id of 45. So that would be,
myArr.filterObjects("id", "45");
I really liked the answer provided by Aaron Digulla but needed to keep my array of objects so I could iterate through it later. So I modified it to
var indexer = {};
for (var i = 0; i < array.length; i++) {
indexer[array[i].id] = parseInt(i);
}
//Then you can access object properties in your array using
array[indexer[id]].property
Use:
var retObj ={};
$.each(ArrayOfObjects, function (index, obj) {
if (obj.id === '5') { // id.toString() if it is int
retObj = obj;
return false;
}
});
return retObj;
It should return an object by id.
This solution may helpful as well:
Array.prototype.grep = function (key, value) {
var that = this, ret = [];
this.forEach(function (elem, index) {
if (elem[key] === value) {
ret.push(that[index]);
}
});
return ret.length < 2 ? ret[0] : ret;
};
var bar = myArray.grep("id","45");
I made it just like $.grep and if one object is find out, function will return the object, rather than an array.
Dynamic cached find
In this solution, when we search for some object, we save it in cache. This is middle point between "always search solutions" and "create hash-map for each object in precalculations".
let cachedFind = (()=>{
let cache = new Map();
return (arr,id,el=null) =>
cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
})();
// ---------
// TEST
// ---------
let myArray = [...Array(100000)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
// example usage
console.log( cachedFind(myArray,'1234').foo );
// Benchmark
let bench = (id) => {
console.time ('time for '+id );
console.log ( cachedFind(myArray,id).foo ); // FIND
console.timeEnd('time for '+id );
}
console.log('----- no cached -----');
bench(50000);
bench(79980);
bench(99990);
console.log('----- cached -----');
bench(79980); // cached
bench(99990); // cached
I'm trying to map an array that has n-dimension sub-arrays. I've looked at deep-map but I'm not using any objects. An example of what I'm trying to do is:
deepMap([4,5,[3,4,[2]]], (x) => x+5)
returns [9,10,[8,9,[7]]]
The function after the array in deepMap can be any function
const deepMap=(arr, fn) =>{
const stop = arr.length===0;
const tail = arr.slice(1);
const head = arr[0];
const front = head instanceof Array ? [head[0]]: [] ;
const next = fn(front, head);
return stop ? [] : front.concat(deepMap(tail,fn));
}
How do you apply a function to values in nested arrays while keeping the whole array a nested array?
any help is greatly appreciated!
You may do as follows in a Haskellesque fashion;
function deepMap([x,...xs],f){
return x ? [Array.isArray(x) ? deepMap(x,f) : f(x), ...deepMap(xs,f)]
: [];
}
var arr = [4,5,[3,4,[2]]],
res = deepMap(arr, x => x+5);
console.log(res);
Use simple recursion. For a nested array, map over the array calling deepMap recursively. When you reach a leaf, call the function.
function deepMap(arr, fn) {
return arr instanceof Array ? arr.map(el => deepMap(el, fn)) : fn(arr);
}
console.log(deepMap([4, 5, [3, 4, [2]]], (x) => x + 5));
I need an easy way to merge, flattern and concat multiple multi-dimensional arrays in javascript in a certain way (right to left)
# Exemple
[['.class1', '.class2'], ['.class3', ['.class4', '.class5', ...], ['.class6'], ...]]
# OR
[['.class1', '.class2'], ['.class3', ['.class4', '.class5', ...]], ['.class6'], ...]
# Would become
['.class1.class3.class4.class6', '.class1.class3.class5.class6', '.class2.class3.class4.class6', '.class2.class3.class5.class6', ...]
I've found the reduceRight function from underscore.js lib but I'm not sure how i could acheive this easily as it needs to be done recursively.
You could use an iterative and recursive approach for variable length of parts with Array#forEach.
This version works now with nested arrays and flats it in advanced.
function combine(array) {
function c(part, index) {
var temp = array[index];
if (Array.isArray(temp) && temp.some(function (a) { return Array.isArray(a); })) {
temp = combine(array[index].map(function (a) { return Array.isArray(a) ? a : [a]; }));
}
temp.forEach(function (a) {
var p = part.concat(a);
if (p.length === array.length) {
r.push(p.join(''));
return;
}
c(p, index + 1);
});
}
var r = [];
c([], 0);
return r;
}
var array = [
['.class1', '.class2'],
['.class3',
['.class4', '.class5'],
['.class6']
]
],
result = combine(array);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var data = [['.class1', '.class2'], ['.class3', ['.class4', '.class5'], ['.class6']]];
function cartesian(a, b) { // return the cartesian product of the two arrays a and b
if(!a.length) return b; // if a is empty then the result of the product is b
if(!b.length) return a; // if b is empty then the product is a
return a.reduce((res, ae) => (b.forEach(be => res.push(ae + be)), res), []); // product of non-empty a and non-empty b
}
function combos(arr) { // take an array arr and return the combinations out from its nested arrays
if(arr.every(e => !Array.isArray(e))) return arr; // if the array doesn't contain any nested arrays then return it as it is
return arr.reduce((acc, e) => { // otherwise return the cartesian product of all its elements
e = Array.isArray(e)? combos(e): [e]; // if the current element is an array, then get its combos, otherwise, wrap it in an array
return cartesian(acc, e); // get the cartesian product of previous elements and the combos of this element e
}, []);
}
console.log(combos(data));
If you're open to using lodash, which is a better version of underscore (imo), this can be expressed fairly simply using flattenDeep (https://lodash.com/docs/4.17.4#flattenDeep) on each element of your multi-dimensional array:
function combine(list) {
const arrays = list.map(_.flattenDeep);
return arrays.map((a) => a.join('');
}