I am trying to get the upper and lower boundaries of a numeric value in an array.
const boundaries = [15, 30, 45, 60, 75, 90];
const age = 22;
For the above example, the outcome should be:
[15, 30]
If for example the value is a boundary, it would become the lower value in the outcome array. If it is the max boundary or above, it should become the max value.
Example outcomes:
15 => [15, 30]
22 => [15, 30]
30 => [30, 45]
90 => [90]
I tried mapping through the array and if the age is higher => return boundary. Then filter out the boundaries and calculate the indexes, but this doesn't feel like the correct way to accomplish this.
const boundaries = [15, 30, 45, 60, 75, 90];
const age = 22;
// get all lower values
const allLower = boundaries.map((b) => age > b ? b : null).filter(x => x);
const lower = allLower[allLower.length - 1]; // get lowest
const upper = boundaries[boundaries.indexOf(lower) + 1]; // get next
const result = [lower, upper]; // form result
console.log(result);
Is there a shorter / better / more reliable way to do this?
Why do you use the indices for this? What if the boundaries array is not sorted? Wouldn't it be easier to filter the lists to allLower and allUpper (containing the values below and above the threshold), and then use min and max on the resulting arrays?
Sample code:
const boundaries = [15, 30, 45, 60, 75, 90];
const age = 22;
const allLower = boundaries.filter(x => x < age);
const allUpper = boundaries.filter(x => x > age);
const lowerBound = Math.max(...allLower);
const upperBound = Math.min(...allUpper);
Looks like a good use case for reduce:
const boundaries = [15, 30, 45, 60, 75, 90];
for (let search of [1, 22, 30, 90, 100]) {
let [low, upr] = boundaries.reduce(([low, upr], x) =>
[
x <= search ? Math.max(low, x) : low,
x > search ? Math.min(upr, x) : upr,
],
[-Infinity, +Infinity]
)
console.log(low, '<=', search, '<', upr)
}
This doesn't require boundaries to be sorted. If they always are, you might consider binary search to locate the lower bound.
Looks like a plain for-loop might help you out ;)
function getBounds(age) {
for (let i = 0; i < boundaries.length; i++) {
if (boundaries[i] <= age && (boundaries[i + 1] ?? Infinity) > age) {
return boundaries.slice(i, i + 2);
}
}
}
You could check the previous value and next value and filter the array.
const
getLowerUpper = (array, pivot) => array
.filter((v, i, { [i - 1]: prev, [i + 1]: next }) =>
v <= pivot && next > pivot ||
prev <= pivot && v >= pivot ||
prev === undefined && next > pivot ||
prev < pivot && next === undefined
),
boundaries = [15, 30, 45, 60, 75, 90];
console.log(...getLowerUpper(boundaries, 22)); // between
console.log(...getLowerUpper(boundaries, 30)); // direct and next
console.log(...getLowerUpper(boundaries, 10)); // lowest
console.log(...getLowerUpper(boundaries, 15)); // direct and next
console.log(...getLowerUpper(boundaries, 90)); // highest
console.log(...getLowerUpper(boundaries, 100)); // highest
You can reduce the array, something like this:
const boundaries = [15, 30, 45, 60, 75, 90];
const getResult = (array, target) => {
if (target < array[0] || target > array[array.length - 1]) {
return [];
}
return array.reduce((a, c) => {
if (c <= target) {
a[0] = c;
} else if (c > target && (!a[1] || c < a[a.length - 1])) {
a[a.length] = c;
}
return a;
}, []);
}
console.log(getResult(boundaries, 22));
console.log(getResult(boundaries, 15));
console.log(getResult(boundaries, 30));
console.log(getResult(boundaries, 90));
console.log(getResult(boundaries, 14));
console.log(getResult(boundaries, 91));
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 have an array of numbers :
array = [20, 44, 55, 66, 24, 38, 500];
key = 25.5;
I want to basically compare every value in the array with the key and print the numbers starting with the closest number equal to that key.
ex: in the above instance, I want the o/p to look like:
newarray =[24,20,38, 44,66,500] // 24 is closest to 25.5, 20 is second closest to 25.5, 38 is thrid closest to 25.5, etc...
code:
var len = array.length;
array.forEach(function(i){
if(i === key) {
return i;
}
})
I'm not sure how to print these numbers based on the closest number in the array.any ideas?
Use Array.sort()
var array = [20, 44, 55, 66, 24, 38, 500];
var key = 25.5;
array.sort((a, b) => {
if (Math.abs(a - key) < Math.abs(b - key)) return -1;
else if (Math.abs(a - key) > Math.abs(b - key)) return 1;
return 0;
});
console.log(array);
Rather than using a for loop, you'll be better off using Javascript's Array.prototype.sort() function.
const array = [20, 44, 55, 66, 24, 38, 500];
const key = 25.5;
// Create a sorting helper
sortByClosestToKey = (a, b) => {
// Calculate how far away each number is from the key
const aDiff = Math.abs(a - key);
const bDiff = Math.abs(b - key);
return (aDiff < bDiff) ? -1 : (aDiff > bDiff) ? 1 : a < b;
}
const sorted = array.sort(sortByClosestToKey);
document.querySelector('#answer').innerText = sorted.toString();
<div id="answer"></div>
I think the example is the simplest.
I have this array : [10, 30, 55, 75, 94, 112] and the value 69.
I want to get: [55, 75, 94, 112]
So I want to filter out the smaller values but keep the closest.
an idea?
Something like this using filter.
var arr = [1, 3, 5, 7, 9, 11];
var value = 6;
function remove(arr, value) {
let copy = arr;
let newArr = arr.filter((arr, index) => !((arr < value) && (copy[index + 1] < value)))
console.log(newArr)
}
remove(arr, value) // [5, 7, 9, 11]
Just filter, and check if either this or the next value in the array is >= limit
const filter=(array, limit)=>array.filter((value,index)=>value>=limit||array[index+1]>limit);
console.log(filter([10, 30, 55, 75, 94, 112], 69));
Use Array.filter, Array.pop, Array.sort Array.concat
function f(val, array){
// array.sort( (a,b)=>a-b ); // if array isn't sorted, you must be use
return array.filter(e=>e>val).concat([array.filter(e=>e<val).pop()])
}
Given an input array arr and a value val:
Iterate over arr, splicing all elements greater than val into a separate array.
Append the maximum element left in arr to the new array.
arr = [1, 3, 5, 7, 9, 11];
val = 6;
new_arr = []; // Initialize new array
for (let i=0; i<arr.length; i++) { // Iterate over current array
if (arr[i]>val) { // If the current element is greater than the given value
new_arr.push(arr.splice(i, 1)[0]) // Splice it into the new array
i--; // Decrement i so as to not skip any elements
}
}
new_arr.unshift(Math.max(...arr)) // Add the closest value to the new array
console.log(new_arr);
Start by finding the closest delta for numbers that are under the limit. Then filter all numbers that are under the limit, and the their delta with the limit is not equal to the delta you've found in the previous step.
Note: this assumes that the numbers are unique, but they don't have to be sorted.
const fn = (arr, lim) => {
const closestSmallerDelta = arr.reduce((acc, n) =>
lim < n || lim - n > acc ? acc : lim - n
, Infinity)
return arr.filter(n => lim < n || lim - n === closestSmallerDelta)
}
console.log(fn([10, 30, 55, 75, 94, 112], 69));
// unsorted array
console.log(fn([112, 55, 75, 94, 10, 30], 69));
assuming the array is always sorted:
const deleteSmallerButLastOne = (array , refNum ) =>{
const finalArr = []
for(let [index, num] of array.entries()){
if(num < refNum && array[index + 1] >= refNum) finalArr.push(num)
if(num > refNum) finalArr.push(num)
}
return finalArr
}
I took this approach instead of modifying the original array just in case you need it for later.
Here's one possible approach (apparently assuming array is sorted). The idea is to find the very first item that is greater than or equal to lim; when you found it, there's no need to check the rest of an array (and that's what all the other answers do).
function processSorted(arr, lim) {
const i = arr.findIndex(el => el >= lim);
if (i === -1) // no elements greater than lim, return just the last one
return arr.slice(-1);
if (i === 0) // the first element is greater than lim, give back 'em all!
return arr;
return arr.slice(i - 1);
}
console.log(processSorted([10, 30, 55, 75, 94, 112], 69));
It's not sorted, it's possible to sort it anyway, or, if you really strive for n-only, go with one-iteration only approach:
function processUnsorted(arr, lim) {
const res = [];
let oneLess = -Infinity,
len = arr.length;
arr.forEach(el => {
if (el >= lim) res.push(el);
else oneLess = Math.max(oneLess, el);
});
if (oneLess !== -Infinity) res.unshift(oneLess);
return res;
}
console.log(processUnsorted([30, 55, 10, 94, 75, 112], 69));
The array "scores" tells the total points for each person involved in a contest. So for example:
User A: 100 points
User B: 90 points
User C: 90 points
User D: 80 points
User E: 75 points
User F: 60 points
According to above scores we will have this ranking:
User A: #1
User B: #2
User C: #2
User D: #3
User E: #4
User F: #5
This ranking method follows the Dense Ranking method.
Then we have a user named alice. If she gets 55 points, she will rank at position #6 (according to ranking above).
If she scores 90 points, she will rank at position #2. And so on.
I actually have an array containing different "sessions" for alice. So having for example:
[55, 90]
This means that first time will alice be ranked at position #6. While second time she will be ranked at position #2.
I coded this, and it works. However, this does not seem to be very efficient. For large datasets, with half million entries in the scores-array, it times out. This is the code:
const getPosition = (element, scores) => {
scores.push(element);
scores.sort(function (a,b) { return b-a; });
return scores.indexOf(element)+1;
}
function climbingLeaderboard(scores, alice) {
var uniqueSet = new Set(scores);
scores = [...uniqueSet];
var positions = [];
let aliceIndex = 0;
while(aliceIndex < alice.length){
positions.push(getPosition(alice[aliceIndex], scores));
aliceIndex++;
}
return positions;
}
function main() {
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
let result = climbingLeaderboard(scores, alice);
console.log(result.join("\n") + "\n");
}
I guess the "sort"-function and/or searching for the element in the array with indexOf is the problem. But I could not find a way to make these two operations more efficient.
Change your getPosition function to below and try. Just removed your sort function and doing the full array search with a condition.
const getPosition = (element, scores) => {
let length = scores.length;
let rank = 1;
for(let i=0; i<length; i++) {
if(scores[i] > element) {
rank++;
}
}
return rank;
}
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
console.log(getPosition(77, scores));
Here's another take... Maybe not the most efficient method, but believe it does the trick. This in effect is an implementation of Jonas Wilms' comment to the question.
This is somewhat of a brute force solution, as it walks the scores array for each of alice's scores. A more efficient means would involve sorting alice's scores from highest to lowest (but keeping track of the original order in order to organize the results in the proper order), and then walking both the scores array and alice's array simultaneously.
Note that the solution below runs the test case from the question, in addition to running a test case against an array of 1M scores which is populated with random scores in the range from 99,999 to 0.
function climbingLeaderboard(scores, alice) {
scores.sort( (a, b) => b - a );
aliceRank = [];
for ( let aliceScore of alice ) {
let scoreIndex = 0;
let rank = 0;
while ( scoreIndex < scores.length ) {
if ( scoreIndex === 0 || scores[ scoreIndex - 1 ] !== scores[ scoreIndex ] ) {
rank++;
}
if ( scores[ scoreIndex ] <= aliceScore ) {
aliceRank.push( rank++ );
break;
}
scoreIndex++;
}
if ( scoreIndex === scores.length ) {
aliceRank.push( ++rank );
}
}
return aliceRank;
}
function main() {
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
let result = climbingLeaderboard(scores, alice);
console.log(result);
console.log( 'Generating array of 1M scores' );
let scores2 = new Array( 1000000 );
for ( let i = 0; i < scores2.length; i++ ) {
scores2[ i ] = Math.floor( 100000 * Math.random() );
}
alice2 = [50000, 65000, 77000, 90000, 102000, -1];
let result2 = climbingLeaderboard(scores2, alice2);
console.log( `First of the 1M scores is ${scores2[0]} and last score is ${scores2[999999]}` );
console.log( result2 );
}
main();
Hope this helps.
this approach assumes that in case of equal scores Alice should be place first in the score bracket
meaning if she scores 90 then she will be ranked 2nd, behind 100 but above the rest of the 90s
function calculatePositions(scores, aliceScores) {
let positions = [];
const uniqueScoresSet = new Set([...scores]);
const uniqueScoresArray = Array.from(uniqueScoresSet);
aliceScores.forEach((aliceScore) => {
let position = uniqueScoresArray.findIndex((score) => aliceScore >= score);
position = position === -1 ? scores.length : position + 1;
positions.push(position);
});
return positions;
}
function main() {
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
let result = calculatePositions(scores, alice);
console.log(result.join("\n") + "\n");
}
this approach assumes that in case of equal scores Alice should be place last in the score bracket
meaning if she scores 90 then she will be ranked 4th, behind 100 and the two other 90s.
function calculatePositions(scores, aliceScores) {
let positions = [];
aliceScores.forEach((aliceScore) => {
let position = scores.findIndex((score) => aliceScore > score);
position = position === -1 ? scores.length : position + 1;
positions.push(position);
});
return positions;
}
function main() {
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
let result = calculatePositions(scores, alice);
console.log(result.join("\n") + "\n");
}
Merged your function into one. returning rank as an object in the format { mark : rank}
{
102: 1,
50: 50,
65: 35,
77: 23,
90: 10
}
function climbingLeaderboard(scores, alice) {
scores = [...new Set(scores)];
let length = scores.length;
const rank = alice.reduce((obj, key) => {
obj[key] = 1;
return obj;
}, {});
for (let i = 0; i < length; i++) {
alice.forEach((item) => {
if (scores[i] > item) {
rank[item]++;
}
});
}
return rank;
}
const scores = [];
for (i = 0; i < 500000; i++) {
scores[i] = Math.floor(Math.random() * 100);
}
const alice = [50, 65, 77, 90, 102];
let result = climbingLeaderboard(scores, alice);
console.log(result);
I have an array of this:
[34, 12, 56]
[100,125,19]
[30,50,69]
125 has been the highest value, it will return the index [1,1] format. Meaning 125 which is the highest value will return row 1 column 1
I was able to get the index in an array using this code
var a = [0, 21, 22, 7, 12];
var indexOfMaxValue = a.reduce((iMax, x, i, arr) => x > arr[iMax] ? i :
iMax, 0);
document.write("indexOfMaxValue = " + indexOfMaxValue); // prints
"indexOfMaxValue = 2"
Here's my approach. It flattens out all the arrays into more managable one, finds the max number and its index, and then calculates it's position using some math. Using a single array makes this calculation much easier.
const arr = [[34, 12, 56], [100,125,19], [30,50,69]];
const arr2 = [0, 21, 22, 7, 12];
function findHighest(arr) {
// Get the number of columns
const cols = arr.length;
// Flatten out the arrays
const tempArr = arr.flatMap(el => el);
// Get the max number from the array
const max = Math.max.apply(null, tempArr);
// Find its index
const indexMax = tempArr.findIndex(el => el === max);
// Find the remainder (modulo) when you divide the index
// by the number of columns
const mod = indexMax % cols;
// Return the final array output
return [Math.floor(indexMax / cols), mod];
}
console.log(findHighest(arr))
console.log(findHighest(arr2))
This will give the expected output but not sure is it good way to solve this:
var arr = [
[34, 12, 56],
[100, 125, 19],
[30, 50, 69]
];
var maxValue, maxIndex;
arr.forEach((arr1, i) => {
arr1.forEach((value, j) => {
if (i == 0 && j == 0) {
maxValue = value;
maxIndex = [i, j]
} else {
if (maxValue < value) {
maxValue = value;
maxIndex = [i, j];
}
}
});
});
console.log("Max Number Index", maxIndex);
If you mean 2d solution, try this. Should work for dynamic length arrays
This should be extendable with a new forEach for a new dimension
[100,125,19],
[30,50,69]];
maxIndex = [-1, -1];
maxElem = 0;
input.forEach(function(arr, row) {
console.error(row);
arr.forEach(function(e, col) {
if( maxElem <= e ) {
maxElem = e;
maxIndex = [row, col];
}
})
})
console.log(maxIndex)