So, I'm new to programming and I am having a bit of a trouble here. See, I wanted to write a small range function, like range(a,b) that would return an array with all numbers between a and b. So I googled and found this one:
const range = (min, max) => [...Array(max - min + 1).keys()].map(i => i + min);
This works perfectly fine, but I'm having a bit of trouble undertanding it, especially with the .keys() part. I thought .keys() was an Object function, that would return the key of a key/value pair of an object, but here it seems to me that it's been used in an Array.
What am I understanding wrong here?
Appreciate the help!
Arrays also have a keys method and it's central to this range function working as it should.
Here's how that works:
It creates an empty (sparse) array of the appropriate length (Array(max - min + 1)).
It gets an iterator for the valid indexes of that array (.keys()). Valid indexes in an arrays are the numbers 0 through the length of the array minus one. (Technically, in specification terms, the indexes of arrays are string property names like "0", "1", etc., but the keys iterator returns numbers because we typically use numbers to index into arrays and that's the more useful value to provide.)
It spreads the elements from that iterator out into an array ([...]).
It creates a new array by mapping each value in the array from #3, adding the min value to them (.map(i => i + min)).
Note that this creates multiple intermediate arrays and other objects. It's unlikely to matter, but it can be done much more efficiently. For instance, with Array.from as shown by Nina, or even just a simple loop:
const range = (min, max) => {
const result = [];
for (let n = min; n < max; ++n) {
result.push(n); // Or: `result[result.length] = n;` if you want to
// avoid the function call
}
return result;
};
const range = (min, max) => {
const result = [];
for (let n = min; n < max; ++n) {
result.push(n);
}
return result;
};
console.log(range(5, 10));
That's not nearly as cool-looking, though. :-D
But, I probably wouldn't have range return an array at all unless I know for sure that's the end product I'm always going to need. Instead, I'd have it return an iterator:
const range = function*(min, max) {
for (let n = min; n < max; ++n) {
yield n;
}
};
for (const value of range(0, 10)) {
console.log(value);
}
.as-console-wrapper {
max-height: 100% !important;
}
You can always spread it out into an array (or use it with Array.from) if you need an array.
Arrays have keys as well and returns an iterator for returning keys. This requires a spreading into an array or use a function which takes iterables and returns an array.
For this purpose Array.from is made - and this has a mapping function as well.
Instead of builing more than one array, you could take Array.from directly with an object with a length property and a mapping function.
const range = (min, max) => Array.from(
{ length: max - min + 1 },
(_, i) => i + min
);
console.log(...range(7, 9));
Related
I'm trying to solve some 'hackerrank.com' coding challenges.
I'm stuck on this one:
You will be given an array of integers arr and a single integer k. You must create an array arr' of length k from elements of arr such that its unfairness is minimized.
Unfairness is defined as max(arr') - min(arr')
The function should return the minimum possible unfairness
My code works fine for the majority of test cases. However, in three of those test cases - the ones where the size of arr and k is particularly huge - fail due to an excession of the given time limit.
How can I optimize the performance of my code?
Thanks in advance
function maxMin(k, arr) {
// create array to push unfairness values to
var unfairnesses = [];
// sort given array in ascending order
arr.sort(function(a, b) {
return a - b;
});
// loop over sorted array
for(var i = 0; i < arr.length - k + 1; i++) {
// get array with the length of k at position i
var tempArr = arr.slice(i, i + k);
// determine Max and Min of the sliced array
var tempArrMax = Math.max(...tempArr);
var tempArrMin = Math.min(...tempArr);
// get unfairness of the sliced array
var thisUnfairness = tempArrMax - tempArrMin;
// push sliced-array-unfairness to unfairnesses array
unfairnesses.push(thisUnfairness);
}
// return minimal value of unfairnesses array
return Math.min(...unfairnesses);
}
The two first steps could be:
Your array is sorted. Thus there's no need to use Math.max and Math.min - the first element of a slice is the smallest, the last is the largest.
When you eliminate Math.max and Math.min calls, you can remove Array.prototype.slice call. Then you're left with a sort and a single pass over the array.
To sort the array you're already looping one time over the whole thing.
Then you're looping another time to figure out which one is max and which one is the minimum.
You're looping twice as you can only loop once if you do:
function minMax(array) {
const safeArray = array ?? []
// No max or min as array is empty
if(safeArray.length === 0)
return [undefined, undefined]
let max: number = Number.MIN_SAFE_INTEGER
let min: number = Number.MAX_SAFE_INTEGER
for(let item of safeArray) {
max = Math.max(item, max)
min = Math.min(item, min)
}
return [max, min]
}
I am trying to sum up the numbers inside an array e.g. from here: https://jsfiddle.net/4r8dtxhz/12/
here is the Code:
var someObj = [{name:"hi", series: [1,2,10,4,5,6]},{name:"ho",series:[3,7,6,9,12,1,3,20,3,1]}]
for (var doc of someObj) {
this.min = doc.series.reduce((agg,val) => val < agg? val:agg, doc.series[0]);
this.max = doc.series.reduce((agg,val) => val > agg? val:agg, doc.series[0]);
}
console.log(max)
var test = Array.from(someObj.map((doc)=>doc.series)).reduce((accumulator, currentValue) => accumulator + currentValue);
console.log(typeof test)
console.log(test)
I was expecting the reduce function to sum up the numbers in the object series array... so I am wondering what goes wrong here?
Your map function is producing an two dimensional array of [someObj[0].series, someObj[1].series].
When you add two arrays together using the + operator in your reducer, it converts them to a string and concatenates the string.
If you want to create an array of the sum of each series, introduce another map function which has a reduce inside it.
You are missing a step to flatten the result of your map step. Your code
someObj.map((doc) => doc.series)
will return an array of arrays (or 2D array) rather than a flat array.
If you add a step to flatten the 2D array after your map step—for example by
.reduce((flattened, series) => flattened.concat(series))
using Array.reduce again—you will get the expected result.
Note that you should always provide an initial value for the accumulator of reduce (in your case 0 was missing for the summation) to assure that + is getting resolved correctly to number addition (otherwise it will be string concatenation, e.g. [1]+[2] === '12').
Also, Array.from wasn't necessary since someObj already is an array (Array.from converts from an Iterable object to Array).
var someObj = [{name:"hi", series: [1,2,10,4,5,6]},{name:"ho",series:[3,7,6,9,12,1,3,20,3,1]}]
for (var doc of someObj) {
this.min = doc.series.reduce((agg,val) => val < agg? val:agg, doc.series[0]);
this.max = doc.series.reduce((agg,val) => val > agg? val:agg, doc.series[0]);
}
console.log(max)
var test = someObj.map((doc)=>doc.series)
.reduce((flattened, series) => flattened.concat(series))
.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(typeof test)
console.log(test)
Rather than getting, for example, the first 5 keys from an object with 10 keys:
var keys = Object.keys(brain.layers[this.layer]).slice(0, 5);
I'd like to get 5 of the keys at random. I know of bulky, long, roundabout ways of doing it, such as something like this:
function getRandomNumber(n1, n2) { ... }
var list = [];
var count = 0;
function choose(arr, count, list, max) {
for (let prop in arr) {
var choice = Math.round(getRandomNumber(0, 1));
if (choice === 1 && count < max && !list.includes(arr[prop])) {
list.push(arr[prop]);
count++;
}
}
if (count >= max) {
return list;
} else {
choose(arr, count, list, max)
}
}
But I was wondering if there's a simpler, more elegant solution.
To get truly a truly (pseudo)random sort use something like an in place random sort.
let arrRand=(a,i=a.length)=>{while(i){a.push(a.splice(Math.random()*i--|0,1)[0])}}
let keys = Object.keys(brain.layers[this.layer])
randSort(keys)
keys=keys.slice(0,5)
This takes an array and the array length though if it is a modern browser you can use default arguments for that. Warning it modifies the passed in array.
I wrote a shuffling program below and ran it through "Will It Shuffle?". The results appear to show that it's working in the console; it's shuffling the array. But the website shows me an all red box, making me think something is wrong with my code, but I don't see it.
function shuffle (array) {
var arr = [],
length = array.length,
el;
while (length > 0) {
var randomEl = Math.floor(Math.random() * (length - 0) - 0);
if (length > 1) {
el = array.splice(randomEl,1);
} else {
el = array.splice(0,1);
}
arr.push(el[0]);
length -= 1;
}
return arr;
}
That page ignores the returned value of the function, because it expects an in-place sort.
If you add this at the end of your code, it works as expected:
array.push(...arr);
You can also do it in-place directly:
function shuffle (array) {
var length = array.length;
while (length) {
var randomEl = Math.floor(Math.random() * length);
var el = array.splice(randomEl, 1);
array.push(el[0]);
--length;
}
}
They alter the array, you do not alter the array.
You need to alter the original array, not return a new array.
function shuffle (array) {
var arr = [],
length = array.length,
el;
while (length > 0) {
var randomEl = Math.floor(Math.random() * (length - 0) - 0);
if (length > 1) {
el = array.splice(randomEl,1);
} else {
el = array.splice(0,1);
}
arr.push(el[0]);
length -= 1;
}
//replace array with the new items
//it is like using concat, but does not produce a new array,
//just appends it to the original which has zero items in it.
Array.prototype.push.apply(array, arr);
}
What you are doing is creating a new array with the elements of the original shuffled around.
However, if you go back and look at the array that you passed in, you'll notice it has not been shuffled, but rather emptied. Apparently, this is not what "Will it Shuffle?" asks you to do.
splice() and push() both always mutate the array you call those methods on.
To answer your question about .push(...arr), an elipses in javascript is a feature that arrived with the latest version, EcmaScript 2015. It is the "spread operator".
When you call a function with a "spread" array, it's like calling the function with the contents of the array as separate arguments. For instance,
array.push(...[1,2,3])
is the same as calling
array.push(1,2,3)
push() can add an arbitrary number of comma-separated arguments to an array. So, after emptying the array argument with your looped splices, you could push the contents of the newly-created arr to the empty array using the spread operator.
Usually when I need to find the max value of an array I use this very simple code:
var max = Math.max.apply(Math, array);
However, now I have a multidimensional array in which for each line I have an array with 5 columns. Is there a similar way to find the max value for a certain column?
Right now I'm doing:
var maxFunc = function(data){
var max = 0;
data.forEach(function(value){
max = Math.max(max, value[0]);
});
return max;
};
I was curious if there was a prettier/simpler way of doing this?
I would write it as such:
Math.max.apply(Math, array.map(v => v[0]));
The array.map will transform the original array based on your picking logic, returning the first item in this case. The transformed array is then fed into Math.max()
To avoid creating a new array, you can also reduce the array to a single value:
array.reduce((max, current) => Math.max(max, current[0]), -Infinity)
As you can see, we need to add the initial value of -Infinity, which is returned if the array is empty.
This is a great application for Array.prototype.reduce:
max = data.reduce(function(previousVal, currentItem, i, arr) {
return Math.max(previousVal, currentItem[0]);
}, Number.NEGATIVE_INFINITY);
This also avoids the bug in your code that would happen if all the values in data are less than 0. You should be comparing against Number.NEGATIVE_INFINITY rather than 0.
Alternatively, you could normalize the data before reducing to the max value:
max = data.map(function (d) {
return d[0];
}).reduce(function (p, c, i, arr) {
return Math.max(p, c);
});