ソート拡張方法【TypeScript】

ソート関数を書くときにいろいろと機能を追加したくなるが
ベースの関数を再利用する形で拡張する方法を調べてみた

試したことは以下
* 引数を増やしていくパターン（失敗）
* 引数を分割していくパターン（成功？）

# 前提条件

以下の関数への適用を考える
```ts
const array = make_data(100)
array.sort(sort_function)
```

# 引数を増やしていくパターン（失敗）

### ソート関数１

基本のソート関数
```ts
import { describe, it } from 'vitest'

function make_data(N: number) {
  const array = new Array(N)
  for (let i = 0; i < N; ++i) array[i] = Math.floor(Math.random() * N)
  return array
}

function test_sort_basic(a: number, b: number) {
  return a - b
}

describe('引数を増やしていくパターン（失敗）', () => {
  it('1-基本のソート関数', () => {
    const array = make_data(100)
    console.log(array)
    const sort_function = test_sort_basic
    array.sort(sort_function)
    console.log(array)
  })
})
```

実行結果
```ts
[
   4, 51, 28, 62, 10, 61, 79, 53, 24, 27, 86,  6,
  39,  0, 93, 48, 71,  1, 58, 24, 63,  3, 24,  6,
  52, 80, 34, 86,  9, 42, 14, 13, 61, 96,  0, 53,
  92, 33, 57, 35, 90, 13, 73, 20, 96,  2, 18, 92,
  20, 29, 43, 91, 51, 38, 41, 96, 45, 91, 26, 60,
  90, 16, 78, 68, 34,  2, 26, 12, 53, 13, 83, 19,
  45, 15, 89, 64, 18, 78, 63, 96, 92, 13, 98, 98,
  34, 69, 27, 60, 26, 21, 95, 19, 73,  9,  3,  7,
  46, 67, 93,  5
]
[
   0,  0,  1,  2,  2,  3,  3,  4,  5,  6,  6,  7,
   9,  9, 10, 12, 13, 13, 13, 13, 14, 15, 16, 18,
  18, 19, 19, 20, 20, 21, 24, 24, 24, 26, 26, 26,
  27, 27, 28, 29, 33, 34, 34, 34, 35, 38, 39, 41,
  42, 43, 45, 45, 46, 48, 51, 51, 52, 53, 53, 53,
  57, 58, 60, 60, 61, 61, 62, 63, 63, 64, 67, 68,
  69, 71, 73, 73, 78, 78, 79, 80, 83, 86, 86, 89,
  90, 90, 91, 91, 92, 92, 92, 93, 93, 95, 96, 96,
  96, 96, 98, 98
]
```

### ソート関数２

uncurry_sort_basic_2のような変換を毎回作成する必要があり、失敗したような気がする
昇順、降順を追加したソート関数
```ts
import { describe, it } from 'vitest'

function make_data(N: number) {
  const array = new Array(N)
  for (let i = 0; i < N; ++i) array[i] = Math.floor(Math.random() * N)
  return array
}

type type_sort_basic_2 = (asc: boolean, a: number, b: number) => number
const uncurry_sort_basic_2 = (f: type_sort_basic_2) => (asc: boolean) => (a: number, b: number) =>
  f(asc, a, b)
  
function test_sort_basic(a: number, b: number) {
  return a - b
}

function test_sort_basic_2(asc: boolean, a: number, b: number) {
  if (asc) return test_sort_basic(a, b)
  else return test_sort_basic(b, a)
}

describe('引数を増やしていくパターン（失敗）', () => {
  it('2-昇順、降順つきのソート関数', () => {
    const array = make_data(100)
    console.log(array)
    const sort_function = uncurry_sort_basic_2(test_sort_basic_2)(false)
    array.sort(sort_function)
    console.log(array)
  })
})
```

実行結果
```ts
[
  37,  3, 63, 50, 75, 24, 43,  4, 38, 88, 57, 56,
  20, 72, 38, 22, 15, 12, 39, 14, 64, 97, 73,  3,
  54, 39, 78, 74, 65, 28, 74, 51, 12, 60, 26, 80,
  89, 99, 56, 56, 66, 89, 74, 92, 45, 20, 62, 94,
  98, 52, 56,  0, 12,  6, 65,  5, 23, 45, 99, 93,
  22, 19, 80, 31, 28, 84, 58, 92, 54, 53, 64, 28,
  25, 77, 14, 53, 37, 63, 41, 22, 86, 84, 12, 25,
  60,  0, 12, 60,  7, 84, 43, 50,  4,  7, 10, 77,
  35, 68, 88, 44
]
[
  99, 99, 98, 97, 94, 93, 92, 92, 89, 89, 88, 88,
  86, 84, 84, 84, 80, 80, 78, 77, 77, 75, 74, 74,
  74, 73, 72, 68, 66, 65, 65, 64, 64, 63, 63, 62,
  60, 60, 60, 58, 57, 56, 56, 56, 56, 54, 54, 53,
  53, 52, 51, 50, 50, 45, 45, 44, 43, 43, 41, 39,
  39, 38, 38, 37, 37, 35, 31, 28, 28, 28, 26, 25,
  25, 24, 23, 22, 22, 22, 20, 20, 19, 15, 14, 14,
  12, 12, 12, 12, 12, 10,  7,  7,  6,  5,  4,  4,
   3,  3,  0,  0
]
```


# 引数を分割していくパターン（成功？）

### ソート関数２

昇順、降順を追加したソート関数
```ts
import { describe, it } from 'vitest'

function make_data(N: number) {
  const array = new Array(N)
  for (let i = 0; i < N; ++i) array[i] = Math.floor(Math.random() * N)
  return array
}

function test_sort_basic(a: number, b: number) {
  return a - b
}

const test_sort_hof_2 = (asc: boolean) => (a: number, b: number) => {
  if (asc) return test_sort_basic(a, b)
  else return test_sort_basic(b, a)
}

describe('引数を分割していくパターン（成功？）', () => {
  it('2-昇順、降順つきのソート関数', () => {
    const array = make_data(100)
    console.log(array)
    const sort_function = test_sort_hof_2(false)
    array.sort(sort_function)
    console.log(array)
  })
})
```

実行結果
```ts
[
  88, 53, 15, 61, 75, 43,  0, 34, 63, 42, 60, 58,
  31, 89, 84, 76,  8, 60, 96, 33, 46, 47, 88, 71,
  40, 88, 14, 91, 88, 42, 85, 66, 70, 15, 35, 73,
   0, 59, 55, 13, 70,  9, 69, 19,  7, 93, 96, 73,
  27, 55, 36, 14, 77,  8, 20,  9, 54, 97, 74, 71,
  79, 17, 69, 49, 72, 88, 94, 72, 71, 41, 27, 23,
  35, 75, 76, 60, 90, 37, 19, 54, 12, 25, 30, 41,
  55, 74, 68,  9, 36, 25, 75, 57, 93, 39, 14, 14,
  73, 42, 49, 59
]
[
  97, 96, 96, 94, 93, 93, 91, 90, 89, 88, 88, 88,
  88, 88, 85, 84, 79, 77, 76, 76, 75, 75, 75, 74,
  74, 73, 73, 73, 72, 72, 71, 71, 71, 70, 70, 69,
  69, 68, 66, 63, 61, 60, 60, 60, 59, 59, 58, 57,
  55, 55, 55, 54, 54, 53, 49, 49, 47, 46, 43, 42,
  42, 42, 41, 41, 40, 39, 37, 36, 36, 35, 35, 34,
  33, 31, 30, 27, 27, 25, 25, 23, 20, 19, 19, 17,
  15, 15, 14, 14, 14, 14, 13, 12,  9,  9,  9,  8,
   8,  7,  0,  0
]
```

### ソート関数３

昇順、降順と特定の値を先頭か末尾に持っていくを追加したソート関数
```ts
import { describe, it } from 'vitest'

function make_data(N: number) {
  const array = new Array(N)
  for (let i = 0; i < N; ++i) array[i] = Math.floor(Math.random() * N)
  return array
}

function test_sort_basic(a: number, b: number) {
  return a - b
}

const test_sort_hof_2 = (asc: boolean) => (a: number, b: number) => {
  if (asc) return test_sort_basic(a, b)
  else return test_sort_basic(b, a)
}

const test_sort_hof_3 = (pick: number[] | number) => (asc: boolean) => (a: number, b: number) => {
  if (Array.isArray(pick)) {
    const is_pick_a = pick.includes(a)
    const is_pick_b = pick.includes(b)
    if (is_pick_a && is_pick_b && a !== b) return test_sort_hof_2(asc)(a, b)
    else if (is_pick_a) return asc ? -1 : 1
    else if (is_pick_b) return asc ? 1 : -1
    else return test_sort_hof_2(asc)(a, b)
  } else {
    if (a == pick) return asc ? -1 : 1
    else if (b == pick) return asc ? 1 : -1
    else return test_sort_hof_2(asc)(a, b)
  }
}

describe('引数を分割していくパターン（成功？）', () => {
  it('昇順、降順と特定の値１つを先頭か末尾に持っていくソート関数', () => {
    const array = make_data(100)
    console.log(array)
    const sort_function = test_sort_hof_3(7)(true)
    array.sort(sort_function)
    console.log(array)
  })
  it('昇順、降順と特定の値複数を先頭か末尾に持っていくソート関数', () => {
    const array = make_data(100)
    console.log(array)
    const sort_function = test_sort_hof_3([1, 11, 7, 77])(false)
    array.sort(sort_function)
    console.log(array)
  })
})
```

実行結果
特定の値が１つの場合
```ts
[
  38, 90, 70, 23, 79, 27, 49, 43,  0, 89, 18, 30,
  80, 50, 23, 58, 92, 12, 15,  8, 84, 97, 81, 33,
  63, 30, 27, 18, 50, 86, 78, 89, 81, 38, 56, 76,
  76, 94, 85, 21, 71, 56, 25, 10, 93, 64, 47, 67,
  85, 18,  5, 68, 74, 63, 23, 86, 31, 69, 81, 94,
  59, 11, 89, 68, 99, 65, 29, 72, 18, 96,  7, 63,
  13, 38, 50, 25, 45, 33, 96,  3, 65, 49, 91, 42,
  75, 25, 59, 94, 87, 78,  2, 13, 86, 47, 98, 81,
  98, 63, 99, 25
]
[
   7,  0,  2,  3,  5,  8, 10, 11, 12, 13, 13, 15,
  18, 18, 18, 18, 21, 23, 23, 23, 25, 25, 25, 25,
  27, 27, 29, 30, 30, 31, 33, 33, 38, 38, 38, 42,
  43, 45, 47, 47, 49, 49, 50, 50, 50, 56, 56, 58,
  59, 59, 63, 63, 63, 63, 64, 65, 65, 67, 68, 68,
  69, 70, 71, 72, 74, 75, 76, 76, 78, 78, 79, 80,
  81, 81, 81, 81, 84, 85, 85, 86, 86, 86, 87, 89,
  89, 89, 90, 91, 92, 93, 94, 94, 94, 96, 96, 97,
  98, 98, 99, 99
]
```

実行結果
特定の値が複数の場合
```ts
[
  58,  6, 71, 57, 69, 45, 57, 68, 49, 69, 83, 15,
  68, 51, 76, 39, 12, 79, 20, 59, 67,  9, 56, 88,
  61, 62,  6, 67, 51, 39, 11, 49, 48, 13, 45, 99,
  52, 89, 93, 43, 88, 27, 34, 59, 66, 34, 91,  7,
  47, 71, 40, 62, 82, 37, 63, 58, 90, 11, 35, 48,
  91, 79, 54, 55, 76, 45, 43, 32, 72,  3, 41, 83,
  16, 67, 50, 31, 28,  1, 96, 53,  7, 51, 37, 72,
  67, 53, 16, 26, 73, 88, 44, 39, 84, 47, 90, 83,
  77, 37,  3, 83
]
[
  99, 96, 93, 91, 91, 90, 90, 89, 88, 88, 88, 84,
  83, 83, 83, 83, 82, 79, 79, 76, 76, 73, 72, 72,
  71, 71, 69, 69, 68, 68, 67, 67, 67, 67, 66, 63,
  62, 62, 61, 59, 59, 58, 58, 57, 57, 56, 55, 54,
  53, 53, 52, 51, 51, 51, 50, 49, 49, 48, 48, 47,
  47, 45, 45, 45, 44, 43, 43, 41, 40, 39, 39, 39,
  37, 37, 37, 35, 34, 34, 32, 31, 28, 27, 26, 20,
  16, 16, 15, 13, 12,  9,  6,  6,  3,  3, 77, 11,
  11,  7,  7,  1
]
```

# まとめ

基本関数に対して引数を増やすような拡張をする場合は
関数を返すような形で引数を追加していったほうがよさそう？