I have 2 lists/arrays:
[Examples]:
let labels = ["John", "Sophie", "Hannah", "Emilia"]
let data = [10, 40, 24, 25]
I want to sort both arrays with the order of data. (Each name corresponds with the value in the other array at the same index). I would use an object like this {name: value} but for my needs, they have to be seperate (at least the result). I have found a solution but i find it isnt very elegant.
[Desired Output Example]:
let data = [40, 25, 24, 10]
let labels = ["Sophie", "Emilia", "Hannah", "John"]
basically the other array has to be sorted with the same pattern that the data array will be sorted (numbers ascending)
-
2Shouldn't these two arrays be one array, which you can sort more easily?VLAZ– VLAZ2021年05月30日 09:01:28 +00:00Commented May 30, 2021 at 9:01
-
Are there duplicate values?Majed Badawi– Majed Badawi2021年05月30日 09:08:43 +00:00Commented May 30, 2021 at 9:08
-
Well show your solution then. Maybe someone will find a way to improve it.derpirscher– derpirscher2021年05月30日 09:10:12 +00:00Commented May 30, 2021 at 9:10
7 Answers 7
You can do this elegantly with the zip function (see also Javascript equivalent of Python's zip function). zip has an interesting property of being its own inverse, that is zip(zip([a, b])) === [a, b], so we zip both arrays into one, sort it and zip again.
let zip = (...a) => a[0].map((_, n) => a.map(b => b[n]))
let labels = ["John", "Sophie", "Hannah", "Emilia"]
let data = [10, 40, 24, 25];
[labels, data] = zip(...
zip(labels, data)
.sort((x, y) => y[1] - x[1])
)
console.log(...labels)
console.log(...data)
Comments
You could take some abstract functions, inspired by
- How can I perform operations in JavaScript just like we do pipeline of operations in Java streams? and
- Sort 2 array by column, as a parameter in a function
and transpose the data, sort it descending by index 1 and transpose the result. Then reassign by destructuring.
const
swap = fn => (a, b) => fn(b, a),
prop = k => o => o[k],
asc = (a, b) => (a > b) - (a < b),
desc = swap(asc),
transpose = array => array.reduce((r, a) => a.map((v, i) => [...(r[i] || []), v]), []),
take = pre => fn => (x, y) => fn(pre(x), pre(y)),
sortBy = fn => array => array.sort(fn),
pipe = (...functions) => input => functions.reduce((acc, fn) => fn(acc), input),
pipeline = pipe(
transpose,
sortBy(take(prop(1))(desc)),
transpose
);
let labels = ["John", "Sophie", "Hannah", "Emilia"],
data = [10, 40, 24, 25];
[labels, data] = pipeline([labels, data]);
console.log(...labels);
console.log(...data);
1 Comment
|> is not there yet.You can first merge both the arrays into one like
[
{label: ..., rank: ...},
{label: ..., rank: ...},
{label: ..., rank: ...},
]
Then sort the above array using the rank property
And finally, map the array to extract either the label or rank properties
const labels = ["John", "Sophie", "Hannah", "Emilia"]
const ranks = [10, 40, 24, 25];
// Merge them both to a single array
const labelRankList = labels.map((label, i) => ({
label,
rank: ranks[i]
}));
const sortedList = labelRankList
.sort((a, b) => b.rank - a.rank) // Sort by rank
console.log("Labels : ", sortedList.map(x => x.label));
console.log("Data : ", sortedList.map(x => x.rank));
Comments
Here's an option that doesn't zip the related arrays, instead it creates an index array, sorts it by the relevant source array and then orders all the passed arrays by the sorted indexes.
const sortBySharedIndex = (...arrs) => {
const index = arrs[0].map((_, i) => i)
index.sort((a, b) => arrs[0][b] - arrs[0][a]);
return arrs.map(arr => index.map(i => arr[i]));
}
let data = [10, 40, 24, 25];
let labels = ["John", "Sophie", "Hannah", "Emilia"];
let [sortedData, sortedLabels] = sortBySharedIndex(data, labels);
console.log(...sortedData)
console.log(...sortedLabels)
Comments
- Use
.map()to merge the arrays into an array of arrays (2D array)[[10, 'John'], [40, 'Sophie'], ...] - Then
.sort()by each sub-array's first elementarrArr[0][0] = 10, arrArr[1][0] = 40 - Then use a
for...ofloop to.push()each element of each sub-array into a new array.
Details are commented in demo below
let labels = ["John", "Sophie", "Hannah", "Emilia"];
let data = [10, 40, 24, 25];
// map() into an array of arrays
// ex. [[10, 'John'], [40, 'Sophie'],...]
let twoDArr = data.map((num, idx) => [num, labels[idx]]);
// sort() by the first element of each sub-array.
// ex. twoDArr[0][0] = 10, twoDarr[1][0] = 40
let sorted = twoDArr.sort((a, b) => b[0] - a[0]);
/*
for each sub-array of sorted 2D array
push sorted[i][0] into an empty array and
push sorted[i][1] into another empty array
*/
let keys = [];
let vals = [];
for (let [key, val] of sorted) {
keys.push(key);
vals.push(val);
}
console.log('data: '+keys);
console.log('labels: '+vals);
Comments
let data = [40, 25, 24, 10];
let labels = ["Sophie", "Emilia", "Hannah", "John"];
const res = data.map((n, i) => [labels[i], n])
.sort((a, b) => a[1] - b[1])
.map(a => a[0])
console.log(res); // [ 'John', 'Hannah', 'Emilia', 'Sophie' ];
2 Comments
data.map((n, i) => ({ [labels[i]]: n })) is a bit of a waste. Making each into a key-value pair only to use Object.values() on each and then Object.keys. Those can be skipped if you initially map to just arrays of pairs: data.map((n, i) => [labels[i], n])You can just map indices of labels to indices of data and sort based on values in data (O(n^2logn) complexity):
const labels = ["John", "Sophie", "Hannah", "Emilia"]
const data = [10, 40, 24, 25]
const sortedLabels = labels.sort((a, b) => data[labels.indexOf(b)] - data[labels.indexOf(a)])
const sortedData = data.sort((a, b) => b - a)
console.log(...sortedLabels)
console.log(...sortedData)
or performance version (O(nlogn) complexity) in case of bigger arrays:
const labels = ["John", "Sophie", "Hannah", "Emilia"]
const data = [10, 40, 24, 25]
const merged = labels.map((e, i) => [e, data[i]])
.sort((a, b) => b[1] - a[1])
const sortedLabels = merged.map(e => e[0])
const sortedData = merged.map(e => e[1])
console.log(...sortedLabels)
console.log(...sortedData)