Peter Olson
Peter Olson

Reputation: 142921

How can you sort an array without mutating the original array?

Let's suppose I wanted a sort function that returns a sorted copy of the inputted array. I naively tried this

function sort(arr) {
  return arr.sort();
}

and I tested it with this, which shows that my sort method is mutating the array.

var a = [2,3,7,5,3,7,1,3,4];
sort(a);
alert(a);  //alerts "1,2,3,3,3,4,5,7,7"

I also tried this approach

function sort(arr) {
  return Array.prototype.sort(arr);
}

but it doesn't work at all.

Is there a straightforward way around this, preferably a way that doesn't require hand-rolling my own sorting algorithm or copying every element of the array into a new one?

Upvotes: 510

Views: 280678

Answers (12)

Putzi San
Putzi San

Reputation: 6301

With the introduction of the new .toSorted method in JavaScript, there's now a straightforward way to get a sorted copy of the array without modifying the original array:

const sorted = arr.toSorted();

For more details, you can refer to the MDN documentation on .toSorted.

Note: Before using .toSorted, make sure to check the compatibility with your environment. This method requires Node.js >= 20.0.0 or a recent version of modern browsers. If you are working in an environment that does not support this version, you may need to use the older method below.


For completeness, here's the older method using ES6 spread syntax to create a copy before sorting:

const sorted = [...arr].sort();

The spread-syntax as array literal (copied from MDN):

const arr = [1, 2, 3];
const arr2 = [...arr]; // like arr.slice()

Upvotes: 547

Prati
Prati

Reputation: 49

Remember that if no argument is passed to sort, it will not properly sort numbers by value. For numbers, try this. This does not mutate the original array.

function sort(arr) {
  return arr.slice(0).sort((a,b) => a-b);
}

Upvotes: 5

XMehdi01
XMehdi01

Reputation: 1

ES2023 Array Method toSorted():

The toSorted() method of Array instances is the copying version of the sort() method. It returns a new array with the elements sorted in ascending order.

const arr = [2, 1, 3];
const arrSorted = arr.toSorted();
console.log(arr); //[2, 1, 3]
console.log(arrSorted); //[1, 2, 3]

Upvotes: 24

Ran Turner
Ran Turner

Reputation: 18026

Update - Array.prototype.toSorted() proposal

The Array.prototype.toSorted(compareFn) -> Array is a new method that was proposed to be added to the Array.prototype and is currently in stage 3 (Soon to be available).

This method will keep the target Array untouched and return a copy with the change performed instead.

Upvotes: 18

Ville
Ville

Reputation: 45

To sort a function without mutating the original, simply use .map() before sort to create a copy of the original array:

const originalArr = [1, 45, 3, 21, 6];
const sortedArr = originalArr.map(value => JSON.parse(JSON.stringify(value))).sort((a, b) => a - b);

console.log(sortedArr); // the logged output will be 1,3,6,21,45

The original array has not been modified, but you have a sorted version of it available to you. JSON.parse(JSON.stringify()) make sure it is a deep copy, not a shallow copy.

Upvotes: 0

Niklas
Niklas

Reputation: 49

You can also extend the existing Array functionality. This allows chaining different array functions together.

Array.prototype.sorted = function (compareFn) {
    const shallowCopy = this.slice();
    shallowCopy.sort(compareFn);

    return shallowCopy;
}

[1, 2, 3, 4, 5, 6]
    .filter(x => x % 2 == 0)
    .sorted((l, r) => r - l)
    .map(x => x * 2)

// -> [12, 8, 4]

Same in typescript:

// extensions.ts
Array.prototype.sorted = function (compareFn?: ((a: any, b: any) => number) | undefined) {
    const shallowCopy = this.slice();
    shallowCopy.sort(compareFn);

    return shallowCopy;
}

declare global {
    interface Array<T> {
        sorted(compareFn?: (a: T, b: T) => number): Array<T>;
    }
}

export {}

// index.ts
import 'extensions.ts';


[1, 2, 3, 4, 5, 6]
    .filter(x => x % 2 == 0)
    .sorted((l, r) => r - l)
    .map(x => x * 2)

// -> [12, 8, 4]

Upvotes: 0

Josh
Josh

Reputation: 2694

There's a new tc39 proposal, which adds a toSorted method to Array that returns a copy of the array and doesn't modify the original.

For example:

const sequence = [3, 2, 1];
sequence.toSorted(); // => [1, 2, 3]
sequence; // => [3, 2, 1]

As it's currently in stage 3, it will likely be implemented in browser engines soon, but in the meantime a polyfill is available here or in core-js.

Upvotes: 2

Hamada
Hamada

Reputation: 37

Anyone who wants to do a deep copy (e.g. if your array contains objects) can use:

let arrCopy = JSON.parse(JSON.stringify(arr))

Then you can sort arrCopy without changing arr.

arrCopy.sort((obj1, obj2) => obj1.id > obj2.id)

Please note: this can be slow for very large arrays.

Upvotes: 2

Aditya Agarwal
Aditya Agarwal

Reputation: 753

You can also do this

d = [20, 30, 10]
e = Array.from(d)
e.sort()

This way d will not get mutated.

function sorted(arr) {
  temp = Array.from(arr)
  return temp.sort()
}

//Use it like this
x = [20, 10, 100]
console.log(sorted(x))

Upvotes: 21

zzzzBov
zzzzBov

Reputation: 179046

You can use slice with no arguments to copy an array:

var foo,
    bar;
foo = [3,1,2];
bar = foo.slice().sort();

Upvotes: 58

JaredPar
JaredPar

Reputation: 754715

Try the following

function sortCopy(arr) { 
  return arr.slice(0).sort();
}

The slice(0) expression creates a copy of the array starting at element 0.

Upvotes: 82

Rob W
Rob W

Reputation: 348992

Just copy the array. There are many ways to do that:

function sort(arr) {
  return arr.concat().sort();
}

// Or:
return Array.prototype.slice.call(arr).sort(); // For array-like objects

Upvotes: 259

Related Questions