Related
I was given this problem at one of my interviews and was told I have 20 minutes to solve it. This is the answer I came up with ( 2 versions ). Can you let me know which version you prefer and why, and if you have a better idea of how to solve it (less complex, less memory usage, etc.) Please share.
Problem: You have an array of random numbers that range from 0 to 100 elements.
Write a function that will split this array into several arrays, each containing elements in the following range: (0-10],(10-20],(20-30], etc up to a 100].
Write a function that outputs these arrays in a form of a simple graph, where each delimiter represents a single value in the array.
Array = [10, 12, 71, 52, 51, 1, 5, 22, 21, 6, 95, 11, 3, 64, 45, 55,
65, 42, 99, 4];
Desired outcome:
5 Elements in array: ***** - 1,5,6,3,4
3 Elements in array: *** - 10,12,11
2 Elements in array: ** - 22,21
No Elements in array.
2 Elements in array: ** - 45,42
3 Elements in array: *** - 52,51,55
2 Elements in array: ** - 64,65
1 Elements in array: * - 71
No Elements in array.
2 Elements in array: ** - 95,99
// Version 1
arr = [10, 12, 71, 52, 51, 1, 5, 22, 21, 6, 95, 11, 3, 64, 45, 55, 65, 42, 99, 4];
const splitArray = (inputArray, range) => {
const newArray = [];
do {
let tempArray = [];
tempArray = inputArray.filter((item) => {
if (item >= range && item < range + 10) return item;
});
range += 10;
newArray.push(tempArray);
} while (range + 10 <= 100);
return newArray;
};
const printArrays = (array, delimiter) => {
let toPrint = "";
for (index in array) {
let stars = array[index].length;
let string = "";
for (let i = stars; i > 0; i--) {
string += delimiter;
}
toPrint += stars
? `${stars} Elements in array: ${string} - ${array[index]} \n`
: "No Elements in array. \n";
}
return toPrint;
};
console.log(printArrays(splitArray(arr, 0), "*"));
// Version 2
arr = [10, 12, 71, 52, 51, 1, 5, 22, 21, 6, 95, 11, 3, 64, 45, 55, 65, 42, 99, 4];
const getArrays = (inputArray) => {
const newArray = [];
let min = 0;
let max = 10;
do {
const tempArray = [];
for (i in arr) {
let val = arr[i];
val >= min && val < max ? tempArray.push(val) : "";
}
min += 10;
max += 10;
newArray.push(tempArray);
} while (max <= 100);
return newArray;
};
const printArrays = (array, delimiter) => {
for (index in array) {
let stars = array[index].length;
let string = "";
for (let i = stars; i > 0; i--) {
string += delimiter;
}
console.log(
stars ? `${stars} Elements in array: ${string} - ${array[index]}` : "No Elements in array."
);
}
};
printArrays(getArrays(arr), "^");
Both approaches have moderate issues.
The first approach does
let tempArray = [];
tempArray = inputArray.filter((item) => {
if (item >= range && item < range + 10) return item;
});
Better to just declare the tempArray as the filtered array to begin with.
const tempArray = inputArray.filter(...
Also, return item is suspicious inside a filter - all the filter callback cares about is whether its return value is truthy or falsey. Returning the array item when you actually want to indicate that the value should be included in the output is a common mistake. It happens not to be a problem here because 0 isn't a possibility, but it's still confusing. A better choice would be to do
const tempArray = inputArray.filter(
item => item >= range && item < range + 10
);
(and maybe rename range to startOfRange)
Both of your approaches are also iterating through the entire input array multiple times (once for each range), which seems a bit wasteful - better to iterate through the input once.
Your second approach uses for (i in arr), and both approaches are doing for (index in array). This is a bad idea, and since you don't actually care about the index you're iterating over, it'd make sense to use for..of loops instead.
I think a better looking approach that iterates through the input just once would be:
const arr = [10, 12, 71, 52, 51, 1, 5, 22, 21, 6, 95, 11, 3, 64, 45, 55, 65, 42, 99, 4];
const getArrays = (inputArray) => {
const grouped = {};
for (let i = 0; i < 100; i += 10) {
grouped[i] = [];
}
for (const item of inputArray) {
const rangeProp = Math.floor(item / 10) * 10;
grouped[rangeProp].push(item);
}
return Object.values(grouped);
};
const printArrays = (groupedArrays, delimiter) => {
for (const array of groupedArrays) {
const stars = delimiter.repeat(array.length);
console.log(
stars
? `${array.length} Elements in array: ${stars} - ${array.join(',')}`
: "No Elements in array."
);
}
};
printArrays(getArrays(arr), "*");
I will do that this way :
This approach is simple: it retrieves the values one by one and adds them to the array corresponding to their range.
const arr = [10, 12, 71, 52, 51, 1, 5, 22, 21, 6, 95, 11, 3, 64, 45, 55, 65, 42, 99, 4];
let ranges = arr.reduce((a,x)=>
{
let range = (x/10)|0 // get range start value 0 to 9
a[range] ??= [] // create the array of if it does not already exist
a[range].push(x)
return a
},{})
console.log('ranges=', ranges ) // so that noobs can visualize this result
for (let r = 0; r < 10; r++ )
{
if (!ranges[r])
document.write('No Elements in array.<br>')
else
{
let count = ranges[r].length
document.write(`${count} Elements in array: ${'*'.repeat(count)} - ${ranges[r].join(',')}<br>`)
}
}
.as-console-wrapper {max-height: 100% !important; width:20%; top: 0;
margin-left: 80%; }
.as-console-row::after {display: none !important;}
range = (x/10)|0 // get range start value 0 to 9
example in case of x = 25 -> 25/10 give 2.5 and 2.5 | 0 give 2 -> integer part value of 2.5
| is the OR boolean operator, work only on integers values so it return an interger
??= is Logical nullish assignment
I can't get a result I wanted:
Even Array is: 2, 18, 38, -10, 0, 104
Odd Array is: 3, 13, -5, 11
Two arrays where one contains even numbers another odd.
Here is the code:
let arrayMain = [2, 3, 13, 18, -5, 38, -10, 11, 0, 104];
let oddArray = [];
let evenArray = [];
for (let i = 0; i < arrayMain.length; i++){
if (i % 2 == 0) {
evenArray.push(arrayMain[i]);
}
else {
oddArray.push(arrayMain[i]);
}
}
console.log("Odd Array is " + oddArray);
console.log("Even Array is " + evenArray);
It gives me:
Odd Array is 3,18,38,11,104
Even Array is 2,13,-5,-10,0
How can I fix this?
You need to check the value, not the index.
if (arrayMain[i] % 2 == 0) {
// ^^^^^^^^^^ ^
To get single even or odd number from a array. Example:
[20, 10, 11, 200, 30] => will return 11.
[31, 23, 45, 20, 43] => will return 20.
I have tried the below function to achieve this requirement:
function getEvenOrOddNum(arr) {
var checkVal, num, i, len = arr.length;
if (len > 2) {
for (i = 0; i < 3; i++) {
var mod = arr[i] % 2;
if (checkVal == mod) { break; }
checkVal = mod;
}
checkVal = checkVal == 0 ? 1 : 0;
num = arr.filter((val) => val % 2 == checkVal);
num = num.length < 2 ? num[0] : null;
}
return num || null;
}
console.log(getEvenOrOddNum([20, 10, 11, 200, 30])) //=> return 11
console.log(getEvenOrOddNum([31, 23, 45, 20, 43])) //=> return 20
console.log(getEvenOrOddNum([20, 10])) //=> return null
console.log(getEvenOrOddNum([20, 10, 11, 23, 200, 30])) //=> return null
console.log(getEvenOrOddNum([31, 23, 45, 20, 43, 50])) //=> return null
You could coount the types and store the last value of each type and check if two values have the same type, then take a flag for the wanted type.
Return then the type with the flag and check the count before.
l means the left element and r the right one which is the actual item as well. Bothe elements are neede for comparing both types of even/odd.
For example:
vv actual item
[20, 10, 11, 200, 30] array
l r variables
function getSingle(array) {
var count = [0, 0],
values = [],
flag;
array.forEach((r, i, { length, [(length + i - 1) % length]: l }) => {
if (l % 2 === r % 2) flag = 1 - r % 2;
values[r % 2] = r;
count[r % 2]++;
});
return count[flag] === 1
? values[flag]
: null;
}
console.log(getSingle([20, 10, 11, 200, 30])); // 11
console.log(getSingle([31, 23, 45, 20, 43])); // 20
console.log(getSingle([20, 10])); // null
console.log(getSingle([20, 10, 11, 23, 200, 30])); // null
console.log(getSingle([31, 23, 45, 20, 43, 50])); // null
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Array.prototype.find().
This will call the provided function for each array element, and will return the first element which 'match the condition' (the provided function returns true for):
const evenArray=[2,4,6,8,9]
const oddArray=[1,2,3,5,7,9]
console.log(evenArray.find(element=> element % 2 === 1)) //9
console.log(oddArray.find(element=> element % 2 === 0)) //2
I have a sorted array:
[0, 1, 1, 2, 3, 4, 6, 6, 6, 50, 70, 80, 81, 100, 10000]
I am trying to use a binary search to parse this sorted array and return all values of the array that are greater or equal to a given value. So if I feed my search 6, the resulting array I desire would be:
[6, 6, 6, 50, 70, 80, 81, 100, 10000]
I cannot just look for the first occurence of a value, because the value may not be present in the array. For example, I may have to look for 5, which is not present, but my output would need to be the same as above.
I have some code here that is stuck in an infinite loop at this jsFiddle: http://jsfiddle.net/2mBdL/691/
var arr = [0, 1, 1, 2, 3, 4, 6, 6, 6, 50, 70, 80, 81, 100, 10000];
function binarySearch(arr, i) {
var mid = Math.floor(arr.length / 2);
var leftOfMid = mid - 1;
console.log(arr[mid], i);
if (arr[mid] == i && arr[leftOfMid] == i) {
console.log('needle is exactly at 6, but left is same, checking lower half', arr[mid], i);
binarySearch(arr.splice(leftOfMid, Number.MAX_VALUE), i);
} else if (arr[mid] < i && arr.length > 1) {
console.log('needle is at a value that is less than 6', arr[mid], i);
binarySearch(arr.splice(mid, Number.MAX_VALUE), i);
} else if (arr[mid] > i && arr.length > 1) {
console.log('needle is at a value that is higher than 6 days', arr[mid], i);
binarySearch(arr.splice(0, mid), i);
} else if (arr[mid] == i && arr[leftOfMid] < i) {
console.log('MATCH, needle is the beginning of the range', arr[mid], i);
return arr[mid];
}
else {
console.log('not here', i);d
return -1;
}
}
var result = binarySearch(arr, 6);
console.log(result);
How could I utilize a binary search for this use case? The array can get very large, so I am really trying to get this to work!
You can use binary search for this. You just need to search for the boundary where the values go from being less than the target to greater or equal to the target.
function bsearch(arr, lim, start = 0, end = arr.length - 1) {
let index = Math.floor((end - start) / 2) + start
if (start >= end) return -1
if (arr[index] > lim)
return (arr[index - 1] < lim || arr[index - 1] === undefined) ? index : bsearch(arr, lim, start, index)
if (arr[index] < lim)
return arr[index - 1] > lim ? index : bsearch(arr, lim, index + 1, end)
if (arr[index] === lim) {
return (arr[index - 1] === lim) ? bsearch(arr, lim, start, index) : index
}
}
let arr = [0, 1, 1, 2, 3, 4, 6, 6, 6, 50, 70, 80, 81, 100, 10000]
// all values >= 6
let index = bsearch(arr, 6)
console.log(arr.slice(index))
// all values >= 5
index = bsearch(arr, 5)
console.log(arr.slice(index))
On large arrays this will have all the benefits of binary search. For example searching an array of 10000 elements only takes about 13 iterations.
You can simply use Array.prototype.filter to filter elements in an array.
array.filter(i => i >= 6)
console.log(
[0, 1, 1, 2, 3, 4, 6, 6, 6, 50, 70, 80, 81, 100, 10000]
.filter(i => i >= 6)
);
Or in a functional form:
function search(arr, n) {
return arr.filter(i => i >= n);
}
let arr = [0, 1, 1, 2, 3, 4, 6, 6, 6, 50, 70, 80, 81, 100, 10000]
function search(arr, n) {
return arr.filter(i => i >= n);
}
console.log( search(arr, 6) );
Binary search will not help here because it searches for a single value not for a range so you can not know if the value you are testing in current iteration is the first one that is greater than 6 or not. The solution is really simple:
arr.filter(e => e > i);
A binary search can really help with large arrays, for smaller ones it likely doesn't matter. I think your strategy is OK but the implementation seems a bit convoluted so I can't see where your error is.
Consider the following, I've tried to keep it simple with comments:
var arr = [0, 1, 1, 2, 3, 4, 6, 6, 6, 50, 70, 80, 81, 100, 10000];
function binaryFind(arr, value) {
// Helper: go down until stop getting matches
function goLeft(arr, value, pos) {
while (arr[pos - 1] == value) --pos;
return arr.slice(pos);
}
// Find match or first value larger
function search(arr, value, pos, iteration, direction) {
// Update position for this iteration
++iteration;
pos += direction * arr.length / (iteration*2) | 0;
// Check if at start or end of array
if (pos >= arr.length) {
return [];
} else if (pos <=0) {
return arr.slice(0);
}
// Hit value
if (arr[pos] == value) {
return goLeft(arr, value, pos);
}
// Under
if (arr[pos] < value) {
// Check higher adjacent
if (arr[pos + 1] >= value) {
return arr.slice(pos+1);
}
//Otherwise search up
return search(arr, value, pos, iteration, 1);
}
// Over
if (arr[pos] > value) {
// Check lower adjacent
if (arr[pos - 1] < value) {
return arr.slice(pos);
}
// Otherwise search down
return search(arr, value, pos, iteration, -1);
}
}
return search(arr, value, 0, 0, 1);
}
[0,1,2,3,4,5,6,7,55,1e6,555,70,69,-1,71].forEach(v =>
console.log(v + ': ' + binaryFind(arr, v))
);
Mark Meyer's idea of searching for the boundary got me thinking, so here's a version that should be more efficient as it continues to use a binary search rather than "walking" down matching values (it looks for the first value that is le). Of course testing with real data will determine whether that's a benefit or not but it's a simpler solution.
The search function is wrapped in an outer function to protect the additional parameters from abuse. Without the wrapper it's 3 lines of code, but one is a bit long. ;-)
// Use a binary search to find all elements in a sorted array
// that are equal to or greater than value.
function binaryFind(arr, value) {
function search(arr, value, pos=0, iteration=1, direction=1) {
pos += direction * arr.length / (iteration*2) | 0;
return pos >= arr.length? [] :
pos <=0? arr.slice(0) :
arr[pos] < value? (arr[pos + 1] >= value? arr.slice(pos+1) : search(arr, value, pos, ++iteration, 1)):
(arr[pos - 1] < value? arr.slice(pos) : search(arr, value, pos, ++iteration, -1));
}
return search(arr, value);
}
// Testing
var arr = [0, 1, 1, 2, 3, 4, 6, 6, 6, 50, 70, 80, 81, 100, 10000];
[0,1,2,3,4,5,6,7,55,1e6,555,70,69,-1,71].forEach(v =>
console.log(v + ': ' + binaryFind(arr, v))
);
Actually I have made this on excel using Vlookup but now I am making this on webpage.
I have a input box where user will enter the value
<input class="text" type="text" name="rawScore" onchange="calcpercentile()">
and I have a span of where user can get the result
<span id="percentile"></span>
I have two arrays
var percentile = [10, 20, 30, 40, 50, 60, 70, 80, 90];
var rawScores = [1, 3, 5, 7, 10, 12, 18, 25, 27];
what code should I write that if I write so I get the
input value
(rawScores) (percentile)
1 10
2 20
3 30
4 40
Your example seems wrong. I expect score 1 to map to the 10th percentile, 2 & 3 to the 20th percentile, and 4 to the 30th percentile.
In essence, I think what you're trying to do is: find the array index of the first raw score that is greater than the input, and return the corresponding value from the percentiles array.
The Javascript could look something like this:
var percentiles = [10, 20, 30, 40, 50, 60, 70, 80, 90];
var rawScores = [1, 3, 5, 7, 10, 12, 18, 25, 27];
function map(input) {
let index = rawScores.findIndex(rawScore => rawScore >= input);
return percentiles[index];
}
console.log(map(1));
console.log(map(2));
console.log(map(3));
console.log(map(4));
Note that browser support for Array#findIndex() is limited. If you need wide browser support, a simple loop-based approach might be better:
var percentiles = [10, 20, 30, 40, 50, 60, 70, 80, 90];
var rawScores = [1, 3, 5, 7, 10, 12, 18, 25, 27];
function map(input) {
for (var i = 0; i < rawScores.length; i++) {
if (rawScores[i] >= input) {
return percentiles[i];
}
}
}
console.log(map(1));
console.log(map(2));
console.log(map(3));
console.log(map(4));
you can input text : 1
span display "10"
window.onload = function(){
var percentile = [0,10, 20, 30, 40, 50, 60, 70, 80, 90];
document.getElementById("rawScore").onchange = function () {
var index = document.getElementById("rawScore").value;
document.getElementById("percentile").innerHTML = percentile[index];
}
}
<input class="text" type="text" id="rawScore">
<span id="percentile"></span>
First you sort your dataset of course
const arr = [0,2,5,2,7,3];
const data = arr.sort();
What may help next, is this function to find the index of the closest number.
console.log(findClosestIndex([0, 1, 2, 3.5, 4.5, 5], 4));
// output: 3
console.log(findClosestIndex([0, 1, 2, 3.49, 4.5, 5], 4));
// output: 4
console.log(findClosestIndex([0, 1, 2, 3.49, 4.5, 5], 90));
// output: 5
console.log(findClosestIndex([0, 1, 2, 3.49, 4.5, 5], -1));
// output: 0
function findClosestIndex(arr, element) {
let from = 0, until = arr.length - 1
while (true) {
const cursor = Math.floor((from + until) / 2);
if (cursor === from) {
const diff1 = element - arr[from];
const diff2 = arr[until] - element;
return diff1 <= diff2 ? from : until;
}
const found = arr[cursor];
if (found === element) return cursor;
if (found > element) {
until = cursor;
} else if (found < element) {
from = cursor;
}
}
}
So, now you know your index and the length of your array. And you have to get a percentile from that. Let's first calculate an exact percentage.
const index = findClosestIndex(data, input);
const pct = index / arr.length;
Turning this percentage into a percentile is a matter of rounding it.
const percentile = (Math.floor(pct/10)+1) * 10;
(PS: I use this function for buying/selling stocks when their current price is in a certain percentile of the daily transaction price rates.)