Sorting an array while iterating by two? - javascript

I'm optimizing an algorithm that generates a texture for my game.. long story
Anyway, while profiling I found I could shave off an extra 10% off the runtime if I could eliminate converting vertex data from one format to another. Essentially, all vertex data comes in a single array like so: [x0, y0, x1, y1, ... xn, yn]
I need to sort the vertices first by X and then by Y. So far I've achieved this by converting the vertex data into an array of arrays like so: [[x0, y0], [x1, y1], ... [xn, yn]]
And then using Array.sort((a, b) => a[0] > b[0] ? -1 : a[0] < b[0] ? 1 : 0) and switching the index to sort by Y later and then convert back into the original format.
All this converting takes unnecessary time. Is it possible to somehow make Array.sort() iterate by two instead or some equivalent?
Other than that, I've also thought about implementing my own sort function. What are my odds of competing with the native Array.sort() performance wise?

You could take a sorting of the indices and build a new array based in the indices.
var data = [0, 1, 3, 2, 1, 5, 1, 4, 2, 3, 4, 2],
indices = [],
result = [];
for (let i = 0; i < data.length; i += 2) indices.push(i);
indices.sort((a, b) => data[a] - data[b]);
for (let i of indices) result.push(data[i], data[i + 1]);
console.log(result);

Just for fun though because I don't believe this is gonna be faster that your existing solution - you can use proxy and iterators to make Array-like object which returns every second member:
var input = [500,400,300,200,100];
var proxy = new Proxy(input, {
get(target, name) {
if (name === 'length') {
return Math.ceil(target.length / 2);
}
if (name === Symbol.iterator) {
return function*() {
for (let i = 0; i < target.length; i += 2)
yield target[i];
}
}
if (Array.prototype.hasOwnProperty(name)) {
return target[name];
}
return target[name * 2];
},
set(target, name, value) {
target[name * 2] = value;
return true;
}
});
console.log([...proxy]); // [500, 300, 100]
proxy.sort((x, y) => x - y);
console.log(input); // [100, 400, 300, 200, 500]
console.log([...proxy]); // [100, 300, 500]

Related

Javascript Sorting Matrix Diagonally

There is an algorithm question asking to sort a matrix diagonally. I'm not finding a solution for this quesiton.
const matrix =
[
[1, 3, 9, 4],
[9, 5, 7, 7],
[3, 6, 9, 7],
[1, 2, 2, 2]
]
Should output:
[
[1, 9, 9, 7],
[3, 5, 6, 9],
[3, 4, 7, 7],
[1, 2, 2, 2]
]
My code so far:
function diagonalSort(matrix) {
const cols = matrix[0].length;
const rows = matrix.length;
const maxLength = Math.max(cols, rows);
let temp;
for (let k = 0; k <= 2 * (maxLength - 1); k++) {
temp = [];
for (let y = cols - 1; y >= 0; y--) {
let x = k - y;
// console.log(k, y, x);
if (x >= 0 && x < rows) {
temp.push(matrix[y][x]);
}
}
if (temp.length > 0) {
console.log(temp);
}
}
}
Which outputs this:
[ 1 ]
[ 3, 9 ]
[ 3, 5, 9 ]
[ 1, 4, 6, 7 ]
[ 2, 7, 9 ]
[ 2, 7 ]
[ 2 ]
But how to put it back together...?
This approach is also a gentle introduction to functional lenses in Javascript:
const range = (lo, hi) => [...Array (hi - lo)] .map ((_, i) => i + lo)
const diagonals = (rows, cols) =>
range (0, rows + cols - 1) .map (
d => range (Math.max (d - rows + 1, 0), Math.min (d, cols - 1) + 1)
.map (x => [d - x, x])
)
const coordLens = (coords) => ({
get: (m) => coords .map (([x, y]) => m [x] [y]),
set: (arr, m) => coords .reduce (
(m, [x, y], i) => {m [x] [y] = arr [i]; return m},
m .map (r => [... r]) // cheap clone
),
})
const diagonalLenses = (rows, cols) =>
diagonals (rows, cols) .map (coordLens)
const sortMatrix = (comparator) => (m) =>
diagonalLenses (m.length, m[0].length)
.reduce ((m, lens) => lens.set (lens .get (m) .sort (comparator), m), m)
const numericSorter = (a, b) => a - b
console .log (
sortMatrix (numericSorter) ([
[1, 3, 9, 4],
[9, 5, 7, 7],
[3, 6, 9, 7],
[1, 2, 2, 2]
])
.map (r => r .join (' ')) .join ('\n')
)
After the minor helper function range (which works like range (3. 12) ==> [3, 4 , 5, 6, 7, 8, 9, 10, 11]) (including the start, excluding the stop) we have diagonals, which calculates the diagonals as arrays of [x, y] pairs, given the height and width of a matrix.
The most important bit here is coordLens. A lens is a glorified getter/setter pair for some facet of a data structure. They turn out to be quite useful. Many of the standard introductions to them point to a single property or array index, or perhaps a nested path inside an object. These are slightly more interesting. coordLens takes an array of [x, y] coordinates and returns a lens that we can use to extract the values at those coordinates from a given matrix, or to set the values in (a copy of) a matrix from elements supplied as an array.
On top of these two we build diagonalLenses, which takes the number of rows and columns and creates an array of coordinate lenses, one for each diagonal.
We build the main function sortMatrix on these functions. We accept a standard comparator and return a function that takes a matrix and returns another of the same shape, with each of the southwest -> northeast diagonals sorted. We create an array of lenses using diagonalLenses. Then for each lens, we get the values, sort them and use the set method to update our cloned matrix with the sorted values.
Note that there is little error-checking in any of this. The pieces will work together in the manner described here, but coordLens should, if used more generically, check that the array values it's trying to receive or to set are within bounds.
Addendum - the use of lenses
I originally wanted to explain the use of lenses in greater depth. But it was bedtime and I left it with just a brief description. There are many tutorials available to cover the basics. But very, very briefly, lenses are structures that focus on one aspect of a data structure. You create one by supplying a getter and a setter for your aspect, and then you can use a consistent interface to read, write, and modify data. (In the write/modify cases, this would involve creating a copy of the object. There's no mutation involved; we're not barbarians!)
What I am taking advantage of here is that the lenses don't have to be as simple as tends to be shown in those tutorials. Instead of pointing to a single property of an object, they can do more. One of my favorite examples is that a weather app which -- as is only sensible -- reports temperatures in Celsius, but which has users who live in a benighted country like by own, who only know Fahrenheit.
const fahrenLens = lens(
obj => obj.temp * 9 / 5 + 32,
(degF, obj) => ({...obj, temp: (degF - 32) * 5 / 9})
)
And it can be used like this:
const nyc= {name: 'New York', lat: 40.730610, lng: -73.935242, temp: 25}
view (fahrenLens, nyc) //=> 77
set (fahrenLens, 86, nyc)
//=> {name: "New York", lat: 40.73061, lng: -73.935242, temp: 30}
over (fahrenLens, t => t - 27, nyc)
//=> {name: "New York", lat: 40.73061, lng: -73.935242, temp: 10}
This uses three standard function for working with lenses, which, in our simple lens implementation might look like this:
const view = (lens, o) => lens .get (o)
const set = (lens, v, o) => lens .set (v, o)
const over = (lens, f, o) => lens .set (f (lens .get (o)), o)
In the answer above we use lenses to track multiple coordinates in an m x n grid, using this code:
const coordLens = (coords) => ({
get: (m) => coords .map (([x, y]) => m [x] [y]),
set: (arr, m) => coords .reduce (
(m, [x, y], i) => {m [x] [y] = arr [i]; return m},
m .map (r => [... r]) // cheap clone
),
})
Given a set of coordinates, when we view a matrix through this coordinate lens, we get back an array of the values at those coordinates. When we set the matrix with an array of values through this lens, we fold the array of values into a new matrix by setting each coordinate to the corresponding array value, starting with a copy of the matrix.
Using diagonalLenses, we create an array of coordinate lenses, one for each diagonal in the grid. (Here considering only on southwest -> northeast diagonals.) If we were to use our over function we could simplify further to
const sortMatrix = (comparator) => (m) =>
diagonalLenses (m)
.reduce ((m, lens) => over (lens, xs => xs.sort (comparator), m), m)
And my own preference would be to introduce a sort function like
const sort = (comparator) => (xs) => [...xs] .sort (comparator)
which would allow us to simplify that further to
const sortMatrix = (comparator) => (m) =>
diagonalLenses (m)
.reduce ((m, lens) => over (lens, sort (comparator), m), m)
Now a good question is how much this abstraction buys us. Using the same range and diagonals functions, we could write sortMatrix like this:
const sortMatrix = (comparator) => (m) =>
diagonals (m)
.reduce (
(m, diagonal) => {
const vals = diagonal .map (([y, x]) => m [y] [x]) .sort (comparator)
return diagonal .reduce ((m, [y, x], i) => {m [y] [x] = vals [i]; return m}, m)
},
m .map (r => [...r])
)
which involves fewer lines of code than the combination of coordLens, diagLenses, and even our shortest earlier version of sortMatrix.
One argument is that it adds nothing, that this version is fine. If you have to do nothing else with those diagonals, or with other collections of coordinates, this might be true. But these lenses can be very convenient. If we wanted to total up each diagonal for some reason, it's a one-liner on top of the response from diagonalLenses:
const m = [
[1, 3, 9, 4],
[9, 5, 7, 7],
[3, 6, 9, 7],
[1, 2, 2, 2]
]
const myDiagonals = diagonalLenses (m .length, m [0] .length)
const diagonalSums = myDiagonals .map (d => sum (view2 (d, m)))
diagonalSums // => [1, 12, 17, 18, 18, 9, 2]
for an obvious sum function over an array.
And there are many more things we could do with them.
One thing that I should make clear is that these lenses we create are not specific to a particular matrix. Our list of diagonal lenses for one m x n matrix are exactly the same for another m x n one.
And the same is true for the underlying coordinate lenses. They have to do only with the shape of the matrix. We can easily imagine a sudoku solver which has a lens for each row, column, and box, which we then test for a correct solution with something like
const gridCompleted = (grid) =>
lenses .every (lens => completeGroup (view (lens, grid)))
Here we go:
function* enumerateDiagonals(xLength, yLength) {
for (let y = 0; y < yLength; y++) {
yield {
x: 0,
y: y
};
}
for (let x = 1; x < xLength; x++) {
yield {
x: x,
y: yLength - 1
};
}
}
function sliceMatrix(matrix, diagonal) {
const slice = [];
for (let y = diagonal.y, x = diagonal.x; y >=0 && x<matrix[y].length ; y--, x++) {
slice.push(matrix[y][x])
}
return slice
}
function emplaceIntoMatrix(slice, matrix, diagonal) {
for (let i=0; i < slice.length; i++) {
matrix[diagonal.y - i][diagonal.x + i] = slice[i];
}
}
const matrix =
[
[1, 3, 9, 4],
[9, 5, 7, 7],
[3, 6, 9, 7],
[1, 2, 2, 2]
];
const Ylength = matrix.length
const Xlength = matrix[0].length
console.log(Ylength)
console.log(Xlength)
const sortedMatrix = [ ];
for(let i=0; i<Ylength; i++) {
sortedMatrix[i] = new Array(Xlength);
}
for (const diagonal of enumerateDiagonals(Xlength, Ylength)) {
var slice = sliceMatrix(matrix, diagonal);
slice.sort();
emplaceIntoMatrix(slice, sortedMatrix, diagonal);
}
console.log(sortedMatrix);
const cols = matrix[0].length;
const rows = matrix.length;
const maxLength = Math.max(cols, rows);
let tempArr = [];
let result = [];
for (let k = 0; k <= 2 * (maxLength - 1); k++) {
let temp = [];
for (let y = cols - 1; y >= 0; y--) {
let x = k - y;
// console.log(k, y, x);
if (x >= 0 && x < rows) {
temp.push(matrix[y][x]);
}
}
if (temp.length > 0) {
// console.log(temp.sort());
tempArr.push(temp.sort());
}
}
for (let k = 0; k <= maxLength - 1; k++) {
let tempResult = [];
let arr = tempArr.filter((a) => a.length > 0);
// console.log(arr);
for (let i = 0; i <= maxLength - 1; i++) {
const length = arr[i].length;
tempResult.push(arr[i][length - 1]);
tempArr[tempArr.indexOf(arr[i])].pop();
}
result.push(tempResult);
}
return result;

What is the time complexity of Javascript Array.reduce() and Array.find()?

I am trying to return an array of indexes of values that add up to a given target. I am trying to solve it the fastest way I can!
Examples:
sumOfTwo([1, 2, 4, 4], 8) // => [2, 3]
sumOfTwo([1, 2, 3, 9], 8) // => []
So first I tried a simple brute-force. (Time complexity: O(n^2) )
function sumOfTwo(arr, target) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] + arr[j] === target) {
return [i, j];
}
}
}
return [];
}
Then I tried: (Time complexity: sorting O(n log n) + for loop O(n))
function sumOfTwo(arr, target) {
const sortedArr = arr.sort();
let idxFromBack = arr.length - 1;
for (let [idx, val] of sortedArr.entries()) {
if (val + arr[idxFromBack] > target) {
idxFromBack--;
}
if (val + arr[idxFromBack] === target) {
return [idx, idxFromBack];
}
}
return [];
}
Then I came with this solution that I don't even know the time complexity.
function sumOfTwo(arr, target) {
const complements = [];
for (let [idx, val] of arr.entries()) {
if (complements.reduce((acc, v) => (acc || v.value === val), false)) {
return [complements.find(v => v.value === target - val).index, idx];
}
complements.push({index: idx, value: target - val});
}
return [];
}
I know that I am using a for-loop but I don't know the complexity of the build-in high order functions .reduce() and .find(). I tried a couple of searches but I couldn't find anything.
If anyone can help me would be great! Please include Big-O notation if possible.
Repl.it: https://repl.it/#abranhe/sumOfTwo
Please also include the time complexity of the last solution.
The minimum time complexity of .reduce is O(n), because it must iterate through all elements once (assuming an error isn't thrown), but it can be unbounded (since you can write any code you want inside the callback).
For your
// Loop, O(n), n = length of arr:
for (let [idx, val] of arr.entries()) {
// .reduce, O(n), n = length of complements:
if (complements.reduce((acc, v) => (acc || v.value === val), false)) {
// If test succeeds, .find, O(n), n = length of complements:
return [complements.find(v => v.value === target - val).index, idx];
}
complements.push({index: idx, value: target - val});
}
the time complexity is, worst case, O(n^2). The reduce runs in O(n) time, and you run a reduce for every entry in arr, making it O(n^2).
(The .find is also an O(n) operation, but O(n) + O(n) = O(n))
Your code that sorts the array beforehand has the right idea for decreasing complexity, but it has a couple flaws.
First, you should sort numerically ((a, b) => a - b)); .sort() with no arguments will sort lexiographically (eg, [1, 11, 2] is not desirable).
Second, just decrementing idxFromBack isn't enough: for example, sumOfTwo([1, 3, 8, 9, 9], 9) will not see that 1 and 8 are a match. Perhaps the best strategy here would be to oscillate with while instead: from a idxFromBack, iterate backwards until a match is found or the sum is too small, and also iterate forwards until a match is found or the sum is too large.
You can also improve the performance of this code by sorting not with .sort((a, b) => a - b), which has complexity of O(n log n), but with radix sort or counting sort instead (both of which have complexity of O(n + k), where k is a constant). The optimal algorithm will depend on the general shape and variance of the input.
An even better, altogether different O(n) strategy would be to use a Map or object. When iterating over the array, put the value which would result in a match for the current item into a key of the object (where the value is the current index), and just look to see if the current value being iterated over exists in the object initially:
const sumOfTwo = (arr, target) => {
const obj = {};
for (const [i, num] of arr.entries()) {
if (obj.hasOwnProperty(String(num))) {
return [obj[num], i];
}
const matchForThis = target - num;
obj[matchForThis] = i;
}
return [];
};
console.log(
sumOfTwo([1, 2, 4, 4], 8), // => [2, 3]
sumOfTwo([1, 2, 8, 9], 9), // 1 + 8 = 9; [0, 2]
sumOfTwo([1, 2, 3, 9], 8) // => []
);
As a supplementary answer, here is the algorithm of the find method in the language spec:
When the find method is called, the following steps are taken:
Let O be ? ToObject(this value).
Let len be ? LengthOfArrayLike(O).
If IsCallable(predicate) is false, throw a TypeError exception.
Let k be 0.
Repeat, while k < len,
a. Let Pk be ! ToString(𝔽(k)).
b. Let kValue be ? Get(O, Pk).
c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
d. If testResult is true, return kValue.
e. Set k to k + 1.
Return undefined.
Note the "repeat, while k < len" in step 5. Since time complexity in general measures the worst case scenario (aka the upper bound), we can assume that the searched element is not present in the collection.
The number of iterations made during step 5 then is equal to len which directly depends on the number of elements in the collection. And what time complexity has a direct correlation to the number of elements? Exactly, the linear O(n).
For a visual-ish demonstration, run the following snippet. Apart from some stray dots, the improvized graph should show a linear progression (takes a little while to display in Stack snippets, but you can watch it live in the devtools console):
const iter = 1e7;
const incr = 2e5;
const test = new Array(iter).fill(0);
const steps = Math.ceil(iter / incr);
for (let i = 0; i < steps; i += 1) {
const sub = test.slice(0, i * incr + incr);
const s = Date.now();
const find = sub.find((v) => v === 1);
const e = Date.now();
const d = e - s;
console.log("\u200A".repeat(Math.floor(d/3))+"*");
}

Creating multidimensional arrays & matrices in Javascript

Trying to create a function mCreate() that given a set a numbers returns a multidimensional array (matrix):
mCreate(2, 2, 2)
// [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
When this functions handles just 2 levels of depth ie: mCreate(2, 2) //[[0, 0], [0, 0]] I know to do 2 levels, you can use 2 nested for loops but the problem I'm having is how to handle an n'th number of arguments.
Would this problem be better approached with recursion, otherwise how can I dynamically determine the number of nested for loops I'm going to need given the number of arguments?
ps: the most performant way would be great but not essential
RE-EDIT - After using Benchmark.js to check perf the results were as follows:
BenLesh x 82,043 ops/sec ±2.56% (83 runs sampled)
Phil-P x 205,852 ops/sec ±2.01% (81 runs sampled)
Brian x 252,508 ops/sec ±1.17% (89 runs sampled)
Rick-H x 287,988 ops/sec ±1.25% (82 runs sampled)
Rodney-R x 97,930 ops/sec ±1.67% (81 runs sampled)
Fastest is Rick-H
#briancavalier also came up with a good solution JSbin:
const mCreate = (...sizes) => (initialValue) => _mCreate(sizes, initialValue, sizes.length-1, 0)
const _mCreate = (sizes, initialValue, len, index) =>
Array.from({ length: sizes[index] }, () =>
index === len ? initialValue : _mCreate(sizes, initialValue, len, index+1))
mCreate(2, 2, 2)(0)
One simple recursive answer is this (in ES2015):
const mCreate = (...sizes) =>
Array.from({ length: sizes[0] }, () =>
sizes.length === 1 ? 0 : mCreate(...sizes.slice(1)));
JS Bin here
EDIT: I think I'd add the initializer in with a higher order function though:
const mCreate = (...sizes) => (initialValue) =>
Array.from({ length: sizes[0] }, () =>
sizes.length === 1 ? initialValue : mCreate(...sizes.slice(1))(initialValue));
Which could be used like:
mCreate(2, 2, 2)('hi');
// [[["hi", "hi"], ["hi", "hi"]], [["hi", "hi"], ["hi", "hi"]]]
JSBin of that
Here's a non-recursive solution:
function mCreate() {
var result = 0, i;
for(i = arguments.length - 1; i >= 0 ; i--) {
result = new Array(arguments[i]).fill(result);
}
return JSON.parse(JSON.stringify(result));
}
The JSON functions are used to mimic a deep clone, but that causes the function to be non-performant.
function mCreate() {
var result = 0, i;
for(i = arguments.length - 1; i >= 0 ; i--) {
result = new Array(arguments[i]).fill(result);
}
return JSON.parse(JSON.stringify(result));
}
console.log(JSON.stringify(mCreate(2, 2, 2)));
console.log(JSON.stringify(mCreate(1, 2, 3, 4)));
console.log(JSON.stringify(mCreate(5)));
console.log(JSON.stringify(mCreate(1, 5)));
console.log(JSON.stringify(mCreate(5, 1)));
var m = mCreate(1, 2, 3, 4);
m[0][1][1][3] = 4;
console.log(JSON.stringify(m));
Recursive algorithms may be easier to reason about, but generally they're not required. In this particular case the iterative approach is simple enough.
Your problem consists of two parts:
creating an array with variable number of 0-value elements
creating variable number of arrays of previously created arrays
Here's an implementation of what I think you're trying to create:
function nested() {
// handle the deepest level first, because we need to generate the zeros
var result = [];
for (var zeros = arguments[arguments.length - 1]; zeros > 0; zeros--) {
result.push(0);
}
// for every argument, walking backwards, we clone the
// previous result as often as requested by that argument
for (var i = arguments.length - 2; i >= 0; i--) {
var _clone = [];
for (var clones = arguments[i]; clones > 0; clones--) {
// result.slice() returns a shallow copy
_clone.push(result.slice(0));
}
result = _clone;
}
if (arguments.length > 2) {
// the shallowly copying the array works fine for 2 dimensions,
// but for higher dimensions, we need to compensate
return JSON.parse(JSON.stringify(result));
}
return result;
}
Since writing the algorithm is only half of the solution, here's the test to verify our function actually performs the way we want it to. We'd typically use one of the gazillion test runners (e.g. mocha or AVA). But since I don't know your setup (if any), we'll just do this manually:
var tests = [
{
// the arguments we want to pass to the function.
// translates to nested(2, 2)
input: [2, 2],
// the result we expect the function to return for
// the given input
output: [
[0, 0],
[0, 0]
]
},
{
input: [2, 3],
output: [
[0, 0, 0],
[0, 0, 0]
]
},
{
input: [3, 2],
output: [
[0, 0],
[0, 0],
[0, 0]
]
},
{
input: [3, 2, 1],
output: [
[
[0], [0]
],
[
[0], [0]
],
[
[0], [0]
]
]
},
];
tests.forEach(function(test) {
// execute the function with the input array as arguments
var result = nested.apply(null, test.input);
// verify the result is correct
var matches = JSON.stringify(result) === JSON.stringify(test.output);
if (!matches) {
console.error('failed input', test.input);
console.log('got', result, 'but expected', rest.output);
} else {
console.info('passed', test.input);
}
});
It's up to you to define and handle edge-cases, like nested(3, 0), nested(0, 4), nested(3, -1) or nested(-1, 2).
As suggested by #Pranav, you should use arguments object.
Recursion + arguments object
function mCreate() {
var args = arguments;
var result = [];
if (args.length > 1) {
for (var i = 1; i < args.length; i++) {
var new_args = Array.prototype.slice.call(args, 1);
result.push(mCreate.apply(this, new_args));
}
} else {
for (var i = 0; i < args[0]; i++) {
result.push(0)
}
}
return result;
}
function print(obj) {
document.write("<pre>" + JSON.stringify(obj, 0, 4) + "</pre>");
}
print(mCreate(2, 2, 2, 2))
The gist is to pass in the result of a create as the second argument of create except for the last (or the first depending on how you look at it) instance:
function create(n, v) {
let arr = Array(n || 0);
if (v !== undefined) arr.fill(v);
return arr;
}
create(2, create(2, 0)); // [[0,0],[0,0]]
create(2, create(2, create(2, 0))); // [[[0,0],[0,0]],[[0,0],[0,0]]]
DEMO
Using a loop we can build up array dimensions:
function loop(d, l) {
var out = create(d, 0);
for (var i = 0; i < l - 1; i++) {
out = create(d, out);
}
return out;
}
loop(2,2) // [[0,0],[0,0]]
loop(2,3) // [[[0,0],[0,0]],[[0,0],[0,0]]]
loop(1,3) // [[[0]]]
DEMO

selecting the two biggest values in an array

I'm trying to write a function that finds the two biggest value inside an array of numbers and stores them inside a new array. I'm unable to first remove the first biggest number from the original array and then find the second biggest.
here is my code:
function choseBig (myArray) {
//var myArray = str.split(" ");
var result = [];
var firstBig;
var secondBig;
// select the biggest value
firstBig = Math.max.apply(Math, myArray);
// find its index
var index = myArray.indexOf(firstBig);
// remove the biggest value from the original array
var myArray_2 = myArray.slice((index -1), 1);
// choose the second biggest value
secondBig = Math.max.apply(Math, myArray_2);
// push the results into a new array
result.push(firstBig, secondBig);
return result;
}
console.log(choseBig ([1,2,3,4,5,9]));
At first glance, I'd suggest:
function choseBig(myArray) {
return myArray.sort((a, b) => b - a).slice(0, 2);
}
console.log(choseBig([1, 2, 3, 4, 5, 9]));
To extend the above a little, for example offering the user the option to specify whether the returned values should be the highest numbers, or the lowest numbers, and how many they wish returned, I'd offer the following:
function choseBig(myArray, opts) {
// 'max': Boolean,
// true: returns the highest numbers,
// false: returns the lowest numbers
// 'howMany': Number,
// specifies how many numbers to return:
var settings = {
'max': true,
'howMany': 2
};
// ensuring we have an Object, otherwise
// Object.keys( opts ) returns an error:
opts = opts || {};
// retrieving the keys of the opts Object, and
// uses Array.prototype.forEach() to iterate over
// those keys; 'o' (in the anonymous function) is
// the array element (the property-name/key) from
// the array Object keys over which we're iterating:
Object.keys(opts).forEach(function(o) {
// updating the settings Object to the new values
// (if any is specified) to those set in the user-
// supplied opts Object:
settings[o] = opts[o];
});
// here we first sort the Array, using a numeric sort;
// using ES2015 Arrow functions. 'a' and 'b' are supplied
// by Array.prototype.sort() and refer to the current ('a')
// and next ('b') array-elements. If b - a is less than zero
// b is moved to a lower index; if a - b is less than zero
// a is moved to a lower index.
// Here we use a ternary operator based on whether settings.max
// is true; if it is true we sort to move the larger number to
// the lower index; otherwise we sort to move the smaller number
// to the lower index.
// Then we slice the resulting array to return the numbers from
// the 0 index (the first number) to the settings.howMany number
// (the required length of the array).
// this is then returned to the calling context.
return myArray.sort((a, b) => settings.max === true ? b - a : a - b).slice(0, settings.howMany);
}
console.log(choseBig([1, 2, 3, 4, 5, 9], {
// here we specify to select the largest numbers:
'max': true,
// we specify we want the 'top' three numbers:
'howMany': 3
}));
function choseBig(myArray, opts) {
var settings = {
'max': true,
'howMany': 2
};
opts = opts || {};
Object.keys(opts).forEach(function(o) {
settings[o] = opts[o];
});
return myArray.sort((a, b) => settings.max === true ? b - a : a - b).slice(0, settings.howMany);
}
console.log(choseBig([1, 2, 3, 4, 5, 9], {
'max': true,
'howMany': 3
}));
JS Fiddle demo.
References:
Array.prototype.forEach.
Array.prototype.slice().
Array.prototype.sort().
Conditional (Ternary) Operator: statement ? ifTrue : ifFalse
How do you use the ? : (conditional) operator in JavaScript?.
Object.keys().
Why not just sort it (descending order) and take the first two entries
biggest = myArray.sort(function(a,b){return b - a}).slice(0,2);
The answers above are probably better and more compact, but in case you don't want to use sort() this is another option
function choseBig (myArray) {
var result = [], firstBig, secondBig;
// select the biggest value
firstBig = Math.max.apply(Math, myArray);
// find its index
var index = myArray.indexOf(firstBig);
// remove the biggest value from the original array
myArray.splice((index), 1);
secondBig = Math.max.apply(Math, myArray);
// push the results into a new array
result.push(firstBig, secondBig);
return result;
}
console.log(choseBig ([1,2,3,4,5,9]));
A linear solution with Array#reduce without sorting.
var array = [1, 2, 3, 4, 5, 9],
biggest = array.reduce(function (r, a) {
if (a > r[1]) {
return [r[1], a];
}
if (a > r[0]) {
return [a, r[1]];
}
return r;
}, [-Number.MAX_VALUE, -Number.MAX_VALUE]);
document.write('<pre>' + JSON.stringify(biggest, 0, 4) + '</pre>');
Edit: more than one biggest value
var array = [1, 2, 3, 4, 5, 9, 9],
biggest = array.reduce(function (r, a) {
if (a > r[1]) {
return [r[1], a];
}
if (a > r[0]) {
return [a, r[1]];
}
return r;
}, [-Number.MAX_VALUE, -Number.MAX_VALUE]);
document.write('<pre>' + JSON.stringify(biggest, 0, 4) + '</pre>');
With Math#max and Array#splice
var first = Math.max(...arr)
arr.splice(arr.indexOf(first))
var second = Math.max(...arr)
and with ES6 spread operator
I like the linear solution of Nina Scholz. And here's another version.
function chooseBig (myArray) {
var a = myArray[0], b = myArray[0];
for(i = 1; i < myArray.length; i++) {
if (myArray[i] === a) {
continue;
} else if (myArray[i] > a) {
b = a;
a = myArray[i];
} else if (myArray[i] > b || a === b) {
b= myArray[i];
}
}
return [a, b];
}
If you want to retrieve the largest two values from a numeric array in a non-destructive fashion (e.g. not changing the original array) and you want to make it extensible so you can ask for the N largest and have them returned in order, you can do this:
function getLargestN(array, n) {
return array.slice(0).sort(function(a, b) {return b - a;}).slice(0, n);
}
And, here's a working snippet with some test data:
function getLargestN(array, n) {
return array.slice(0).sort(function(a, b) {return b - a;}).slice(0, n);
}
// run test data
var testData = [
[5,1,2,3,4,9], 2,
[1,101,22,202,33,303,44,404], 4,
[9,8,7,6,5], 2
];
for (var i = 0; i < testData.length; i+=2) {
if (i !== 0) {
log('<hr style="width: 50%; margin-left: 0;">');
}
log("input: ", testData[i], " :", testData[i+1]);
log("output: ", getLargestN(testData[i], testData[i+1]));
}
<script src="http://files.the-friend-family.com/log.js"></script>
[1,101,22,202].sort(function(a, b){return b-a})[0]
[1,101,22,202].sort(function(a, b){return b-a})[1]

Find the number in an array that is closest to a given number

I have an Array of integers in javascript, [5,10,15,20,25,30,35]
when given a number x, how can I find the element in the array that is closest to that number?
If the number is over a value, but less than halfway to the next number, I would choose the smaller value, if it were over halfway to the next number, I would choose the higher number.
For example 7 would return 5, but 8 would return 10. How can I accomplish this? Any help or tips would be appreciated. I have searched and cannot find a solution. I'm sure this is sort of common.
Probably the easiest thing to do is sort based on distance from the reference value x, and then take the first item.
The built-in Array.prototype.sort() can take a comparison function which will be called for pairs of values from the array. Then the key is simply to pass in a comparison function which compares the two values based on their distance from the reference value x.
let x = 8;
let array = [5, 10, 15, 20, 25, 30, 35];
let closest = array.sort( (a, b) => Math.abs(x - a) - Math.abs(x - b) )[0];
See this simple demo.
function getClosest(array, target) {
var tuples = _.map(array, function(val) {
return [val, Math.abs(val - target)];
});
return _.reduce(tuples, function(memo, val) {
return (memo[1] < val[1]) ? memo : val;
}, [-1, 999])[0];
}
If using a functional approach is applicable then you can map the set to tuples of (value, distance) then reduce that set of tuples to the tuple with the smallest distance. We return the value in that tuple.
To explain the useage of _.map. You map all the values in your array to new values and the function will return the array of new values. In this case an array of tuples.
To explain the useage of _.reduce. You reduce the array to a single value. You pass in an array and a memo. The memo is your "running counter" as you move through the array. In this case we check whether the current tuple is closer then the memo and if so make it the memo. We then return the memo at the end.
The code snippet above relies on underscore.js to remove the nitty gritty of functional style javascript
Your example list is sorted. If this is always the case, then binary search for your number. If you don't find the exact number, make the binary search end off by checking the two numbers around where the number would be and return the closest. Be careful with edge cases where all numbers are greater or are all smaller than the target number
If the list isn't always sorted, then go through the list keeping track of the largest number <= the target number and the smallest number >= the target number. Return the one that's closest to the target.
In either solution, you'll need to decide which side to favour if for example you're searching for 2 in [1, 3].
Create a temporary array of the same size as your original array, and populate it with the differences between your x and the array element.
For example, let the temporary array be temp[], and your original array be a[]:
temp[i]=Math.abs(x-a[i]);
Then, return the index of the minimum value in temp[] to the user.
Assuming the array is sorted, step through each adjacent pair of integers in the array. For each pair (say "5 and 10" or "20 and 25"), test if x is in between them, and if so, return whichever one is closer to x (with a bias towards the lower one).
You would also need a special case for when x is less than the first number (return the first number) or greater than the last number (return the last number).
If the array is not sorted, sort it first.
I created my own function since i could not find any that meets my requeriments.
function closest_number(quantities, number, closest_factor)
{
if (closest_factor == 'ceil')
{
quantities.sort(function(a, b)
{
return a - b
}
);
for (var i = 0; i < quantities.length; i++)
{
if (quantities[i] >= number)
{
return quantities[i];
}
last_value = quantities[i];
}
return last_value;
}
else if (closest_factor == 'floor')
{
quantities.sort(function(a, b)
{
return a - b
}
);
min_value = quantities[0];
for (var i = 0; i < quantities.length; i++)
{
if (number == quantities[i])
{
return number;
}
else if (quantities[i] < number)
{
min_value = quantities[i];
}
else if(quantities[i] > number)
{
return min_value;
}
}
return min_value;
}
else
{
return false;
}
};
Since Array.reduce is a reality for so long (even IE9 supports it), the problem is easily solvable with it. This way, no need to sort the array first (no array mutation at all):
var numbers = [20, 25, 30, 35, 5, 10, 15], x = 7;
var output = numbers.reduce(function (prev, curr) {
return Math.abs(curr - x) < Math.abs(prev - x) ? curr : prev
});
console.log(output);
You can go further and solve it with only one line of ES6 (ECMAScript 2015) syntax, by using an arrow function (but with no IE support in this case):
const numbers = [20, 25, 30, 35, 5, 10, 15], x = 7;
const output = numbers.reduce((prev, curr) => Math.abs(curr - x) < Math.abs(prev - x) ? curr : prev);
console.log(output);
Of course, for flexibility and reusability, it's easy to make it as a function:
const closest = (array, goal) => array.reduce((prev, curr) => Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
console.log(closest([20, 25, 30, 35, 5, 10, 15], 7));
console.log(closest([20, 25, 30, 35, 5, 10, 15], 8));
console.log(closest([1, 5, 7], -5));
console.log(closest([1, 5, 7], 4));
console.log(closest([1, 5, 7], 20));

Categories