λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ‘¨‍πŸ’» web.dev/js.ts

[JavaScript] Array.sort λŠ” μ•ˆμ •μ„±μ„ 보μž₯ν• κΉŒ?

by HandHand 2022. 3. 9.

JavaScript 의 sort ν•¨μˆ˜λŠ” μ•ˆμ • μ •λ ¬μΌκΉŒ?

πŸ“Œ μ•ˆμ • μ •λ ¬μ΄λž€?

μ•ˆμ • μ •λ ¬(stable sort) 은 λ™μΌν•œ ν‚€ 값에 λŒ€ν•΄ μž…λ ₯ μˆœμ„œλ₯Ό 보μž₯ν•˜λŠ” μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜μž…λ‹ˆλ‹€.

κ·Έ λ°˜λŒ€μΈ λΆˆμ•ˆμ • μ •λ ¬(unstable sort) 은 μ •λ ¬ ν›„ μž…λ ₯ μˆœμ„œλ₯Ό 보μž₯해주지 μ•ŠμŠ΅λ‹ˆλ‹€.

λŒ€ν‘œμ μΈ μ•ˆμ • μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜μ€ λ‹€μŒκ³Ό 같이 3가지가 μžˆμŠ΅λ‹ˆλ‹€.

1️⃣ 버블 μ •λ ¬ (Bubble Sort)

1

2️⃣ μ‚½μž… μ •λ ¬ (Insertion Sort)

2

3️⃣ 병합 μ •λ ¬ (Merge Sort)

3

κ·Έ μ™Έ 퀡 μ •λ ¬ κ³Ό 선택 μ •λ ¬ 은 λΆˆμ•ˆμ • μ •λ ¬ 의 λŒ€ν‘œμ μΈ μ•Œκ³ λ¦¬μ¦˜μž…λ‹ˆλ‹€.

 

πŸ“Œ JavaScript 의 sort ν•¨μˆ˜

JavaScript 의 sort ν•¨μˆ˜λŠ” μ•ˆμ • μ •λ ¬μΌκΉŒ?

JavaScript 의 sort ν•¨μˆ˜λŠ” κ·Έλ ‡λ‹€λ©΄ μ•ˆμ • μ •λ ¬ μΌκΉŒμš”?

Chrome 70 버전 μ΄μ „μ—μ„œλŠ” 사싀 JavaScript 의 κ΅¬ν˜„ μŠ€νŽ™μƒ μ•ˆμ • 정렬을 κ°•μš”ν•˜μ§€ μ•Šμ•˜λ‹€κ³  ν•©λ‹ˆλ‹€.

λ•Œλ¬Έμ— 각각의 엔진 κ΅¬ν˜„μ²΄λ§ˆλ‹€ μ •λ ¬ 방식이 λ‹€λ₯΄κ²Œ λ˜μ–΄μžˆμœΌλ©° chrome 의 κ²½μš°μ—λŠ” λ°°μ—΄μ˜ 길이에 따라

μ•ˆμ • 정렬일 μˆ˜λ„ 있고 λΆˆμ•ˆμ • 정렬일 μˆ˜λ„ μžˆμ—ˆλ‹€κ³  ν•©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ chrome 70 이후 λ²„μ „λΆ€ν„°λŠ” Array.sort λ₯Ό μ•ˆμ • μ •λ ¬λ‘œ κ΅¬ν˜„ν•˜λ„λ‘ μŠ€νŽ™μ΄ μ§€μ •λ˜μ—ˆκ³ 

그에 따라 λͺ¨λ“  λΈŒλΌμš°μ €μ—μ„œ μ•ˆμ • μ •λ ¬μž„μ„ 보μž₯ν•  수 있게 λ˜μ—ˆλ‹€κ³  ν•©λ‹ˆλ‹€.

Chrome 70 이전 μ‹œμ ˆ μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜

λ‹€μŒμ€ chrome 70 μ΄μ „μ˜ V8 엔진 μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜ μ†ŒμŠ€μ½”λ“œμž…λ‹ˆλ‹€.

// v8/array.js

/**
 * βœ… Array.prototype.sort λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.
 */
DEFINE_METHOD(
  GlobalArray.prototype,
  sort(comparefn) {
    if (!IS_UNDEFINED(comparefn) && !IS_CALLABLE(comparefn)) {
      throw %make_type_error(kBadSortComparisonFunction, comparefn);
    }

    var array = TO_OBJECT(this);
    var length = TO_LENGTH(array.length);
    return InnerArraySort(array, length, comparefn);
  }
);

/**
 * βœ… μ‹€μ œ 정렬을 μˆ˜ν–‰ν•˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.
 */
function InnerArraySort(array, length, comparefn) {
  // In-place QuickSort algorithm.
  // For short (length <= 10) arrays, insertion sort is used for efficiency.

  if (!IS_CALLABLE(comparefn)) {
    comparefn = /** κΈ°λ³Έ 비ꡐ ν•¨μˆ˜ ν• λ‹Ή */
  }

  function InsertionSort(a, from, to) {/** */};

  function QuickSort(a, from, to) {
        // ...

        while (true) {
      // Insertion sort is faster for short arrays.
      if (to - from <= 10) {
        InsertionSort(a, from, to);
        return;
      }

        // ...
  };

  if (length < 2) return array;

  QuickSort(array, 0, num_non_undefined);

  return array;
}

μ½”λ“œλ₯Ό 보면 Array.prototype.sort λŠ” 엔진 λ‚΄λΆ€μ μœΌλ‘œ InnerArraySort ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κ³ 

ν•΄λ‹Ή ν•¨μˆ˜ λ‚΄μ—μ„œ λ°°μ—΄μ˜ 길이가 10 보닀 μž‘μ„ κ²½μš°μ—λŠ” Insertion Sort λ₯Ό μ‚¬μš©ν•˜κ³ 

κ·Έ μ™Έμ—λŠ” Quick Sort λ₯Ό μ‚¬μš©ν•˜λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄ν›„μ˜ v8 엔진은 Timsort μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•΄μ„œ μ •λ ¬ν•©λ‹ˆλ‹€

stable 함을 보μž₯ν•˜κΈ° μœ„ν•΄μ„œ ν˜„μž¬ v8 엔진은 TimSort μ•Œκ³ λ¦¬μ¦˜μ„ μ΄μš©ν•΄ 정렬을 μˆ˜ν–‰ν•©λ‹ˆλ‹€.

λ‹€μŒμ€ ν˜„μž¬ v8 엔진 μ†ŒμŠ€μ˜ μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜ μ½”λ“œμž…λ‹ˆλ‹€.

ArrayTimSort λ₯Ό ν˜ΈμΆœν•΄μ„œ stable ν•œ μ •λ ¬ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

// third_party/v8/builtins/array-sort.tq

ArrayPrototypeSort(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  // ...

  if (len < 2) return obj;

  const sortState: SortState = NewSortState(obj, comparefn, len);
  ArrayTimSort(context, sortState);

  return obj;
}

 

πŸ“Œ Chrome 70 이전에 μ•ˆμ •μ •λ ¬μ„ κ΅¬ν˜„ν–ˆλ˜ 방법

μ•ˆμ • 정렬을 κ΅¬ν•˜λŠ” κ°€μž₯ μ‰¬μš΄ 방법은 각 λ°°μ—΄ μš”μ†Œμ— key 값을 μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ§€μ •ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

μ΄λ ‡κ²Œ ν•˜λ©΄ 두 λ°°μ—΄ μš”μ†Œμ˜ 비ꡐ 킀값이 λ™μΌν•œ κ²½μš°μ—λŠ” 인덱슀둜 λΉ„κ΅ν•˜κΈ° λ•Œλ¬Έμ— μˆœμ„œλ₯Ό 보μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

const stableSort = (arr, compare) =>
  arr
    .map((item, index) => ({ item, index }))
    .sort((a, b) => compare(a.item, b.item) || a.index - b.index)
    .map(({ item }) => item)

 

μœ„ ν•¨μˆ˜λ₯Ό μ΄μš©ν•΄μ„œ 정렬을 μˆ˜ν–‰ν•˜λ©΄ λ‹€μŒκ³Ό 같은 κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€.

 

/** βœ… rating 을 κΈ°μ€€μœΌλ‘œ 정렬을 μˆ˜ν–‰ν•©λ‹ˆλ‹€. */
console.log(
  stableSort(
    [
      { name: "Abby", rating: 12 },
      { name: "Bandit", rating: 13 },
      { name: "Choco", rating: 14 },
      { name: "Daisy", rating: 12 },
      { name: "Elmo", rating: 12 },
      { name: "Falco", rating: 13 },
      { name: "Ghost", rating: 14 },
    ],
    (a, b) => a.rating - b.rating
  )
)[
  /** 그러면 λ‹€μŒκ³Ό 같은 μ •λ ¬ κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€. */
  ({ name: "Abby", rating: 12 },
  { name: "Daisy", rating: 12 },
  { name: "Elmo", rating: 12 },
  { name: "Bandit", rating: 13 },
  { name: "Falco", rating: 13 },
  { name: "Choco", rating: 14 },
  { name: "Ghost", rating: 14 })
]

 

πŸ“Œ μ°Έκ³  자료

Stable Array.prototype.sort

Fast stable sorting algorithm implementation in javascript

λ°˜μ‘ν˜•

πŸ’¬ λŒ“κΈ€