简体   繁体   中英

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?

You need to copy the array before you sort it. One way with es6:

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

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

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

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

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

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.

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

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

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

Anyone who wants to do a deep copy (eg 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.

Update - Array.prototype.toSorted() proposal

The Array.prototype.toSorted(compareFn) -> Array is a method which 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 returns a copy of it with the change performed instead.

I use Object.assign() for most of my copies:

var copyArray = Object.assign([], originalArray).sort();

However, after looking through the OP comments, I researched a bit of deep copying and turns out Object.assign not only performs a shallow copy, but also only selects enumerable and own properties (as answered in this post ).

Try this to sort the numbers. This does not mutate the original array.

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

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 .

I think that my answer is a bit too late but if someone come across this issue again the solution may be useful.

I can propose yet another approach with a native function which returns a sorted array .

This code still mutates the original object but instead of native behaviour this implementation returns a sorted array.

// Remember that it is not recommended to extend build-in prototypes 
// or even worse override native functions.  
// You can create a seperate function if you like

// You can specify any name instead of "sorted" (Python-like)

// Check for existence of the method in prototype
if (typeof Array.prototype.sorted == "undefined") {
  // If it does not exist you provide your own method
  Array.prototype.sorted = function () {
    Array.prototype.sort.apply(this, arguments);
    return this;
  };
}

This way of solving the problem was ideal in my situation.

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]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM