Javascript Array Filter to Execute Functions - javascript

I have an array with two values, (1 or 2) and (a, b, c or d). Depending on the two values a certain math function will execute. The function takes an separate inputted number and multiplies it by a constant, but that part isn't necessary to this.
Essentially a user provides the 3 values, I've already removed the one value which is the constant 'k', so I'm left with 2 values that determine the right multiplier for the constant 'k'.
I'm looking for something that would be easier and more robust than combining the array and running through all the possible solutions in a switch statement. There is a possibility of new variables for the array in the future.
let k = 5;
let input = [2, 'c'];
if (input.join().includes('1')) {
if (input.join().includes('a')) {
return k * 10;
};
else if (input.join().includes('b')) {
return k * 11;
};
else if (input.join().includes('c')) {
return k * 12;
};
else if (input.join().includes('d')) {
return k * 13;
};
};
else if (input.join().includes('2')) {
if (input.join().includes('a')) {
return k * 14;
};
else if (input.join().includes('b')) {
return k * 15;
};
else if (input.join().includes('c')) {
return k * 16;
};
else if (input.join().includes('d')) {
return k * 17;
};
};
Basically I have something like this right now. input and k are provided by the user, but not necessarily in any certain order, so I can't reliable assume input[1] will give me (a, b, c or d).

I agree with Patrick's comment that there is a bit of mystery around what you're actually trying to do here. I also noticed your comment:
would I be able to add: var mathFuncs = { 1: 'one': { a: mathFunc10, b: mathFunc20 }
Are you saying you would like to be able to accept input values of 1 or one interchangeably?
You can do this very simply. The calculate() function below makes a copy of the input array with each element converted to a number if it is one of the number names, then sorts this array to put it in a consistent order (numbers sort before letters). Next, we select which list of multipliers to use (1 or 2), and finally get the specific multiplier (a-d) to calculate the return value. If the input array doesn't match anything, we return NaN.
const numbers = {
'one': 1,
'two': 2,
};
const multipliers = {
1: { a:10, b:11, c:12, d:13 },
2: { a:14, b:15, c:16, d:17 },
};
function calculate( k, input ) {
const convert = n => numbers[ n.toString().toLowerCase() ] || n;
const sorted = input.map( convert ).sort();
const list = multipliers[ sorted[0] ];
if( ! list ) return NaN;
const multiplier = list[ sorted[1] ];
return multiplier == null ? NaN : k * multiplier;
}
function test( k, input) {
console.log( k, input.toString(), calculate( k, input ) );
}
test( 5, [ 1, 'a' ] ); // 50
test( 5, [ 'a', 1 ] ); // 50
test( 5, [ 'a', 'one' ] ); // 50
test( 5, [ 2, 'c' ] ); // 80
test( 5, [ 'two', 'c' ] ); // 80
test( 5, [ 2, 'e' ] ); // NaN
test( 5, [ 3, 'a' ] ); // NaN
test( 5, [ 'three', 'a' ] ); // NaN

var mathFunc = getMathFunc([2, 'b']);
mathFunc(2); // 80
function getMathFunc(arr) {
var inputString = arr.join('');
var mathFuncs = {
1: {
a: mathFunc10,
b: mathFunc20
},
2: {
a: mathFunc30,
b: mathFunc40
}
};
for (var numKey in mathFuncs) {
if (inputString.includes(numKey)) {
var numObj = mathFuncs[numKey];
for (var letterKey in numObj) {
if (inputString.includes(letterKey)) {
return numObj[letterKey];
}
}
}
}
function mathFunc10(num) {
return 10 * num;
}
function mathFunc20(num) {
return 20 * num;
}
function mathFunc30(num) {
return 30 * num;
}
function mathFunc40(num) {
return 40 * num;
}
}

If the logic of the returned value is like in your example, then this will do it:
const ref = { 1: 10, 2: 14, a: 0, b: 1, c: 2, d: 3 },
calc = (k, input) => k * input.reduce( (res, x) => res + ref[x], 0 );
// Sample calls:
console.log([
calc(5, [2, 'c']), // 80
calc(5, ['c', 2]), // 80
calc(5, ['a', 1]), // 50
calc(5, [2, 'b']), // 75
calc(5, [3, 'd']), // NaN
calc(5, ['e', 2]), // NaN
]);
Of course, if the logic is more complex in other cases, then you'll have to implement that logic in your function and/or the proposed ref mapping object. If the "variables" are more interdependent, you may even need several mapping objects.

Related

Sort Array by Element Frequency JavaScript

I want to sort an array by element frequency. My code works for arrays of strings, but not for arrays of numbers:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
frequencySort(['a','b','b','b','c','c'])) returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
frequencySort([4, 6, 2, 2, 6, 4, 4, 4]) returns [ 4, 4, 4, 4, 6, 2, 2, 6 ]
The only reason your letters worked is because you didn't have the same number of any two letters, where in your numbers, you have 2 of both 2 and 6.
Here's your snippet, but with 2 a's and 2 c's. You'll see it's out of order just like the numbers.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
You need a way to sort instances that have the same number of occurrences. I adapted your forEach loop to give the last index of each letter to your b object and then changed your sort to use that index in case the number of occurrences is the same.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach((i,index) => d[i] = {
num: countOccurrences(arr,i),
i: index
});
arr.sort(function(a,b){
let diff = d[b].num - d[a].num;
if(diff == 0)
diff = d[b].i - d[a].i;
return diff;
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4]));
It has nothing to do with the elements being letters or numbers. In you letters array, each letter has unique occurence count (3, 2, 1), therefore they are sorted the way you want to.
However, in your numbers array, "2" and "6" both occur 2 times each. Therefore, your sort callback function returns 0 for them, and they are treated as equal order by the sort function.
In your array of numbers you have the same amount of the number 2 as 6 and your sorting function doesn't care about the actual values it just cares about their counts. So in your example 2 and 6 both have the same priority.
You want to adjust your sorting function to compare values of elements if they have the same amount of occurrences.
You'll need to implement separate comparisons for all the data types you want to accept and decide if you want ascending/descending order.
Here is a basic example for number and string elements:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
const r = d[b] - d[a]
if (r != 0) return r
switch (typeof d[a]) {
case 'number': return a - b
case 'string': return a.localeCompare(b)
default: return 0
}
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c'])) // returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4])) // returns [ 4, 4, 4, 4, 2, 2, 6, 6 ]
A possible approach would first collect all equal array items within an item specific group array by a reduce task ...
console.log(
"grouped ['a','b','b','b','c','c'] ...",
['a','b','b','b','c','c'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
console.log(
"grouped [4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'] ...",
[4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The final computation then has to transform ... via Object.values ... the temporary result (as shown above) into an array of arrays of equal items where the former gets 1stly sorted by each array's length (indicates the items frequency) and 2ndly, for arrays of equal length', by a locale compare of each array's first item. The final result is the sorted array's flatted version ...
function sortItemsByFrequency(arr) {
const groupedItems = arr.reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {});
return Object
.values(groupedItems)
.sort((a, b) =>
// - sort by frequency first indicated by an
// array's length.
// - the higher frequency count wins.
b.length - a.length ||
// in case of equal frequency counts do a
// locale compare of both array's first items.
b[0].toLocaleString().localeCompare(a[0].toLocaleString())
)
.flat();
}
console.log(
"sortItemsByFrequency(['a','b','b','b','c','c']) ...",
sortItemsByFrequency(['a','b','b','b','c','c'])
);
console.log(
"sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2']) ...",
sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
sort first based on frequency of characters in desc order,if freq is same, then sort alphabetically in asc order.
const str = 'zzzzvnttteeeqqaao';
const frequencySort = (str = '') => {
let map = {}
for (const letter of str) {
map[letter] = (map[letter] || 0) + 1;
};
let res = "";
let sorted = Object.keys(map).sort((a, b) => map[b] < map[a] ? -1 : 1);
//console.log(sorted);
for (let letter of sorted) {
for (let count = 0; count < map[letter]; count++) {
res += letter
}
}
return res;
};
console.log(frequencySort(str));

How to convert nested loop in to recursion?

I want to convert this nested loops in recursion. How do I achieve this?
for(let i = 0; i < 5; i++) {
for(let j = 0; j < 5; j++) {
console.log(i,j);
}
}
Here another example of this recursion:
function loop(i,j,limitI,limitJ){
if(i>=limitI) return;
if(j>=limitJ) loop(i+1,0,limitI,limitJ);
else{
console.log(i,j);
loop(i,j+1,limitI,limitJ)
}
}
loop(0,0,4,4);
Generic function product calculates the Cartesian product of its inputs - You can polyfill Array.prototype.flatMap if it's not already in your environment
Array.prototype.flatMap = function (f, context)
{
return this.reduce ((acc, x) => acc.concat (f (x)), [])
}
const product = (first = [], ...rest) =>
{
const loop = (comb, first, ...rest) =>
rest.length === 0
? first.map (x => [ ...comb, x ])
: first.flatMap (x => loop ([ ...comb, x ], ...rest))
return loop ([], first, ...rest)
}
const suits =
['♤', '♡', '♧', '♢']
const ranks =
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
for (const card of product (ranks, suits))
console.log (card)
// [ 'A', '♤' ]
// [ 'A', '♡' ]
// [ 'A', '♧' ]
// [ 'A', '♢' ]
// [ '2', '♤' ]
// ...
// [ 'Q', '♧' ]
// [ 'K', '♤' ]
// [ 'K', '♡' ]
// [ 'K', '♧' ]
// [ 'K', '♢' ]
product is a variadic function (by use of a rest parameter) which accepts 1 or more inputs
const range = (min = 0, max = 0) =>
max < min
? []
: [ min, ...range (min + 1, max) ]
const r =
range (0, 2)
for (const comb of product (r, r, r))
console.log (comb)
// [ 0, 0, 0 ]
// [ 0, 0, 1 ]
// [ 0, 0, 2 ]
// [ 0, 1, 0 ]
// ...
// [ 2, 1, 2 ]
// [ 2, 2, 0 ]
// [ 2, 2, 1 ]
// [ 2, 2, 2 ]
Using destructuring assignment, you can effectively create nested loops
for (const [ i, j ] of product (range (0, 5), range (0, 5)))
console.log ("i %d, j %d", i, j)
// i 0, j 0
// i 0, j 1
// i 0, j 2
// i 0, j 3
// i 0, j 4
// i 0, j 5
// i 1, j 0
// ...
// i 4, j 5
// i 5, j 0
// i 5, j 1
// i 5, j 2
// i 5, j 3
// i 5, j 4
// i 5, j 5
product can also be written using generators - below, we find all perfect Pythagorean triples under 20
const product = function* (first, ...rest)
{
const loop = function* (comb, first, ...rest)
{
if (rest.length === 0)
for (const x of first)
yield [ ...comb, x ]
else
for (const x of first)
yield* loop ([ ...comb, x ], ...rest)
}
yield* loop ([], first, ...rest)
}
const range = (min = 0, max = 0) =>
max < min
? []
: [ min, ...range (min + 1, max) ]
const pythagTriple = (x, y, z) =>
(x * x) + (y * y) === (z * z)
const solver = function* (max = 20)
{
const N = range (1, max)
for (const [ x, y, z ] of product (N, N, N))
if (pythagTriple (x, y, z))
yield [ x, y, z ]
}
console.log ('solutions:', Array.from (solver (20)))
// solutions:
// [ [ 3, 4, 5 ]
// , [ 4, 3, 5 ]
// , [ 5, 12, 13 ]
// , [ 6, 8, 10 ]
// , [ 8, 6, 10 ]
// , [ 8, 15, 17 ]
// , [ 9, 12, 15 ]
// , [ 12, 5, 13 ]
// , [ 12, 9, 15 ]
// , [ 12, 16, 20 ]
// , [ 15, 8, 17 ]
// , [ 16, 12, 20 ]
// ]
I think using map (and reduce), while it allows for more complex recursive structures as you demonstrate, is actually an implicit for loop, which does not really answer the question on how to convert one into a recurrence. However, if you also defined a recursive map and reduce, then it would be OK :) - גלעד ברקן
Your wish is my command :D
const Empty =
Symbol ()
const concat = (xs, ys) =>
xs.concat (ys)
const append = (xs, x) =>
concat (xs, [ x ])
const reduce = (f, acc = null, [ x = Empty, ...xs ]) =>
x === Empty
? acc
: reduce (f, f (acc, x), xs)
const mapReduce = (m, r) =>
(acc, x) => r (acc, m (x))
const map = (f, xs = []) =>
reduce (mapReduce (f, append), [], xs)
const flatMap = (f, xs = []) =>
reduce (mapReduce (f, concat), [], xs)
const product = (first = [], ...rest) =>
{
const loop = (comb, first, ...rest) =>
rest.length === 0
? map (x => append (comb, x), first)
: flatMap (x => loop (append (comb, x), ...rest), first)
return loop ([], first, ...rest)
}
const suits =
['♤', '♡', '♧', '♢']
const ranks =
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
for (const card of product (ranks, suits))
console.log (card)
// same output as above
I don't recommend this but you can do following(as it is difficult to read, for readability and understandability your code is best).
function forLoop(i,j){
if(j===0){
if(i!==0)
forLoop(i-1,4);
console.log(i,j);
}
else{
forLoop(i,j-1);
console.log(i,j);
}
}
forLoop(4,4);
Here's my rendition:
function nested(i, j, maxI, maxJ) {
if (i == maxI) return
console.log(i, j)
if (i < maxI) {
++j < maxJ ? nested(i, j, maxI, maxJ) : nested(++i, 0, maxI, maxJ)
}
}
nested(0, 0, 5, 5)
This is an alternative.
This approach uses param initialization with comma operator (just to make the code shorter).
Additionally, an operator param (callback) to execute any logic for each iteration.
function loop(n, operator, i = 0, j = 0) { // Param initialization.
if (j === n) (j = 0, i++); // Comma operator.
if (i === n) return;
operator(i, j);
loop(n, operator, i, ++j);
}
loop(5, (i, j) => console.log(i, j));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use an array for limit and values. The order is reversed, because of the incrementing of the lowest index first.
This works for an arbitrary count of nested loops and it allowes to use an arbitrary limit of the max values.
function iter(limit, values = limit.map(_ => 0)) {
console.log(values.join(' '));
values = values.reduce((r, v, i) => {
r[i] = (r[i] || 0) + v;
if (r[i] >= limit[i]) {
r[i] = 0;
r[i + 1] = (r[i + 1] || 0) + 1;
}
return r;
}, [1]);
if (values.length > limit.length) {
return;
}
iter(limit, values);
}
iter([2, 3]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's an outline of a "recurrence relation," where "each further term of the sequence ... is defined as a function of the preceding terms."
As you are probably aware, recursive functions usually have at least one base case, terminating the recursion, and at least one recursive call. To find a pattern, let's examine the sequence:
0,0
0,1
0,2
0,3
0,4
1,0
1,2
...
Our base case, where the call to a preceding parameter terminates, seems to be 0,0. But this is also where the console logs begin, which means we first have to call all the way back to the base case. For convenience, let's assume the function expects positive parameters:
function f(i, j){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
}
We can also notice that the outer loop, the i, stays constant for each cycle of js:
function f(i, j){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
if (j == 0)
// ... what happens here?
}
but here we get stuck. When j is greater than zero, we can determine that the current term came from f(i, j - 1), but if j is zero in the current term, we have no way of formulating what it was in the preceding term. We need one more parameter:
function f(i, j, jj){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
if (j == 0)
f(i - 1, jj, jj);
else
f(i, j - 1, jj);
console.log(i,j);
}
f(4,4,4);
You could recurse by taking a depth and the values to iterate:
function loop(start, end, depth, exit, ...args){
for(let i = start; i < end; i++)
depth ? loop(start, end, depth - 1, exit, ...args, i) : exit(...args, i);
}
Usable as:
loop(0, 5, 1, (i, j) => console.log(i, j))
The only real usecase would be deeper loops, e.g. this one
If you want it completely without for:
const range = (start, end, cb) =>
(cb(start), start + 1 >= end || range (start + 1, end, cb));
function loop(start, end, depth, exit, ...args){
range(start, end, i =>
depth ? loop(start, end, depth - 1, exit, ...args, i) : exit(...args, i));
}
Try it
Transforming a nested for loop into its recursive counterpart is surprisingly hard. Good question!
You can transform every loop (without a stack) into a tail recursive algorithm. So this rule should hold for a nested loop too.
I think we need two distinct functions to get something equivalent to your two nested loops:
const loop = ([i, j], [k, l]) => {
const loop_ = (k_, l_) => {
if (k_ >= l_) return;
else {
console.log(i, k_);
loop_(k_ + 1, l_);
}
};
if (i >= j) return;
else {
loop_(k, l);
loop([i + 1, j], [k, l]);
}
};
loop([0, 5], [0, 5]);
You have to pass ranges for both the out and the inner loop.
As you can see both recursive calls are in tail position. I think this is the closest equivalent we can get.
suggested solution
function recurse(arg1=0, arg2=0, cb) {
if ( arg2 <= 5 ) {
let _l = arg2++;
if ( arg1 === 5 )
return ;
if ( ++_l === 6 ) {
arg2 = 0;
cb(arg1++, arg2);
recurse(arg1, arg2, cb);
} else {
cb(arg1, arg2 - 1);
recurse(arg1, arg2, cb);
}
}
}
recurse( 0 , 0 , (i,j) => console.log(i,j));

Trying to sort array by key [duplicate]

I have a multidimensional array. The primary array is an array of
[publicationID][publication_name][ownderID][owner_name]
What I am trying to do is sort the array by owner_name and then by publication_name. I know in JavaScript you have Array.sort(), into which you can put a custom function, in my case i have:
function mysortfunction(a, b) {
var x = a[3].toLowerCase();
var y = b[3].toLowerCase();
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}
This is fine for just sorting on the one column, namely owner_name, but how do I modify it to sort on owner_name, then publication_name?
If owner names differ, sort by them. Otherwise, use publication name for tiebreaker.
function mysortfunction(a, b) {
var o1 = a[3].toLowerCase();
var o2 = b[3].toLowerCase();
var p1 = a[1].toLowerCase();
var p2 = b[1].toLowerCase();
if (o1 < o2) return -1;
if (o1 > o2) return 1;
if (p1 < p2) return -1;
if (p1 > p2) return 1;
return 0;
}
I think what you're looking for is thenBy.js: https://github.com/Teun/thenBy.js
It allows you to use the standard Array.sort, but with firstBy().thenBy().thenBy() style.
An example can be seen here.
A good way to sort on many fields that are strings is to use toLocaleCompare and the boolean operator ||.
Something like:
// Sorting record releases by name and then by title.
releases.sort((oldRelease, newRelease) => {
const compareName = oldRelease.name.localeCompare(newRelease.name);
const compareTitle = oldRelease.title.localeCompare(newRelease.title);
return compareName || compareTitle;
})
If you wanted to sort on more fields, you could simply chain them off the return statement with more boolean operators.
Came across a need to do SQL-style mixed asc and desc object array sorts by keys.
kennebec's solution above helped me get to this:
Array.prototype.keySort = function(keys) {
keys = keys || {};
// via
// https://stackoverflow.com/questions/5223/length-of-javascript-object-ie-associative-array
var obLen = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key))
size++;
}
return size;
};
// avoiding using Object.keys because I guess did it have IE8 issues?
// else var obIx = function(obj, ix){ return Object.keys(obj)[ix]; } or
// whatever
var obIx = function(obj, ix) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (size == ix)
return key;
size++;
}
}
return false;
};
var keySort = function(a, b, d) {
d = d !== null ? d : 1;
// a = a.toLowerCase(); // this breaks numbers
// b = b.toLowerCase();
if (a == b)
return 0;
return a > b ? 1 * d : -1 * d;
};
var KL = obLen(keys);
if (!KL)
return this.sort(keySort);
for ( var k in keys) {
// asc unless desc or skip
keys[k] =
keys[k] == 'desc' || keys[k] == -1 ? -1
: (keys[k] == 'skip' || keys[k] === 0 ? 0
: 1);
}
this.sort(function(a, b) {
var sorted = 0, ix = 0;
while (sorted === 0 && ix < KL) {
var k = obIx(keys, ix);
if (k) {
var dir = keys[k];
sorted = keySort(a[k], b[k], dir);
ix++;
}
}
return sorted;
});
return this;
};
sample usage:
var obja = [
{USER:"bob", SCORE:2000, TIME:32, AGE:16, COUNTRY:"US"},
{USER:"jane", SCORE:4000, TIME:35, AGE:16, COUNTRY:"DE"},
{USER:"tim", SCORE:1000, TIME:30, AGE:17, COUNTRY:"UK"},
{USER:"mary", SCORE:1500, TIME:31, AGE:19, COUNTRY:"PL"},
{USER:"joe", SCORE:2500, TIME:33, AGE:18, COUNTRY:"US"},
{USER:"sally", SCORE:2000, TIME:30, AGE:16, COUNTRY:"CA"},
{USER:"yuri", SCORE:3000, TIME:34, AGE:19, COUNTRY:"RU"},
{USER:"anita", SCORE:2500, TIME:32, AGE:17, COUNTRY:"LV"},
{USER:"mark", SCORE:2000, TIME:30, AGE:18, COUNTRY:"DE"},
{USER:"amy", SCORE:1500, TIME:29, AGE:19, COUNTRY:"UK"}
];
var sorto = {
SCORE:"desc",TIME:"asc", AGE:"asc"
};
obja.keySort(sorto);
yields the following:
0: { USER: jane; SCORE: 4000; TIME: 35; AGE: 16; COUNTRY: DE; }
1: { USER: yuri; SCORE: 3000; TIME: 34; AGE: 19; COUNTRY: RU; }
2: { USER: anita; SCORE: 2500; TIME: 32; AGE: 17; COUNTRY: LV; }
3: { USER: joe; SCORE: 2500; TIME: 33; AGE: 18; COUNTRY: US; }
4: { USER: sally; SCORE: 2000; TIME: 30; AGE: 16; COUNTRY: CA; }
5: { USER: mark; SCORE: 2000; TIME: 30; AGE: 18; COUNTRY: DE; }
6: { USER: bob; SCORE: 2000; TIME: 32; AGE: 16; COUNTRY: US; }
7: { USER: amy; SCORE: 1500; TIME: 29; AGE: 19; COUNTRY: UK; }
8: { USER: mary; SCORE: 1500; TIME: 31; AGE: 19; COUNTRY: PL; }
9: { USER: tim; SCORE: 1000; TIME: 30; AGE: 17; COUNTRY: UK; }
keySort: { }
(using a print function from here)
here is a jsbin example.
edit: cleaned up and posted as mksort.js on github.
This is handy for alpha sorts of all sizes.
Pass it the indexes you want to sort by, in order, as arguments.
Array.prototype.deepSortAlpha= function(){
var itm, L=arguments.length, order=arguments;
var alphaSort= function(a, b){
a= a.toLowerCase();
b= b.toLowerCase();
if(a== b) return 0;
return a> b? 1:-1;
}
if(!L) return this.sort(alphaSort);
this.sort(function(a, b){
var tem= 0, indx=0;
while(tem==0 && indx<L){
itm=order[indx];
tem= alphaSort(a[itm], b[itm]);
indx+=1;
}
return tem;
});
return this;
}
var arr= [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"],
["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]];
arr.deepSortAlpha(1,0);
I suggest to use a built in comparer and chain the wanted sort order with logical or ||.
function customSort(a, b) {
return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
}
Working example:
var array = [
[0, 'Aluminium', 0, 'Francis'],
[1, 'Argon', 1, 'Ada'],
[2, 'Brom', 2, 'John'],
[3, 'Cadmium', 3, 'Marie'],
[4, 'Fluor', 3, 'Marie'],
[5, 'Gold', 1, 'Ada'],
[6, 'Kupfer', 4, 'Ines'],
[7, 'Krypton', 4, 'Joe'],
[8, 'Sauerstoff', 3, 'Marie'],
[9, 'Zink', 5, 'Max']
];
array.sort(function (a, b) {
return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
});
document.write('<pre>');
array.forEach(function (a) {
document.write(JSON.stringify(a) + '<br>');
});
You could concat the 2 variables together into a sortkey and use that for your comparison.
list.sort(function(a,b){
var aCat = a.var1 + a.var2;
var bCat = b.var1 + b.var2;
return (aCat > bCat ? 1 : aCat < bCat ? -1 : 0);
});
I found multisotr. This is simple, powerfull and small library for multiple sorting. I was need to sort an array of objects with dynamics sorting criteria:
const criteria = ['name', 'speciality']
const data = [
{ name: 'Mike', speciality: 'JS', age: 22 },
{ name: 'Tom', speciality: 'Java', age: 30 },
{ name: 'Mike', speciality: 'PHP', age: 40 },
{ name: 'Abby', speciality: 'Design', age: 20 },
]
const sorted = multisort(data, criteria)
console.log(sorted)
<script src="https://cdn.rawgit.com/peterkhayes/multisort/master/multisort.js"></script>
This library more mutch powerful, that was my case. Try it.
String Appending Method
You can sort by multiple values simply by appending the values into a string and comparing the strings. It is helpful to add a split key character to prevent runoff from one key to the next.
Example
const arr = [
{ a: 1, b: 'a', c: 3 },
{ a: 2, b: 'a', c: 5 },
{ a: 1, b: 'b', c: 4 },
{ a: 2, b: 'a', c: 4 }
]
function sortBy (arr, keys, splitKeyChar='~') {
return arr.sort((i1,i2) => {
const sortStr1 = keys.reduce((str, key) => str + splitKeyChar+i1[key], '')
const sortStr2 = keys.reduce((str, key) => str + splitKeyChar+i2[key], '')
return sortStr1.localeCompare(sortStr2)
})
}
console.log(sortBy(arr, ['a', 'b', 'c']))
Recursion Method
You can also use Recursion to do this. It is a bit more complex than the String Appending Method but it allows you to do ASC and DESC on the key level. I'm commenting on each section as it is a bit more complex.
There are a few commented out tests to show and verify the sorting works with a mixture of order and default order.
Example
const arr = [
{ a: 1, b: 'a', c: 3 },
{ a: 2, b: 'a', c: 5 },
{ a: 1, b: 'b', c: 4 },
{ a: 2, b: 'a', c: 4 }
]
function sortBy (arr, keys) {
return arr.sort(function sort (i1,i2, sKeys=keys) {
// Get order and key based on structure
const compareKey = (sKeys[0].key) ? sKeys[0].key : sKeys[0];
const order = sKeys[0].order || 'ASC'; // ASC || DESC
// Calculate compare value and modify based on order
let compareValue = i1[compareKey].toString().localeCompare(i2[compareKey].toString())
compareValue = (order.toUpperCase() === 'DESC') ? compareValue * -1 : compareValue
// See if the next key needs to be considered
const checkNextKey = compareValue === 0 && sKeys.length !== 1
// Return compare value
return (checkNextKey) ? sort(i1, i2, sKeys.slice(1)): compareValue;
})
}
// console.log(sortBy(arr, ['a', 'b', 'c']))
console.log(sortBy(arr, [{key:'a',order:'desc'}, 'b', 'c']))
// console.log(sortBy(arr, ['a', 'b', {key:'c',order:'desc'}]))
// console.log(sortBy(arr, ['a', {key:'b',order:'desc'}, 'c']))
// console.log(sortBy(arr, [{key:'a',order:'asc'}, {key:'b',order:'desc'}, {key:'c',order:'desc'}]))
Try this:
t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );
let t = [
//[publicationID, publication_name, ownderID, owner_name ]
[1, 'ZBC', 3, 'John Smith'],
[2, 'FBC', 5, 'Mike Tyson'],
[3, 'ABC', 7, 'Donald Duck'],
[4, 'DBC', 1, 'Michael Jackson'],
[5, 'XYZ', 2, 'Michael Jackson'],
[6, 'BBC', 4, 'Michael Jackson'],
];
// owner_name subarrray index = 3
// publication_name subarrray index = 1
t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );
console.log(t.join('\n'));
I assume that your data in array let t = [ [publicationID, publication_name, ownderID, owner_name ], ... ] where index of owner_name = 3 and publication_name =1.
I was working with ng-grid and needed to to multiple column sorting on an array of records returned from an API, so I came up with this nifty, dynamic multi-sort function.
First of all, ng-grid fires an "event" for "ngGridSorted" and passes this structure back, describing the sort:
sortData = {
columns: DOM Element,
directions: [], //Array of string values desc or asc. Each index relating to the same index of fields
fields: [], //Array of string values
};
So I built a function that will dynamically generate a sort function based on the sortData as shown above (Don't be scared by the scroll bar! It's only about 50 lines long! Also, I'm sorry about the slop. It prevented a horizontal scrollbar!):
function SortingFunction(sortData)
{
this.sortData = sortData;
this.sort = function(a, b)
{
var retval = 0;
if(this.sortData.fields.length)
{
var i = 0;
/*
Determine if there is a column that both entities (a and b)
have that are not exactly equal. The first one that we find
will be the column we sort on. If a valid column is not
located, then we will return 0 (equal).
*/
while( ( !a.hasOwnProperty(this.sortData.fields[i])
|| !b.hasOwnProperty(this.sortData.fields[i])
|| (a.hasOwnProperty(this.sortData.fields[i])
&& b.hasOwnProperty(this.sortData.fields[i])
&& a[this.sortData.fields[i]] === b[this.sortData.fields[i]])
) && i < this.sortData.fields.length){
i++;
}
if(i < this.sortData.fields.length)
{
/*
A valid column was located for both entities
in the SortData. Now perform the sort.
*/
if(this.sortData.directions
&& i < this.sortData.directions.length
&& this.sortData.directions[i] === 'desc')
{
if(a[this.sortData.fields[i]] > b[this.sortData.fields[i]])
retval = -1;
else if(a[this.sortData.fields[i]] < b[this.sortData.fields[i]])
retval = 1;
}
else
{
if(a[this.sortData.fields[i]] < b[this.sortData.fields[i]])
retval = -1;
else if(a[this.sortData.fields[i]] > b[this.sortData.fields[i]])
retval = 1;
}
}
}
return retval;
}.bind(this);
}
I then sort the results of my API (results) like so:
results.sort(new SortingFunction(sortData).sort);
I hope somebody else enjoys this solution as much as I do! Thanks!
I had a similar problem while displaying memory pool blocks from the output of some virtual DOM h-functions composition. Basically I faced to the same problem as sorting multi-criteria data like scoring results from players around the world.
I have noticed that multi-criteria sorting is:
- sort by the first column
- if equal, sort by the second
- if equal, sort by the third
- etc... nesting and nesting if-else
And if you don't care, you could fail quickly in a if-else nesting hell... like callback hell of promises...
What about if we write a "predicate" function to decide if which part of alternative using ? The predicate is simply :
// useful for chaining test
const decide = (test, other) => test === 0 ? other : test
Now after having written your classifying tests (byCountrySize, byAge, byGameType, byScore, byLevel...) whatever who need, you can weight your tests (1 = asc, -1 = desc, 0 = disable), put them in an array, and apply a reducing 'decide' function like this:
const multisort = (s1, s2) => {
const bcs = -1 * byCountrySize(s1, s2) // -1 = desc
const ba = 1 *byAge(s1, s2)
const bgt = 0 * byGameType(s1, s2) // 0 = doesn't matter
const bs = 1 * byScore(s1, s2)
const bl = -1 * byLevel(s1, s2) // -1 = desc
// ... other weights and criterias
// array order matters !
return [bcs, ba, bgt, bs, bl].reduce((acc, val) => decide(val, acc), 0)
}
// invoke [].sort with custom sort...
scores.sort(multisort)
And voila ! It's up to you to define your own criterias / weights / orders... but you get the idea. Hope this helps !
EDIT:
* ensure that there is a total sorting order on each column
* be aware of not having dependencies between columns orders, and no circular dependencies
if, not, sorting can be unstable !
function multiSort() {
var args =$.makeArray( arguments ),
sortOrder=1, prop='', aa='', b='';
return function (a, b) {
for (var i=0; i<args.length; i++){
if(args[i][0]==='-'){
prop=args[i].substr(1)
sortOrder=-1
}
else{sortOrder=1; prop=args[i]}
aa = a[prop].toLowerCase()
bb = b[prop].toLowerCase()
if (aa < bb) return -1 * sortOrder;
if (aa > bb) return 1 * sortOrder;
}
return 0
}
}
empArray.sort(multiSort( 'lastname','firstname')) Reverse with '-lastname'
My own library for working with ES6 iterables (blinq) allows (among other things) easy multi-level sorting
const blinq = window.blinq.blinq
// or import { blinq } from 'blinq'
// or const { blinq } = require('blinq')
const dates = [{
day: 1, month: 10, year: 2000
},
{
day: 1, month: 1, year: 2000
},
{
day: 2, month: 1, year: 2000
},
{
day: 1, month: 1, year: 1999
},
{
day: 1, month: 1, year: 2000
}
]
const sortedDates = blinq(dates)
.orderBy(x => x.year)
.thenBy(x => x.month)
.thenBy(x => x.day);
console.log(sortedDates.toArray())
// or console.log([...sortedDates])
<script src="https://cdn.jsdelivr.net/npm/blinq#2.0.2"></script>
I have just published to npm a micro-library called sort-helper (source on github). The idea is to import the helper by to create the comparison function for sort array method through the syntax items.sort(by(column, ...otherColumns)), with several way to express the columns to sort by:
By key: persons.sort(by('lastName', 'firstName')),
By selector: dates.sort(by(x => x.toISOString())),
In descending order: [3, 2, 4, 1].sort(by(desc(n => n))) → [3, 2, 1, 0],
Ignoring case: ['B', 'D', 'c', 'a'].sort(by(ignoreCase(x => x))).join('') → 'aBcD'.
It's similar to the nice thenBy mentioned in this answer but with the following differences that may be more to the taste of some:
An approach more functional than object-oriented (see thenBy fluent API),
A syntax a bit terser and still as much readable, natural almost like SQL.
Fully implemented in TypeScript, to benefit from type safety and type expressivity.
Sourced from GitHub
function sortMethodAsc(a, b) {
return a == b ? 0 : a > b ? 1 : -1;
}
function sortMethodWithDirection(direction) {
if (direction === undefined || direction == "asc") {
return sortMethodAsc;
} else {
return function(a, b) {
return -sortMethodAsc(a, b);
}
}
}
function sortMethodWithDirectionByColumn(columnName, direction){
const sortMethod = sortMethodWithDirection(direction)
return function(a, b){
return sortMethod(a[columnName], b[columnName]);
}
}
function sortMethodWithDirectionMultiColumn(sortArray) {
//sample of sortArray
// sortArray = [
// { column: "column5", direction: "asc" },
// { column: "column3", direction: "desc" }
// ]
const sortMethodsForColumn = (sortArray || []).map( item => sortMethodWithDirectionByColumn(item.column, item.direction) );
return function(a,b) {
let sorted = 0;
let index = 0;
while (sorted === 0 && index < sortMethodsForColumn.length) {
sorted = sortMethodsForColumn[index++](a,b);
}
return sorted;
}
}
//=============================================
//=============================================
//=============================================
//test
var data = [
{"CountryName":"Aruba","CountryCode":"ABW","GNI":280},{
"CountryName":"Afghanistan","CountryCode":"ABW","GNI":280},{"CountryName":"Angola","CountryCode":"AGO","GNI":280},{"CountryName":"Albania","CountryCode":"ALB","GNI":4320},
{"CountryName":"Arab World","CountryCode":"ARB","GNI":280},{"CountryName":"United Arab Emirates","CountryCode":"ARE","GNI":39130},
{"CountryName":"Argentina","CountryCode":"ARG","GNI":13030},{"CountryName":"Armenia","CountryCode":"ARM","GNI":3990},{"CountryName":"American Samoa","CountryCode":"ASM","GNI":280},
{"CountryName":"Antigua and Barbuda","CountryCode":"ATG","GNI":13810},{"CountryName":"Australia","CountryCode":"AUS","GNI":51360},
{"CountryName":"Austria","CountryCode":"AUT","GNI":45440},{"CountryName":"Azerbaijan","CountryCode":"AZE","GNI":4080},{"CountryName":"Burundi","CountryCode":"BDI","GNI":280},
{"CountryName":"Belgium","CountryCode":"BEL","GNI":41790},{"CountryName":"Benin","CountryCode":"BEN","GNI":800},{"CountryName":"Burkina Faso","CountryCode":"BFA","GNI":590},
{"CountryName":"Bangladesh","CountryCode":"BGD","GNI":1470},{"CountryName":"Bulgaria","CountryCode":"BGR","GNI":7860},{"CountryName":"Bahrain","CountryCode":"BHR","GNI":21150},
{"CountryName":"Bosnia and Herzegovina","CountryCode":"BIH","GNI":4910},{"CountryName":"Belarus","CountryCode":"BLR","GNI":5280},
{"CountryName":"Belize","CountryCode":"BLZ","GNI":4390},{"CountryName":"Bolivia","CountryCode":"BOL","GNI":3130},{"CountryName":"Brazil","CountryCode":"BRA","GNI":8600},
{"CountryName":"Barbados","CountryCode":"BRB","GNI":15270},{"CountryName":"Brunei Darussalam","CountryCode":"BRN","GNI":29600},
{"CountryName":"Bhutan","CountryCode":"BTN","GNI":2660},{"CountryName":"Botswana","CountryCode":"BWA","GNI":6730},
{"CountryName":"Central African Republic","CountryCode":"CAF","GNI":390},{"CountryName":"Canada","CountryCode":"CAN","GNI":42870},
{"CountryName":"Central Europe and the Baltics","CountryCode":"CEB","GNI":13009},{"CountryName":"Switzerland","CountryCode":"CHE","GNI":80560},
{"CountryName":"Chile","CountryCode":"CHL","GNI":13610},{"CountryName":"China","CountryCode":"CHN","GNI":8690},{"CountryName":"Cote d'Ivoire","CountryCode":"CIV","GNI":1580},
{"CountryName":"Cameroon","CountryCode":"CMR","GNI":1370},{"CountryName":"Colombia","CountryCode":"COL","GNI":5890},{"CountryName":"Comoros","CountryCode":"COM","GNI":1280},
{"CountryName":"Cabo Verde","CountryCode":"CPV","GNI":3030},{"CountryName":"Costa Rica","CountryCode":"CRI","GNI":11120},
{"CountryName":"Caribbean small states","CountryCode":"CSS","GNI":8909},{"CountryName":"Cyprus","CountryCode":"CYP","GNI":23720},
{"CountryName":"Czech Republic","CountryCode":"CZE","GNI":18160},{"CountryName":"Germany","CountryCode":"DEU","GNI":43490},
{"CountryName":"Djibouti","CountryCode":"DJI","GNI":1880},{"CountryName":"Dominica","CountryCode":"DMA","GNI":6590},{"CountryName":"Denmark","CountryCode":"DNK","GNI":55220},
{"CountryName":"Dominican Republic","CountryCode":"DOM","GNI":6630},{"CountryName":"Algeria","CountryCode":"DZA","GNI":3940},
{"CountryName":"East Asia & Pacific (excluding high income)","CountryCode":"EAP","GNI":6987},{"CountryName":"Early-demographic dividend","CountryCode":"EAR","GNI":3352},
{"CountryName":"East Asia & Pacific","CountryCode":"EAS","GNI":10171},{"CountryName":"Europe & Central Asia (excluding high income)","CountryCode":"ECA","GNI":7375},
{"CountryName":"Europe & Central Asia","CountryCode":"ECS","GNI":22656},{"CountryName":"Ecuador","CountryCode":"ECU","GNI":5920},
{"CountryName":"Euro area","CountryCode":"EMU","GNI":35645},{"CountryName":"Spain","CountryCode":"ESP","GNI":27180},{"CountryName":"Estonia","CountryCode":"EST","GNI":18190},
{"CountryName":"Ethiopia","CountryCode":"ETH","GNI":740},{"CountryName":"European Union","CountryCode":"EUU","GNI":32784},
{"CountryName":"Fragile and conflict affected situations","CountryCode":"FCS","GNI":1510},{"CountryName":"Finland","CountryCode":"FIN","GNI":44580},
{"CountryName":"Fiji","CountryCode":"FJI","GNI":4970},{"CountryName":"France","CountryCode":"FRA","GNI":37970},{"CountryName":"Gabon","CountryCode":"GAB","GNI":6650},
{"CountryName":"United Kingdom","CountryCode":"GBR","GNI":40530},{"CountryName":"Georgia","CountryCode":"GEO","GNI":3780},{"CountryName":"Ghana","CountryCode":"GHA","GNI":1880},
{"CountryName":"Guinea","CountryCode":"GIN","GNI":790},{"CountryName":"Guinea-Bissau","CountryCode":"GNB","GNI":660},
{"CountryName":"Equatorial Guinea","CountryCode":"GNQ","GNI":7050},{"CountryName":"Greece","CountryCode":"GRC","GNI":18090},
{"CountryName":"Grenada","CountryCode":"GRD","GNI":9180},{"CountryName":"Guatemala","CountryCode":"GTM","GNI":4060},{"CountryName":"Guyana","CountryCode":"GUY","GNI":4500},
{"CountryName":"High income","CountryCode":"HIC","GNI":40142},{"CountryName":"Honduras","CountryCode":"HND","GNI":2250},{"CountryName":"Heavily indebted poor countries (HIPC)","CountryCode":"HPC","GNI":904},{"CountryName":"Croatia","CountryCode":"HRV","GNI":12570},{"CountryName":"Haiti","CountryCode":"HTI","GNI":760},{"CountryName":"Hungary","CountryCode":"HUN","GNI":12870},{"CountryName":"IBRD only","CountryCode":"IBD","GNI":5745},{"CountryName":"IDA & IBRD total","CountryCode":"IBT","GNI":4620},{"CountryName":"IDA total","CountryCode":"IDA","GNI":1313},{"CountryName":"IDA blend","CountryCode":"IDB","GNI":1791},
{"CountryName":"Indonesia","CountryCode":"IDN","GNI":3540},{"CountryName":"IDA only","CountryCode":"IDX","GNI":1074},{"CountryName":"India","CountryCode":"IND","GNI":1800},{"CountryName":"Ireland","CountryCode":"IRL","GNI":55290},{"CountryName":"Iraq","CountryCode":"IRQ","GNI":4630},{"CountryName":"Iceland","CountryCode":"ISL","GNI":60830},{"CountryName":"Israel","CountryCode":"ISR","GNI":37270},{"CountryName":"Italy","CountryCode":"ITA","GNI":31020},{"CountryName":"Jamaica","CountryCode":"JAM","GNI":4760},{"CountryName":"Jordan","CountryCode":"JOR","GNI":3980},{"CountryName":"Japan","CountryCode":"JPN","GNI":38550},{"CountryName":"Kazakhstan","CountryCode":"KAZ","GNI":7970},{"CountryName":"Kenya","CountryCode":"KEN","GNI":1460},{"CountryName":"Kyrgyz Republic","CountryCode":"KGZ","GNI":1130},
{"CountryName":"Cambodia","CountryCode":"KHM","GNI":1230},{"CountryName":"Kiribati","CountryCode":"KIR","GNI":3010},{"CountryName":"St. Kitts and Nevis","CountryCode":"KNA","GNI":16240},{"CountryName":"Kuwait","CountryCode":"KWT","GNI":31430},{"CountryName":"Latin America & Caribbean (excluding high income)","CountryCode":"LAC","GNI":7470},{"CountryName":"Lao PDR","CountryCode":"LAO","GNI":2270},{"CountryName":"Lebanon","CountryCode":"LBN","GNI":8400},{"CountryName":"Liberia","CountryCode":"LBR","GNI":620},{"CountryName":"Libya","CountryCode":"LBY","GNI":5500},{"CountryName":"St. Lucia","CountryCode":"LCA","GNI":8830},{"CountryName":"Latin America & Caribbean","CountryCode":"LCN","GNI":8251},{"CountryName":"Least developed countries: UN classification","CountryCode":"LDC","GNI":1011},{"CountryName":"Low income","CountryCode":"LIC","GNI":774},{"CountryName":"Sri Lanka","CountryCode":"LKA","GNI":3850},{"CountryName":"Lower middle income","CountryCode":"LMC","GNI":2118},{"CountryName":"Low & middle income","CountryCode":"LMY","GNI":4455},{"CountryName":"Lesotho","CountryCode":"LSO","GNI":1210},{"CountryName":"Late-demographic dividend","CountryCode":"LTE","GNI":8518},{"CountryName":"Lithuania","CountryCode":"LTU","GNI":15200},{"CountryName":"Luxembourg","CountryCode":"LUX","GNI":70260},{"CountryName":"Latvia","CountryCode":"LVA","GNI":14740},{"CountryName":"Morocco","CountryCode":"MAR","GNI":2860},{"CountryName":"Moldova","CountryCode":"MDA","GNI":2200},{"CountryName":"Madagascar","CountryCode":"MDG","GNI":400},{"CountryName":"Maldives","CountryCode":"MDV","GNI":9760},
{"CountryName":"Middle East & North Africa","CountryCode":"MEA","GNI":7236},{"CountryName":"Mexico","CountryCode":"MEX","GNI":8610},{"CountryName":"Marshall Islands","CountryCode":"MHL","GNI":4840},{"CountryName":"Middle income","CountryCode":"MIC","GNI":4942},{"CountryName":"Mali","CountryCode":"MLI","GNI":770},
{"CountryName":"Malta","CountryCode":"MLT","GNI":24080},{"CountryName":"Myanmar","CountryCode":"MMR","GNI":1210},{"CountryName":"Middle East & North Africa (excluding high income)","CountryCode":"MNA","GNI":3832},{"CountryName":"Montenegro","CountryCode":"MNE","GNI":7400},{"CountryName":"Mongolia","CountryCode":"MNG","GNI":3270},{"CountryName":"Mozambique","CountryCode":"MOZ","GNI":420},{"CountryName":"Mauritania","CountryCode":"MRT","GNI":1100},{"CountryName":"Mauritius","CountryCode":"MUS","GNI":10130},{"CountryName":"Malawi","CountryCode":"MWI","GNI":320},{"CountryName":"Malaysia","CountryCode":"MYS","GNI":9650},{"CountryName":"North America","CountryCode":"NAC","GNI":56721},{"CountryName":"Namibia","CountryCode":"NAM","GNI":4570},{"CountryName":"Niger","CountryCode":"NER","GNI":360},{"CountryName":"Nigeria","CountryCode":"NGA","GNI":2100},
{"CountryName":"Nicaragua","CountryCode":"NIC","GNI":2130},{"CountryName":"Netherlands","CountryCode":"NLD","GNI":46180},{"CountryName":"Norway","CountryCode":"NOR","GNI":75990},{"CountryName":"Nepal","CountryCode":"NPL","GNI":800},{"CountryName":"Nauru","CountryCode":"NRU","GNI":10220},{"CountryName":"New Zealand","CountryCode":"NZL","GNI":38970},{"CountryName":"OECD members","CountryCode":"OED","GNI":37273},{"CountryName":"Oman","CountryCode":"OMN","GNI":14440},{"CountryName":"Other small states","CountryCode":"OSS","GNI":12199},{"CountryName":"Pakistan","CountryCode":"PAK","GNI":1580},{"CountryName":"Panama","CountryCode":"PAN","GNI":13280},{"CountryName":"Peru","CountryCode":"PER","GNI":5960},{"CountryName":"Philippines","CountryCode":"PHL","GNI":3660},{"CountryName":"Palau","CountryCode":"PLW","GNI":12700},{"CountryName":"Papua New Guinea","CountryCode":"PNG","GNI":2340},{"CountryName":"Poland","CountryCode":"POL","GNI":12730},{"CountryName":"Pre-demographic dividend","CountryCode":"PRE","GNI":1379},{"CountryName":"Portugal","CountryCode":"PRT","GNI":19820},{"CountryName":"Paraguay","CountryCode":"PRY","GNI":5470},{"CountryName":"West Bank and Gaza","CountryCode":"PSE","GNI":3180},{"CountryName":"Pacific island small states","CountryCode":"PSS","GNI":3793},{"CountryName":"Post-demographic dividend","CountryCode":"PST","GNI":41609},{"CountryName":"Qatar","CountryCode":"QAT","GNI":60510},{"CountryName":"Romania","CountryCode":"ROU","GNI":10000},{"CountryName":"Russian Federation","CountryCode":"RUS","GNI":9230},{"CountryName":"Rwanda","CountryCode":"RWA","GNI":720},{"CountryName":"South Asia","CountryCode":"SAS","GNI":1729},{"CountryName":"Saudi Arabia","CountryCode":"SAU","GNI":20090},{"CountryName":"Sudan","CountryCode":"SDN","GNI":2380},{"CountryName":"Senegal","CountryCode":"SEN","GNI":1240},{"CountryName":"Singapore","CountryCode":"SGP","GNI":54530},{"CountryName":"Solomon Islands","CountryCode":"SLB","GNI":1920},{"CountryName":"Sierra Leone","CountryCode":"SLE","GNI":510},{"CountryName":"El Salvador","CountryCode":"SLV","GNI":3560},{"CountryName":"Serbia","CountryCode":"SRB","GNI":5180},{"CountryName":"Sub-Saharan Africa (excluding high income)","CountryCode":"SSA","GNI":1485},{"CountryName":"Sub-Saharan Africa","CountryCode":"SSF","GNI":1486},{"CountryName":"Small states","CountryCode":"SST","GNI":11099},{"CountryName":"Sao Tome and Principe","CountryCode":"STP","GNI":1770},{"CountryName":"Suriname","CountryCode":"SUR","GNI":5150},{"CountryName":"Slovak Republic","CountryCode":"SVK","GNI":16610},{"CountryName":"Slovenia","CountryCode":"SVN","GNI":22000},{"CountryName":"Sweden","CountryCode":"SWE","GNI":52590},{"CountryName":"Eswatini","CountryCode":"SWZ","GNI":2950},{"CountryName":"Seychelles","CountryCode":"SYC","GNI":14170},{"CountryName":"Chad","CountryCode":"TCD","GNI":640},{"CountryName":"East Asia & Pacific (IDA & IBRD countries)","CountryCode":"TEA","GNI":7061},
{"CountryName":"Europe & Central Asia (IDA & IBRD countries)","CountryCode":"TEC","GNI":7866},{"CountryName":"Togo","CountryCode":"TGO","GNI":610},{"CountryName":"Thailand","CountryCode":"THA","GNI":5950},{"CountryName":"Tajikistan","CountryCode":"TJK","GNI":990},{"CountryName":"Turkmenistan","CountryCode":"TKM","GNI":6380},{"CountryName":"Latin America & the Caribbean (IDA & IBRD countries)","CountryCode":"TLA","GNI":8179},{"CountryName":"Timor-Leste","CountryCode":"TLS","GNI":1790},{"CountryName":"Middle East & North Africa (IDA & IBRD countries)","CountryCode":"TMN","GNI":3839},{"CountryName":"Tonga","CountryCode":"TON","GNI":4010},{"CountryName":"South Asia (IDA & IBRD)","CountryCode":"TSA","GNI":1729},
{"CountryName":"Sub-Saharan Africa (IDA & IBRD countries)","CountryCode":"TSS","GNI":1486},{"CountryName":"Trinidad and Tobago","CountryCode":"TTO","GNI":15340},{"CountryName":"Tunisia","CountryCode":"TUN","GNI":3490},{"CountryName":"Turkey","CountryCode":"TUR","GNI":10940},{"CountryName":"Tuvalu","CountryCode":"TUV","GNI":4970},{"CountryName":"Tanzania","CountryCode":"TZA","GNI":910},{"CountryName":"Uganda","CountryCode":"UGA","GNI":600},{"CountryName":"Ukraine","CountryCode":"UKR","GNI":2390},{"CountryName":"Upper middle income","CountryCode":"UMC","GNI":8197},{"CountryName":"Uruguay","CountryCode":"URY","GNI":15250},{"CountryName":"United States","CountryCode":"USA","GNI":58270},{"CountryName":"Uzbekistan","CountryCode":"UZB","GNI":2000},{"CountryName":"St. Vincent and the Grenadines","CountryCode":"VCT","GNI":7390},{"CountryName":"Vietnam","CountryCode":"VNM","GNI":2160},{"CountryName":"Vanuatu","CountryCode":"VUT","GNI":2920},{"CountryName":"World","CountryCode":"WLD","GNI":10371},{"CountryName":"Samoa","CountryCode":"WSM","GNI":4090},{"CountryName":"Kosovo","CountryCode":"XKX","GNI":3900},
{"CountryName":"South Africa","CountryCode":"ZAF","GNI":5430},{"CountryName":"Zambia","CountryCode":"ZMB","GNI":1290},{"CountryName":"Zimbabwe","CountryCode":"ZWE","GNI":1170},
{"CountryName":"Zimbabwe","CountryCode":"ZWE","GNI":1171}];
const sortMethod = sortMethodWithDirectionMultiColumn(
[
{ column: "GNI", direction: "asc" },
{ column: "CountryCode", direction: "desc" }
]
);
let sortedData = data.sort(sortMethod);
console.log("sorted by: 1)column:GNI-asc, 2)column:CountryCode-desc")
console.table(sortedData);
console.log(sortedData);
I need this for a small project I'm working on, so performance is not a priority.
I have two arrays, main array I want to be sorted, and array of sorting rules. I loop that rules array inside sorting callback function, and try to exit that loop as soon as possible.
I use multiplier in order to convert -1 to 1 depending on weather I'm sorting a property in ascending or descending order.
let array = [
{fullName: 'Michael Schumacher', sport: 'Formula 1'},
{fullName: 'Michael Jordan', sport: 'Basketball'},
{fullName: 'Damon Hill', sport: 'Formula 1'},
{fullName: 'Kobe Bryant', sport: 'Basketball'},
{fullName: 'Lebron James', sport: 'Basketball'},
{fullName: 'Lewis Hamilton', sport: 'Formula 1'},
];
const sortArray = (array, options) => {
if (!Array.isArray(options)) {
options = [{ key: options, order: 'asc' }];
}
options.forEach(item => {
item.multiplier = item.order != 'desc' ? -1 : 1;
});
return array.sort((firstItem, secondItem) => {
for (item of options) {
const { key, multiplier } = item;
const firstValue = firstItem[key];
const secondValue = secondItem[key];
if (firstValue != secondValue) {
return multiplier * (firstValue < secondValue ? 1 : -1);
}
}
return 0;
});
}
console.log('Original array');
console.log([...array]);
sortArray(array, 'sport');
console.log('Sorted by sport only (ascending, implicit, keeping the same order of athletes)');
console.log([...array]);
sortArray(array, [{key: 'sport'}, {key: 'fullName', order: 'desc'}]);
console.log('Sorted by sport (ascending, implicit), and by fullName (descending)');
console.log(array);
To simplify the understanding
The sort method compares numbers, if below 0, it sorts it to the let, if above zero it sorts it to the right.
So to add multi level sorting, check if the match === 0, then further sort it.
See example below
['a/b/c', 'a long piece of text/b', 'apple/b'].sort((a, b) => {
const asc = a.split('/').length - b.split('/').length
return asc
})
// outputs ['a long piece of text/b', 'apple/b', 'a/b/c']
['a/b/c', 'a long piece of text/b', 'apple/b'].sort((a, b) => {
const asc = a.split('/').length - b.split('/').length
return asc === 0 ? a.length - b.length : asc
})
// outputs: 'apple/b', 'a long piece of text/b', 'a/b/c'
I see a lot of complicated solutions, so I'll paste here what I'm using:
assignedIssues.sort((a, b) => {
let order = sortByText(a.assignee?.username, b.assignee?.username)
if (order === 0) order = sort(a.labels, b.labels, statusLabels)
if (order === 0) order = sort(a.labels, b.labels, priorityLabels)
if (order === 0) order = sortByText(a.web_url, b.web_url)
return order
})
I think that this is much more readable, let you implement any custom sorting function for each level, without calling all unnecessarily.
Assuming you want to sort by multiple indexes, and assuming that you don't know the type of each field (string, number, or null).
You can create a function to sort with as many indexes as you like.
const compareWithType = (a, b) => {
if (typeof a === 'string') return a.localeCompare(b);
if (typeof a === 'number') return a - b;
return (!!a) - (!!b); // to sort non-string non-number falsy or null values, modify as you like.
}
const compareWithIndexes = (...indexes) => {
return (a, b) => {
for (let i in indexes) {
let diff = 0;
while (!diff) {
compareWithType(a[i], b[i]);
}
return diff;
}
}
}
[[1, 2, 3, 4, 5], [0, 2, 3, 4, 6]].sort(compareWithIndexes(2, 3, 4));
// compares (3 - 3) then (4 - 4) then (5 - 6)
Despite a lot of complicated answers here, I still like the basic way to do it
var arr = [
[3, 'pub2', 1, 'ownA'],
[1, 'pub1', 2, 'ownA'],
[2, 'pub1', 3, 'ownC']
];
// sorting priority is bottom to top, in this case owner name then publication name
// sort publication name
arr.sort((a,b) => a[1].localeCompare(b[1]));
// sort owner name
arr.sort((a,b) => a[3].localeCompare(b[3]));
console.log(arr);

Fast way to get the min/max values among properties of object

I have an object in javascript like this:
{ "a":4, "b":0.5 , "c":0.35, "d":5 }
Is there a fast way to get the minimum and maximum value among the properties without having to loop through them all? because the object I have is huge and I need to get the min/max value every two seconds. (The values of the object keeps changing).
Update: Modern version (ES6+)
let obj = { a: 4, b: 0.5 , c: 0.35, d: 5 };
let arr = Object.values(obj);
let min = Math.min(...arr);
let max = Math.max(...arr);
console.log( `Min value: ${min}, max value: ${max}` );
Original Answer:
Try this:
let obj = { a: 4, b: 0.5 , c: 0.35, d: 5 };
var arr = Object.keys( obj ).map(function ( key ) { return obj[key]; });
and then:
var min = Math.min.apply( null, arr );
var max = Math.max.apply( null, arr );
Live demo: http://jsfiddle.net/7GCu7/1/
There's no way to find the maximum / minimum in the general case without looping through all the n elements (if you go from, 1 to n-1, how do you know whether the element n isn't larger (or smaller) than the current max/min)?
You mentioned that the values change every couple of seconds. If you know exactly which values change, you can start with your previous max/min values, and only compare with the new ones, but even in this case, if one of the values which were modified was your old max/min, you may need to loop through them again.
Another alternative - again, only if the number of values which change are small - would be to store the values in a structure such as a tree or a heap, and as the new values arrive you'd insert (or update) them appropriately. But whether you can do that is not clear based on your question.
If you want to get the maximum / minimum element of a given list while looping through all elements, then you can use something like the snippet below, but you will not be able to do that without going through all of them
var list = { "a":4, "b":0.5 , "c":0.35, "d":5 };
var keys = Object.keys(list);
var min = list[keys[0]]; // ignoring case of empty list for conciseness
var max = list[keys[0]];
var i;
for (i = 1; i < keys.length; i++) {
var value = list[keys[i]];
if (value < min) min = value;
if (value > max) max = value;
}
You could try:
const obj = { a: 4, b: 0.5 , c: 0.35, d: 5 };
const max = Math.max.apply(null, Object.values(obj));
console.log(max) // 5
min and max have to loop through the input array anyway - how else would they find the biggest or smallest element?
So just a quick for..in loop will work just fine.
var min = Infinity, max = -Infinity, x;
for( x in input) {
if( input[x] < min) min = input[x];
if( input[x] > max) max = input[x];
}
// 1. iterate through object values and get them
// 2. sort that array of values ascending or descending and take first,
// which is min or max accordingly
let obj = { 'a': 4, 'b': 0.5, 'c': 0.35, 'd': 5 }
let min = Object.values(obj).sort((prev, next) => prev - next)[0] // 0.35
let max = Object.values(obj).sort((prev, next) => next - prev)[0] // 5
You can also try with Object.values
const points = { Neel: 100, Veer: 89, Shubham: 78, Vikash: 67 };
const vals = Object.values(points);
const max = Math.max(...vals);
const min = Math.min(...vals);
console.log(max);
console.log(min);
// Sorted
let Sorted = Object.entries({ "a":4, "b":0.5 , "c":0.35, "d":5 }).sort((prev, next) => prev[1] - next[1])
>> [ [ 'c', 0.35 ], [ 'b', 0.5 ], [ 'a', 4 ], [ 'd', 5 ] ]
//Min:
Sorted.shift()
>> [ 'c', 0.35 ]
// Max:
Sorted.pop()
>> [ 'd', 5 ]
You can use a reduce() function.
Example:
let obj = { "a": 4, "b": 0.5, "c": 0.35, "d": 5 }
let max = Object.entries(obj).reduce((max, entry) => entry[1] >= max[1] ? entry : max, [0, -Infinity])
let min = Object.entries(obj).reduce((min, entry) => entry[1] <= min[1] ? entry : min, [0, +Infinity])
console.log(max) // ["d", 5]
console.log(min) // ["c", 0.35]
Here's a solution that allows you to return the key as well and only does one loop. It sorts the Object's entries (by val) and then returns the first and last one.
Additionally, it returns the sorted Object which can replace the existing Object so that future sorts will be faster because it will already be semi-sorted = better than O(n). It's important to note that Objects retain their order in ES6.
const maxMinVal = (obj) => {
const sortedEntriesByVal = Object.entries(obj).sort(([, v1], [, v2]) => v1 - v2);
return {
min: sortedEntriesByVal[0],
max: sortedEntriesByVal[sortedEntriesByVal.length - 1],
sortedObjByVal: sortedEntriesByVal.reduce((r, [k, v]) => ({ ...r, [k]: v }), {}),
};
};
const obj = {
a: 4, b: 0.5, c: 0.35, d: 5
};
console.log(maxMinVal(obj));
To get the keys for max and min
var list = { "a":4, "b":0.5 , "c":0.35, "d":5 };
var keys = Object.keys(list);
var min = keys[0]; // ignoring case of empty list for conciseness
var max = keys[0];
var i;
for (i = 1; i < keys.length; i++) {
var value = keys[i];
if (list[value] < list[min]) min = value;
if (list[value] > list[max]) max = value;
}
console.log(min, '-----', max)
For nested structures of different depth, i.e. {node: {leaf: 4}, leaf: 1}, this will work (using lodash or underscore):
function getMaxValue(d){
if(typeof d === "number") {
return d;
} else if(typeof d === "object") {
return _.max(_.map(_.keys(d), function(key) {
return getMaxValue(d[key]);
}));
} else {
return false;
}
}
Using the lodash library you can write shorter
_({ "a":4, "b":0.5 , "c":0.35, "d":5 }).values().max();
var newObj = { a: 4, b: 0.5 , c: 0.35, d: 5 };
var maxValue = Math.max(...Object.values(newObj))
var minValue = Math.min(...Object.values(newObj))
obj.prototype.getMaxinObjArr = function (arr,propName) {
var _arr = arr.map(obj => obj[propName]);
return Math.max(..._arr);
}
This works for me:
var object = { a: 4, b: 0.5 , c: 0.35, d: 5 };
// Take all value from the object into list
var valueList = $.map(object,function(v){
return v;
});
var max = valueList.reduce(function(a, b) { return Math.max(a, b); });
var min = valueList.reduce(function(a, b) { return Math.min(a, b); });
If we are sorting date time value then follow the below described procedure
const Obj = {
"TRADE::Trade1": {
"dateTime": "2022-11-27T20:17:05.980Z",
},
"TRADE::Trade2": {
"dateTime": "2022-11-27T20:36:10.659Z",
},
"TRADE::Trade3": {
"dateTime": "2022-11-27T20:28:10.659Z",
}
}
const result = Object.entries(Obj).sort((prev, next) => new Date(prev[1].dateTime) - new Date(next[1].dateTime))
console.log(result)

Is there a shortcut to create padded array in JavaScript?

I have this javascript:
function padded_array(k, value){
var a = [];
a[k] = value;
return a;
}
padded_array(3, "hello"); //=> [undefined, undefined, undefined, 'hello']
Is it possible to shorten the code in the function body?
for all the googlers coming here - you're probably looking for this:
var pad_array = function(arr,len,fill) {
return arr.concat(Array(len).fill(fill)).slice(0,len);
}
From 2020 & 2021 : straight forward options
Let assume that is your Array
const yourArray = [1,2]
If you just want to loop 4 times (maybe for react jsx )
Array.from({length:4}) //[undefined,undefined,undefined,undefined]
Array(4).fill()//[undefined,undefined,undefined,undefined]
If you want to loop yourArray 4 times, but to start with values you already have
// unmutation option
Array.from({...yourArray, length:4}) //[1,2,undefined,undefined]
// unmutation option, but need some calcualtion
[...yourArray , ...Array(2) ] //[1,2,undefined,undefined]
[...Array(2), ...yourArray ] //[undefined,undefined,1,2]
// loop on your array several times
Array(3).fill(yourArray).flat() // [1, 2, 1, 2, 1, 2]
// mutation the original array.
yourArray.length = 4;
Array.from(yourArray) //[1,2,undefined,undefined]
If You actually want an Array with full of values. ex. with increment numbers.
Remap it
// unmutation option
Array.from({...yourArray,length:4}, (v,i) => v ?? i+1 )
// [1,'2',3, 4]
// Or, mutation the original array. and fill with "x"
array.yourArray.length = 4;
Array.from(yourArray, (v) => v ?? 'x')
// [1,'2','x','x']
If you want to exclude the 'hello', you can use
new Array(count);
to create padded Arrays.
Edit: Maybe like this ?
new Array(5).concat("hello")
Another solution using spread operator:
padArray = (length, value) => [...Array(length).fill(), value];
And the usage is the same as you mentioned:
padded_array(3, "hello"); //=> [undefined, undefined, undefined, 'hello']
For padding at the start:
function padArrayStart(arr, len, padding){
return Array(len - arr.length).fill(padding).concat(arr);
}
Demo:
function padArrayStart(arr, len, padding){
return Array(len - arr.length).fill(padding).concat(arr);
}
console.log(...padArrayStart([1,2,3], 5, 0));//0 0 1 2 3
console.log(...padArrayStart([4,5,6], 3, 0));//4 5 6
For padding at the end:
function padArrayEnd(arr, len, padding){
return arr.concat(Array(len - arr.length).fill(padding));
}
Demo:
function padArrayEnd(arr, len, padding){
return arr.concat(Array(len - arr.length).fill(padding));
}
console.log(...padArrayEnd(['a','b','c'], 10, 'z'));//a b c z z z z z z z
console.log(...padArrayEnd([0, 'a', 'd'], 6, -1));//0 a d -1 -1 -1
Not in standard ES5 or predecessor. Surely you can do something like $.extend([], {"3": "hello"}) in jQuery; you can even do
Object.create(Array.prototype, {"3": {value: "hello"} });
in bare ES5, but it is hack, I would not consider this a solution (if it is ok with you, you can adopt it).
You can use that if your JS doesn't support Array.prototype.fill() (ex. Google Apps Script) and you can't use the code from the first answer:
function array_pad(array, length, filler)
{
if(array.length < length)// [10.02.20] Fixed error that Dimitry K noticed
while(true)
if(array.push(filler) >= length)
break;
return array;
}
I know this is an old(er) question but wanted to add my 2 cents if someone stumbles here (like i initially did).
Anyway, heres my take with Array.from
const padded_array = (k, value) => Array.from({ length: k }).concat(value)
console.log(padded_array(3, "hello"));
Also you could do it with something like this:
const padded = (arr, pad, val) => {
arr[pad] = val
return arr
}
console.log(padded([],3,'hello'))
// push is faster than concat
// mutate array in place + return array
const pad_right = (a, l, f) =>
!Array.from({length: l - a.length})
.map(_ => a.push(f)) || a;
const a = [1, 2];
pad_right(a, 4, 'x');
// -> [ 1, 2, 'x', 'x' ]
a;
// -> [ 1, 2, 'x', 'x' ]
function leftPad(array, desiredLength, padding) {
array.unshift(...Array(desiredLength - array.length).fill(padding));
}
function rightPad(array, desiredLength, padding) {
array.push(...Array(desiredLength - array.length).fill(padding));
}
const myHello = ['hello'];
leftPad(myHello, 3, undefined);
// [undefined, undefined, 'hello']
const myHello2 = ['hello2'];
rightPad(myHello, 3, 0);
// ['hello2', 0, 0];

Categories

Resources