JGStyle
JGStyle

Reputation: 127

sort array based on other array js

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)

Upvotes: 0

Views: 525

Answers (7)

Nina Scholz
Nina Scholz

Reputation: 386540

You could take some abstract functions, inspired by

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);

Upvotes: 2

ulou
ulou

Reputation: 5853

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)

Upvotes: 1

Dor Ben Itzhak
Dor Ben Itzhak

Reputation: 315

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' ];

Upvotes: 1

georg
georg

Reputation: 214949

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)

Upvotes: 5

zer00ne
zer00ne

Reputation: 43870

  1. Use .map() to merge the arrays into an array of arrays (2D array)
    [[10, 'John'], [40, 'Sophie'], ...]
    
  2. Then .sort() by each sub-array's first element
    arrArr[0][0] = 10, arrArr[1][0] = 40
    
  3. Then use a for...of loop 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);


  
                    

Upvotes: 1

pilchard
pilchard

Reputation: 12911

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)

Upvotes: 1

Akhil
Akhil

Reputation: 879

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));

Upvotes: 1

Related Questions