Writing 'Map' Using Callbacks in custom 'Each' - JavaScript - javascript

I have each() that iterates through a collection and performs a function on it.
_['each'] = function(collection, iterator) {
if (Array.isArray(collection) === false && typeof(collection) ===
'object') {
var values = Object.values(collection);
var keys = Object.keys(collection);
for (let i = 0; i < values.length; i++) {
iterator(values[i], keys[i], collection, i);
}
} else {
for (let i = 0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
}
}
I want to write map() that takes the results and returns a new array. If I had to use a callback to make use of the each() function, I'm stuck on how to pull those values out. But assuming map() takes two parameters, a collection and the function for the iterator, I'm not sure how I would even access 'i' or any other value as they would be undefined. Should each() have a third parameter to take another callback to adding values into a collection?

Initialize an empty array, call _.each on the input collection, and write results to your output array. Something like
_['map'] = function(collection, iterator) {
var result = [];
var mapIterator = function(value, key, collection, i) {
result[i] = iterator(value, key, collection, i);
}
_['each'](collection, mapIterator);
return result;
}

Related

Assigning value to function as object property within for loop

I am trying to create an array of objects. One object property is a function, which I want to change value of depending on which number in the array the object is.
When I try to use the 'i' value from the for loop, this is not being maintained in the function as a number. It is remaining a variable.
var array = [];
for (var i = 0; i<number; i++){
array[i].someFunction = function(i) {console.log(i)}}
However, when I call the value held in that property (i.e.):
console.log(array[2].someFunction)
It returns {console.log(i)} instead of {console.log(2)} which is what I want it to do.
Basically you had some problems in your code, like:
not defining number
using var instead of let
not defining the object
using the same value as parameter of the function
here you have the working solution.
var array = [];
// numbers was not defined.
const number = 10;
// use let instead of var
for (let i = 0; i < number; i++) {
// you are not instantiating your object, here is an example.
array[i] = {
"foo": "bar",
"index": i,
}
// you have to change i to j, because if you use i
// it will be the parameter of the function
array[i].someFunction = function(j) {
console.log('index: ', i);
console.log('parameter: ', j)
}
}
// we see the function
console.log(array[2].someFunction)
// we call the function succesfully
array[2].someFunction(100);
It's still referencing i, which has since changed to (number - 1). Save the value somewhere you know it's not subject to change- perhaps in the object itself:
var array = [{}, {}, {}];
for(var i = 0; i < array.length; i++){
array[i].index = i;
array[i].someFunction = function(){console.log(this.index);}
}
//and see that it's working...
for(var i = 0; i < array.length; i++){
array[i].someFunction();
}

JavaScript - Writing indexOf and each

I'm trying to rewrite _.each and _.indexOf and it is throwing me for a loop.
My each function takes either an object or an array and passes tests set up.
_['each'] = function(collection, iterator) {
if (Array.isArray(collection) === false && typeof(collection) === 'object') {
var values = Object.values(collection);
var keys = Object.keys(collection);
for (let i = 0; i < values.length; i++) {
iterator(values[i], keys[i], collection, i);
}
} else {
for (let i = 0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
}
}
So I'm assuming this code is alright since it passes a preset test, but I'm not even sure about that. My question is, how would I write an indexOf() function that also uses the each() function? Each will run a function for every element and won't break, right? And I can't access the index via collection[i] because i is undefined in indexOf's scope. What am I missing?
pseudo code, check if you need the full javascript code :
indexOf = function(element, array){
// found index
index = -1;
// for each element, compare element, store index if found
each array (function(e, i){
// use cmp function to deep compare object/array as values
if(e === element)
{
// remove condition for lastIndexOf
if(index == -1)
{
index = i;
}
}
});
// return found index
return index;
}

How to add objects into an array without repeating value of one of the properties of objects in JavaScript?

I am trying to insert data into an array. The data is of object form with 2 properties name and value. I am retrieving the data from a different array object with a completely different structure.
The new array that I am creating is as follow:
newarray = [Object, Object, Object]
where Object = {name: "abc", value: "12"}
This is what I am doing for my purpose:
var newarray = [];
for (var i = 0; i < oldarray.length; i++) {
a = newarray.indexOf(oldarray[i].studentname);
if (a == -1) {
newarray.push({
name: oldarray[i].studentname,
value: oldarray[i].marks
})
}
}
I don't want the student name to repeat so I have tried to use indexOf to check the occurrence of the name in the array. But I am not doing it correctly. What am I doing wrong?
var newarray= [];
for(var i=0; i<oldarray.length; i++)
{
var found = false;
for (var j=0; j<newarray.length; j++) {
if (oldarray[i].studentname == newarray[j].studentname) {
found = true;
break;
}
}
if(!found)
{
newarray.push({
name : oldarray[i].studentname,
value :oldarray[i].marks
});
}
}
This code:
a = newarray.indexOf(oldarray[i].studentname);
if (a == -1)
looks to see if the student's name is an entry in the array. But the entries you're putting in the array aren't just namnes, they're objects.
If you want to find if an object with the same name is in the array, you can use Array#some:
if (!newarray.some(function(entry) { return entry.name == oldarray[i].studentname; })) {
// It's not there, add it
}
Array#some calls its callback for each entry. If the callback returns a truthy value for any entry, some stops and returns true; if the callback never returns a truthy value (or the array is empty), some returns false.
It's a bit more concise with ES2015, if you're using ES2015:
if (!newarray.some(entry => entry.name == oldarray[i].studentname)) {
// It's not there, add it
}
== will require that the names be in the same capitalization, of course. If that may not be true, you might want to make both sides lower (or upper) case first.
I don't see a declared anywhere, make sure you're not falling prey to The Horror of Implicit Globals.
var newarray = [];
for (var i = 0; i < oldarray.length; i++) {
a = findWithAttr(newarray, "studentname", oldarray[i].studentname);
if (a == undefined) {
newarray.push({
name: oldarray[i].studentname,
value: oldarray[i].marks
})
}
}
function findWithAttr(array, attr, value) {
for (var i = 0; i < array.length; i += 1) {
if (array[i][attr] === value) {
return i;
}
}
}

More efficient array search

I'd like to pull a particular object from an array of objects based on a unique property of that object (ie, a key).
In the following, I'm searching for an element in 'arr' where the key is 8.
var myElement = arr.filter(function(element) {
return element.key === 8;
});
This works, but every time this runs, it will iterate through all elements in the array, even after the correct element has been found. For example, if it finds myElement at index 4, but there are 100 elements in the array, the snippet is running 25x more than it needs to.
Is there a more efficient way, or a way to terminate filter() when it has found myElement?
I feel like I've missed something obvious...
You actually want a find function that returns when the first occurrence is found. You can use Array.prototype.find if you are on ECMAScript 6 spec or you can implement a simple find like:
function find8(arr) {
// type checks skipped for brevity
var i = 0,
j = arr.length;
for (; i < j; i++) {
if (arr[i].key === 8) {
return arr[i];
}
}
throw new Error('Not found');
}
If you want a more generic solution that accepts predicates (i.e. functions that return a boolean value), you can write a more generic function like:
function find(arr, predicate) {
// Again, type checks skipped for brevity
var i = 0,
j = arr.length;
for (; i < j; i++) {
// Note the condition is now a call to the predicate function
if (predicate(arr[i])) {
return arr[i];
}
}
throw new Error('Not found');
}
for (var i=0; i<arr.length; i++) {
if (arr[i].key === 8) {
console.log('FOUND ITEM AT KEY '+8);
break;
}
}
It sounds like you'd be better off with something like this:
function findByKey(items, key) {
for (var index = 0; index < items.length; index++) {
var item = items[index];
if (item.key === key) {
return item;
}
}
return null;
}
Note that if your list is sorted, you can improve upon that by doing a binary-search instead. Though personally I'd suggest hashing (assuming that the same key isn't used twice). Something like:
var array = [/* your data here... */];
var map = {};
//do this once; or once each time your array changes
for (var index = 0; index < array.length; index++) {
var item = array[index];
map[item.key] = item;
}
//now find things by doing this
var itemWithKey8 = map[8];
I'd handle this with a simple for loop (used a function for generic re-use):
function findObject(arr, cond)
for (i = 0; i < arr.length; i++) {
if (cond(arr[i]){
return arr[i];
}
}
}
// now call fincObject(myArray, function(element){element.key === 8})
Or, if you know you are going to do this many times, create a mapping which should be a lot faster:
function makeMapping(arr, keyFunc){
var mapping = {};
for (var i = 0; i < arr.length; i++) {
mapping[keyFunc(arr[i])] = arr[i];
}
return mapping;
}
This returns an object mapping 8 to the object with key.id === 8. Your keyFunc would be:
function keyFunc(element){
return element.key;
}
Why reinvent the wheel? There are a lot of similar answers here, so I'll share a different approach--by far my favorite approach. There is an excellent library called linq.js (both standalone and jQuery plugin versions) which makes searching, filtering, sorting, etc. a breeze.
var myElement = Enumerable.From(arr).FirstOrDefault(null, function(element) {
return element.key === 8;
});
In the above example, the first element that matches the conditions is returned. If nothing is found, null is returned (the first parameter is the default value to return).

Using recursion to return a value for each item in an array

I'm trying to use recursion in JavaScript to deeply go through an object and return its key and value.
An example of this would be:
var json2 = {
'key1': {
'key2Nested': {
'key3Nested': {
'key4Nested': 'SomeValue'
},
'key5Nested': 'unimportantValue',
'key6Nested': 'SimpleValue'
},
'key7Nested': '2SimpleValue',
'key8Nested': 'unimportantValue2'
}
};
The function will take the above input and return something like
['key1/key2Nested/key3Nested/key4Nested', 'SomeValue'],
['key1/key2Nested/key5Nested', 'unimportantValue'],
etc for all values.
The problem is I try to use a for loop on all the object's keys and I try to use recursion inside the loop. But the recursion value returns an array, which ends the for loop.
Here is the code that I have so far:
var makeArray = function(obj) {
var keysArray = Object.keys(obj);
var returnArray = [];
for (var i = 0; i < keysArray.length; i++) {
var key = keysArray[i];
var next_results;
var path, value;
if (typeof(value) != 'object' ) {
value = obj[key];
returnArray = orderedArray.concat([key, value]);
} else if (typeof(value) == "object") {
next_results = makeArray(obj[key]);
if (next_results) {
for (var j = 0; j < next_results.length; j++) {
next_results[j][1] = '/' + key + next_results[j][1];
returnArray = returnArray.concat(next_results[j]);
}
}
}
console.log(returnArray);
return returnArray;
}
}
The function needs to save the key returned from deeper recursion levels so that it can concatenate it to the path.
Perhaps my algorithm can be improved somehow or I'm thinking of it wrong. Can anyone give some advice? Thanks!
Just don't return returnArray inside the for loop body, but only after it.
Also, some other bugs:
The line
next_results[j][1] = '/' + key + next_results[j][1];
doesn't seem to be right. Your keys are in the first slot of each tuple, and you want the slash in between the keys not before them:
next_results[j][0] = key + '/' + next_results[j][0];
In
var path, value;
if (typeof(value) != 'object' ) {
value = obj[key];
you are testing the type of value before assigning it (so that you basically use the value from the previous iteration). Move the property access before the condition!
The method call
returnArray = returnArray.concat(…)
doesn't do what you think it does. You're passing in a tuple (array) that you want to get appended to the array, but the concat method merges the two arrays: [key1, value1].concat([key2, value]) == [key1, value1, key2, value2]. You want to use push instead to get an array of tuples.
In whole:
function makeArray(obj) {
var keys = Object.keys(obj);
var returnArray = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i],
value = obj[key];
if (typeof value != 'object' ) {
returnArray.push([key, value]);
} else {
var next_results = makeArray(value);
for (var j = 0; j < next_results.length; j++) {
next_results[j][0] = key + '/' + next_results[j][0];
returnArray.push(next_results[j]);
}
}
}
return returnArray;
}

Categories