Related
I was playing around with Arrays in jsfiddle and noticed that when I do this:
let a = [0,1,2,3,4];
for (let i in a) {
console.log(a.indexOf(i));
}
it logs:
But when I do:
let a = [0,1,2,3,4];
for (let i in a) {
console.log(a.indexOf(i - 0));
}
it logs:
Minus zero changed the result!
At first I thought it was a jsfiddle problem, but then I tried it with my code editor the same thing happened. Why does this happen can someone explain to me?
When you do:
for (let i in a) ...
This will iterate over indexes of the array as strings. It treats the array like an object and iterates over the indexes like keys. You will get strings: "0", "1"...
You can see that if you print the value and type:
let a = [0,1,2,3,4];
for (let i in a) {
console.log(i, typeof i)
}
Those strings are not in your array — your array has numbers — so findIndex() doesn't find them and gives you -1. However when you subtract 0, javascript converts to an integer for you and suddenly it finds them because the indexes match the values.
What you probably want is for...of to iterate over the values:
let a = [0,1,2,3,4];
for (let i of a) {
console.log("i:", i, typeof i)
console.log("index:", a.indexOf(i));
}
For... in loop is meant to use on objects. In your particular case, the i element in the loop is not a value from the array, but it's key.
Key is typeof string and since you don't have any strings in your array, you are getting -1 with every cycle.
Using - 0 evaluates the string into a number.
Note: It's an unique and "happy" situation, since your elements in the array are sorted, iterated integers starting at 0. That's why it seems to work properly.
Just a quick showcase:
const keys = Object.assign({}, [0, 1, 2, 3]);
console.log(keys);
console.log(typeof Object.keys(keys)[0]);
Snippet above represents how your array is interpreted by for... in loop. As you can easily see, every key from such object is a string.
Please have a look:
let a = [0,1,2,3,4];
for (let i in a) {
console.log(a.indexOf(a[i]));
}
I'm trying to solve a freeCodeCamp exercise with this goal:
Write a function that takes two or more arrays and returns a new array
of unique values in the order of the original provided arrays.
In other words, all values present from all arrays should be included
in their original order, but with no duplicates in the final array.
The unique numbers should be sorted by their original order, but the
final array should not be sorted in numerical order.
So what I do is concatenate all the arguments into a single array called everything. I then search the array for duplicates, then search the arguments for these duplicates and .splice() them out.
So far everything works as expected, but the last number of the last argument does not get removed and I can't really figure out why.
Can anybody please point out what I'm doing wrong? Please keep in mind that I'm trying to learn, so obvious things probably won't be obvious to me and need to be pointed out. Thanks in advance.
function unite(arr1, arr2, arr3) {
var everything = [];
//concat all arrays except the first one
for(var x = 0; x < arguments.length; x++) {
for(var y = 0; y < arguments[x].length; y++) {
everything.push(arguments[x][y]);
}
}
//function that returns duplicates
function returnUnique(arr) {
return arr.reduce(function(dupes, val, i) {
if (arr.indexOf(val) !== i && dupes.indexOf(val) === -1) {
dupes.push(val);
}
return dupes;
}, []);
}
//return duplicates
var dupes = returnUnique(everything);
//remove duplicates from all arguments except the first one
for(var n = 1; n < arguments.length; n++) {
for(var m = 0; m < dupes.length; m++) {
if(arguments[n].hasOwnProperty(dupes[m])) {
arguments[n].splice(arguments[n].indexOf(dupes[m]), 1);
}
}
}
//return concatenation of the reduced arguments
return arr1.concat(arr2).concat(arr3);
}
//this returns [1, 3, 2, 5, 4, 2]
unite([1, 3, 2], [5, 2, 1, 4], [2, 1]);
Looks like you overcomplicated it a bit ;)
function unite() {
return [].concat.apply([], arguments).filter(function(elem, index, self) {
return self.indexOf(elem) === index;
});
}
res = unite([1, 2, 3], [5, 2, 1, 4], [2, 1], [6, 7, 8]);
document.write('<pre>'+JSON.stringify(res));
Explanations
We split the problem into two steps:
combine arguments into one big array
remove non-unique elements from this big array
This part handles the first step:
[].concat.apply([], arguments)
The built-in method someArray.concat(array1, array2 etc) appends given arrays to the target. For example,
[1,2,3].concat([4,5],[6],[7,8]) == [1,2,3,4,5,6,7,8]
If our function had fixed arguments, we could call concat directly:
function unite(array1, array2, array3) {
var combined = [].concat(array1, array2, array3);
// or
var combined = array1.concat(array2, array3);
but as we don't know how many args we're going to receive, we have to use apply.
someFunction.apply(thisObject, [arg1, arg2, etc])
is the same as
thisObject.someFunction(arg1, arg2, etc)
so the above line
var combined = [].concat(array1, array2, array3);
can be written as
var combined = concat.apply([], [array1, array2, array3]);
or simply
var combined = concat.apply([], arguments);
where arguments is a special array-like object that contains all function arguments (actual parameters).
Actually, last two lines are not going to work, because concat isn't a plain function, it's a method of Array objects and therefore a member of Array.prototype structure. We have to tell the JS engine where to find concat. We can use Array.prototype directly:
var combined = Array.prototype.concat.apply([], arguments);
or create a new, unrelated, array object and pull concat from there:
var combined = [].concat.apply([], arguments);
This prototype method is slightly more efficient (since we're not creating a dummy object), but also more verbose.
Anyways, the first step is now complete. To eliminate duplicates, we use the following method:
combined.filter(function(elem, index) {
return combined.indexOf(elem) === index;
})
For explanations and alternatives see this post.
Finally, we get rid of the temporary variable (combined) and chain "combine" and "dedupe" calls together:
return [].concat.apply([], arguments).filter(function(elem, index, self) {
return self.indexOf(elem) === index;
});
using the 3rd argument ("this array") of filter because we don't have a variable anymore.
Simple, isn't it? ;) Let us know if you have questions.
Finally, a small exercise if you're interested:
Write combine and dedupe as separate functions. Create a function compose that takes two functions a and b and returns a new function that runs these functions in reverse order, so that compose(a,b)(argument) will be the same as b(a(argument)). Replace the above definition of unite with unite = compose(combine, dedupe) and make sure it works exactly the same.
You can also try this :
var Data = [[1, 2, 3], [5, 2, 1, 4], [2, 1], [6, 7, 8]]
var UniqueValues = []
for (var i = 0; i < Data.length; i++) {
UniqueValues = [...new Set(UniqueValues.concat(Data[i]))]
}
console.log(UniqueValues)
I'm reading a book called Eloquent JavaScript. There's an exercise in it that requires one to flatten a heterogeneous array & after trying so long and failing to get the answer, I looked up the solution online & couldn't understand the code. I'm hoping someone will be kind enough to explain, especially for argument "flat" and how it's supposed to work. The code is below
var arrays = [[1, 2, 3], [4, 5], [6]];
console.log(arrays.reduce(function(flat, current) {
return flat.concat(current);
}, []));
The reduce function defined in the book is:
function reduce(array, combine, start) {
var current = start;
for (var i = 0; i < array.length; i++)
current = combine(current, array[i]);
return current;
}
and as a method of an array,
arr.reduce(combine, start);
Let's look at each part of the reduce method. The book describes it as "folding up the array, one element at a time." The first argument for reduce is the "combiner function", that accepts two arguments, the "current" value and the "next" item in the array.
Now, the initial "current" value is given as the second argument of the reduce function, and in the solution of flattening arrays, it is the empty array, []. Note that in the beginning, the "next" item in the array is the 0th item.
Quoting the book to observe: "If your array contains at least one element, you are allowed to leave off the start argument."
It may also be confusing that in the flattening solution, current is placed as the second argument to reduce, whereas in the reduce definition above, current is used to assign the cumulative, folded value. In the flattening solution, current refers to the "next" arrays item (the individual array of integers)
Now, at each step of the reduction, the "current" value plus the next array item is fed to the (anonymous) combiner, and the return value becomes the updated "current" value. That is, we consumed an element of the array and continue with the next item.
flat is merely the name given to the accumulated result. Because we wish to return a flat array, it is an appropriate name. Because an array has the concat function, the first step of the reduce function is, (pretending that I can assign the internal variables)
flat = []; // (assignment by being the second argument to reduce)
Now, walk through the reduction as iterating over arrays, by going through the steps shown above in reduce's definition
for (var i = 0; i < arrays.length; i++)
flat = combine(flat, arrays[i]);
Calling combine gives [].concat([1, 2, 3]) // => [1, 2, 3]
Then,
flat = [1, 2, 3].concat([4, 5]) // => [1, 2, 3, 4, 5]
and again for the next iteration of the reduction. The final return value of the reduce function is then the final value of flat.
This would be the solution I came with with ES6 format:
const reduced = arrays.reduce((result,array) => result.concat(array),[]);
console.log(reduced);
I have implemented this solution and this seems to work for nested arrays as well.
function flattenArray(arr){
for(var i=0;i<arr.length;i++){
if(arr[i] instanceof Array){
Array.prototype.splice.apply(arr,[i,1].concat(arr[i]))
}
}
return arr;
}
There is an easy way to do these exercises. those functions are already built inside the javascript so you can use them easily.
But the whole joy of this exercise is to create those functions:
Create reduce function. Reduce function should add all array elements. you can use a higher-order function or just a normal one. here is an example for higher-order:
function reduce(array, calculate){
let sumOfElements = 0;
for(let element of array){
sumOfElements = calculate(sumOfElements, element)
}
return sumOfElements
}
Next step is to create a concat function. since we need to return those reduced arrays in new array we will just return them. (Warning: you must use rest parameter)
function concat(...arr){
return arr
}
And for last. you will just display it (You can use any example)
console.log(concat(reduce([1, 2, 3, 4], (a, b) => a + b), reduce([5, 6], (a, b) => a + b)))
The reduce method acts as a for loop iterating over each element in an array. The solution takes each array element and concatenates it to the next one. That should flatten the array.
var arr =[[1,2],[3,4],[5,6]]
function flatten(arr){
const flat= arr.reduce((accumulator,currentValue)=>{
return accumulator.concat(currentValue)
})
return flat
}
console.log(flatten(arr))
//Output 1,2,3,4,5,6
I'm just learning how to use JS higher-order functions (map, forEach, reduce, etc), and have stumbled into confusion. I'm trying to write a simple 'range' function, but can't seem to populate my output array. This is the goal:
range(1, 4) // [1, 2, 3, 4]
I'm getting this:
[undefined × 4]
Here is my code:
function range(num1, num2) {
var rangeArr = new Array((num2 + 1) - num1);
return rangeArr.map(function(e, i, arr) {return arr[i] = num1 + i});
}
What am I missing here? As far as I can tell the problem appears to have something to do with the way I'm utilizing 'new Array', but beyond that I'm lost.
Oh, and here's the part that really confuses me. This works fine:
function bleck() {
var blah = [1, 2, 3, 4];
var x = 'wtf';
return blah.map(function(e, i, arr) {return arr[i] = x})
}
["wtf", "wtf", "wtf", "wtf"]
Thanks!!
The forEach method iterates over the indices of the array. Interestingly enough, when you create a new array via new Array(n), it contains no indices at all. Instead, it just sets its .length property.
> var a = new Array(3);
> console.info(a)
[]
> console.info([undefined, undefined, undefined])
[undefined, undefined, undefined]
MDN describes forEach, and specifically states:
forEach executes the provided callback once for each element of the
array with an assigned value. It is not invoked for indexes which have
been deleted or elided.
Here's a neat technique to get an array with empty, but existing, indices.
var a = Array.apply(null, Array(3));
This works because .apply "expands" the elided elements into proper arguments, and the results ends up being something like Array(undefined, undefined, undefined).
The array is defined with 4 entires each of which is undefined.
Map will not iterate over undefined entires, it skips them.
callback is invoked only for indexes of the array which have assigned
values; it is not invoked for indexes that are undefined, those which
have been deleted or which have never been assigned values.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
When you create a new Array(x) it is creating what is called a sparse array, which might behave a bit differently, as you can see, some browsers will say [undefined x 20,"foo", undefined x 5] if you just set one value, and I believe it doesn't iterate over those values.
The problem is that map doesn't iterate undefined entries (*).
I suggest using a for loop instead:
var rangeArr = new Array((num2 + 1) - num1);
for(var i=0; i<=num2-num1; ++i)
rangeArr[i] = num1 + i;
return rangeArr;
(*) With undefined entries I mean rangeArr.hasOwnProperty(i) === false, not to be confused with rangeArr[i] === void 0.
Why
for (i in [1, 2, 3]) console.log(typeof(i), i);
gives this:
[Log] string 0
[Log] string 1
[Log] string 2
I've expected numbers.
# Safari 7.0 (9537.71), Mac OS X 10.9
Try instead:
var arr = [1, 2, 3];
for (var i in arr)
console.log(typeof arr[i], arr[i]);
You're getting strings rather than numbers because for..in loops iterate keys/properties (in this case, the Array's indices) rather than values.
To get the value of each key, i, you'll have to use a property accessor, arr[i].
Though, why strings at all rather than the original number indices is because, with current standards, all properties are strings.
console.log(Object.keys( ['foo', 'bar', 'baz'] )); // ["0", "1", "2"]
Any value can actually be used with property accessors, but it'll be converted ToString() before it's actually used as a key/property.
6) Let propertyNameString be ToString(propertyNameValue).
Maps and Symbols are currently planned to be the exceptions to that.
Also, you may find "Why is using “for…in” with array iteration such a bad idea?" of interest and at least consider using a simple for loop instead:
var arr = [1, 2, 3];
for (var i = 0, l = arr.length; i < l; i++)
console.log(typeof arr[i], arr[i]);
Though, for future reference, the upcoming ECMAScript 6 standard has added for..of loops, which should iterate as you were expecting.
for (var i of [1, 2, 3])
console.log(typeof i, i); // number, 1, 2, 3
That is because an Array in Javascript is a special Object with property keys (which are strings) used as indices.
you are iterating that Array like an Object and because of that i is seen as a property key, a string.
To iterate in the right way an Array you have to use the following:
for( var i=0; i < [1,2,3].length; i++){ ... }