2

I have this 2d Array in JavaScript:

['Daniela', 'Jonas', 'Simon', 'Vincent']
['0', '3', '0', '4']

in which the numbers stand for the amount of guesses the user took to guess a random number. and i want it to look like this:

['Jonas', 'Vincent', 'Simon', 'Daniela']
['3', '4', '0', '0']

so basically, sorted by the numbers from lowest to highest (except 0 since the user didnt finish his run), but the names sorted accordingly to the numbers.

How (if) is this possible?

I tried sorting it like normally like a 2d Array and it didnt work, i tried splitting the different array and sorting them manually but then i dont know how to sort them connectively.

JonasMGGD
  • 21
  • 2
  • A 2D array would be: `[['Daniela', 'Jonas', 'Simon', 'Vincent'], ['0', '3', '0', '4']]` - not what you're presenting. But I assume that's what is meant there. Can you should the code of your best attempt? Also, why don't you use a better data model for your task?: `[{name: "foo", score: 4}, {...]` – Roko C. Buljan Mar 12 '23 at 11:17
  • You need to connect the two, either join them into tuples or objects, or create a Map indexed by user name with guesses as value. Storing them separately is only going to lead to headaches and possible data dissociation over time. – pilchard Mar 12 '23 at 11:20
  • some considerations: why not take a single array with objects or at least arrays of name and count? and why not take for count a number instead of a string with a numerical value? ususally it is better to treat numbers as numbers and avoid to convert this value to string and to number and again to string. – Nina Scholz Mar 12 '23 at 11:24

5 Answers5

2

If you like to keep arrays, you could take an array of indices, sort by the values and map finally both arrays with the indices.

let names = ['Daniela', 'Jonas', 'Simon', 'Vincent'],
    values = ['0', '3', '0', '4'],
    indices = [...values.keys()].sort((a, b) =>
        (+values[a] || Number.MAX_VALUE) - (+values[b] || Number.MAX_VALUE)
    );

[names, values] = [names, values].map(a => indices.map(i => a[i]));

console.log(...names);
console.log(...values);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • @Thatkookooguy that is the OP's expected output, read the full question. *'`['3', '4', '0', '0']` ...so basically, sorted by the numbers from lowest to highest (except 0 since the user didnt finish his run)'* – pilchard Mar 12 '23 at 11:40
0

Map the original array into pairs like [['0', 'Daniela'], ...]. Then sort by the number, treating zero as a special case that will always be last in the sort. Then use reduce to map the pairs back into the original 2D array format.

const arr = [['Daniela', 'Jonas', 'Simon', 'Vincent'],
             ['0', '3', '0', '4']]

console.log(arr[0].map((e,i)=>[arr[1][i],e])
  .sort(([a],[b])=>((a==0)-(b==0) || a-b))
  .reduce((a,[x,y])=>(a[0].push(y),a[1].push(x),a),[[],[]]))
Andrew Parks
  • 6,358
  • 2
  • 12
  • 27
0

As suggested by @Roko, an array of objects is preferred in this particular situation as it helps making a sorting function much easier.

However, if that is not at all possible we can zip them manually:

const zipped = users.map(
  (user, index) => ({ name: user, guesses: Number(guesses[index]) })
);

Then, we sort them based on their guesses. Note how I use Infinity as the sorting value if user.guesses === 0.

zipped.sort(
  (user1, user2) => (user1.guesses || Infinity) - (user2.guesses || Infinity)
);

However, how the order of users who did not complete the test was not specified. Hence, their order are assumed to be preserved.

Try it (I separated zipped back to two arrays, just in case you need them):

const users = ['Daniela', 'Jonas', 'Simon', 'Vincent'];
const guesses = ['0', '3', '0', '4'];

const zipped = users.map(
  (user, index) => ({ name: user, guesses: Number(guesses[index]) })
);

zipped.sort(
  (user1, user2) => (user1.guesses || Infinity) - (user2.guesses || Infinity)
);

console.log(zipped);

const [sortedUsers, sortedGuesses] = zipped.reduce(
  ([sortedUsers, sortedGuesses], currentUser) => [
    [...sortedUsers, currentUser.name],
    [...sortedGuesses, currentUser.guesses],
  ], [[], []]
);

console.log(sortedUsers, sortedGuesses);
InSync
  • 4,851
  • 4
  • 8
  • 30
  • This is a good explanation, but your reduce is highly inefficient since it repeatedly destructures, creates a new array, and then spreads. Just push to the initial accumulators. – pilchard Mar 12 '23 at 11:40
  • `[newUsers.push(user.name) && newUsers, newGuesses.push(user.guesses) && newGuesses]` looks way more complicated than the way I chose. If the data is not too large difference in performance should be negligible. – InSync Mar 12 '23 at 11:44
  • Don't destructure either, just push and return. `zipped.reduce((acc, currentUser) => { acc[0].push(user.name); acc[1].push(user.guesses); return acc; }, [[],[]]);` – pilchard Mar 12 '23 at 11:56
0

This is a perfect use case for the zip function:

let zip = (...a) => a[0].map((_, i) => a.map(r => r[i]))

//

let names = ['Daniela', 'Jonas', 'Simon', 'Vincent']
let scores = [0, 3, 0, 4]

let temp = zip(scores, names);
temp.sort((x, y) => (x[0] || Infinity) - (y[0] || Infinity));
[scores, names] = zip(...temp);

console.log(...names)
console.log(...scores)
gog
  • 10,367
  • 2
  • 24
  • 38
0

An efficient sorting method, without creating new arrays and objects. Minimum runtime.

const names = ['Daniela', 'Jonas', 'Simon', 'Vincent'];
const numbers = ['0', '3', '0', '4'];
for (let i = 1; i < names.length; i++) {
    for (let j = i - 1; j >= 0; j--) {
        if (numbers[j] > numbers[j + 1] && +numbers[j + 1] || !+numbers[j]) {
            [names[j], names[j + 1]] = [names[j + 1], names[j]];
            [numbers[j], numbers[j + 1]] = [numbers[j + 1], numbers[j]];
        } else {
            break;
        }
    }
}
console.log(...names);
Itzik
  • 275
  • 2
  • 4