ソート合成方法【TypeScript】

ソート関数を書くときにいろいろと機能を追加する方法は前回調べた
複数のソート関数を合成して複数列でのソートする方法を調べてみた

# 前提条件
以下の関数への適用を考える
```ts
const array = make_data(10)
array.sort(sort_function)
```

前回の記事
[ソート拡張方法【TypeScript】](https://qiita.com/kuangzhixitian3/items/873c9d5b0dbabab81d37)

# 合成前の関数

### 1つのKeyに対するソート

```ts
import { describe, it } from 'vitest'

type SortFunctionType = (a: any, b: any) => number

const get_value_with_key =
  <T>(key: keyof T) =>
  (input_data: T) =>
    input_data[key]

interface SampleDataType {
  index: number
  word: string
  price: number
}

function make_data(N: number) {
  const array = new Array<SampleDataType>(N)
  for (let i = 0; i < N; ++i) {
    array[i] = {
      index: i,
      word: crypto.randomUUID(),
      price: Math.floor(Math.random() * N)
    }
  }
  return array
}

const apply_argument =
  <T>(f: (input_data: T) => any) =>
  (g: SortFunctionType) =>
  (a: T, b: T) => {
    return g(f(a), f(b))
  }
  
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('indexでのソート1', () => {
    const array = make_data(10)
    console.log(array)
    const sort_function = apply_argument(get_value_with_key<SampleDataType>('index'))(
      test_sort_basic
    )
    array.sort(sort_function)
    console.log(array)
  })
  it('indexでのソート2', () => {
    const array = make_data(10)
    console.log(array)
    const sort_funct = apply_argument(get_value_with_key<SampleDataType>('index'))(
      test_sort_hof_3([1, 7])(false)
    )
    array.sort(sort_funct)
    console.log(array)
  })
})
```

実行結果
indexでのソート2の場合
```ts
[
  { index: 0, word: '3c209f94-cf11-48d1-91d0-3db9bf9f75c5', price: 9 },
  { index: 1, word: '5f5a2a0a-68fa-45cf-af70-cef174325326', price: 4 },
  { index: 2, word: 'bd10e9a1-3b38-4250-80af-fd921e67325d', price: 0 },
  { index: 3, word: '99bb565f-dced-4a25-8d01-edb9bc8e150a', price: 0 },
  { index: 4, word: 'ae273f24-3cc9-4a0f-9218-7d9dfc532d3a', price: 8 },
  { index: 5, word: '19d58de8-086a-4e15-bcae-7b94dc81450f', price: 0 },
  { index: 6, word: 'a8af936f-e3fa-452b-82c2-fa713574c4d4', price: 8 },
  { index: 7, word: '543e346c-48ab-4e10-a897-31e059dfb910', price: 1 },
  { index: 8, word: 'af7c93ff-08ec-4878-80aa-30a760572c7e', price: 4 },
  { index: 9, word: 'a63b3556-a80a-45be-90b6-84a3a70f23d4', price: 8 }
]
[
  { index: 9, word: 'a63b3556-a80a-45be-90b6-84a3a70f23d4', price: 8 },
  { index: 8, word: 'af7c93ff-08ec-4878-80aa-30a760572c7e', price: 4 },
  { index: 6, word: 'a8af936f-e3fa-452b-82c2-fa713574c4d4', price: 8 },
  { index: 5, word: '19d58de8-086a-4e15-bcae-7b94dc81450f', price: 0 },
  { index: 4, word: 'ae273f24-3cc9-4a0f-9218-7d9dfc532d3a', price: 8 },
  { index: 3, word: '99bb565f-dced-4a25-8d01-edb9bc8e150a', price: 0 },
  { index: 2, word: 'bd10e9a1-3b38-4250-80af-fd921e67325d', price: 0 },
  { index: 0, word: '3c209f94-cf11-48d1-91d0-3db9bf9f75c5', price: 9 },
  { index: 7, word: '543e346c-48ab-4e10-a897-31e059dfb910', price: 1 },
  { index: 1, word: '5f5a2a0a-68fa-45cf-af70-cef174325326', price: 4 }
]
```

# 関数合成

### 複数のKeyによるソート

```ts
import { describe, it } from 'vitest'

type SortFunctionType = (a: any, b: any) => number

const get_value_with_key =
  <T>(key: keyof T) =>
  (input_data: T) =>
    input_data[key]

interface SampleDataType {
  index: number
  word: string
  price: number
}

function make_data(N: number) {
  const array = new Array<SampleDataType>(N)
  for (let i = 0; i < N; ++i) {
    array[i] = {
      index: i,
      word: crypto.randomUUID(),
      price: Math.floor(Math.random() * N)
    }
  }
  return array
}

const apply_argument =
  <T>(f: (input_data: T) => any) =>
  (g: SortFunctionType) =>
  (a: T, b: T) => {
    return g(f(a), f(b))
  }

const compose_function_for_sort =
  (...f_array: SortFunctionType[]) =>
  <T>(a: T, b: T) => {
    let i
    for (i = 0; i < f_array.length - 1; ++i) {
      if (f_array[i](a, b) !== 0) {
        return f_array[i](a, b)
      }
    }

    return f_array[i](a, b)
  }

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('複数のKeyによるソート', () => {
    const array = make_data(10)
    console.log(array)
    const sub_sort_function_price = apply_argument(get_value_with_key<SampleDataType>('price'))(
      test_sort_basic
    )
    const sub_sort_function_index = apply_argument(get_value_with_key<SampleDataType>('index'))(
      test_sort_hof_2(false)
    )
    const sort_function = compose_function_for_sort(
      sub_sort_function_price,
      sub_sort_function_index
    )
    array.sort(sort_function)
    console.log(array)
  })
})
```

実行結果
```ts
[
  { index: 0, word: '35dd21a2-6ad9-400b-8c57-b3b781ed2200', price: 9 },
  { index: 1, word: '8a752c0f-77ab-4956-bc08-5acebaf0af8e', price: 2 },
  { index: 2, word: 'afdbd5e1-a3a6-44cb-9153-3f2d097e045c', price: 0 },
  { index: 3, word: 'b7b93018-7222-4141-afba-119682d89fea', price: 9 },
  { index: 4, word: '8ae2f84b-af4f-4b7b-90ba-c181995a4cb7', price: 0 },
  { index: 5, word: '3e5ce29e-0ab0-4ec4-85cf-087027fcc14b', price: 1 },
  { index: 6, word: '8ed97234-a5fc-4a98-bcdf-9de83b0beca9', price: 9 },
  { index: 7, word: 'eddc80ee-bbd0-4900-a77f-55e274fe3949', price: 9 },
  { index: 8, word: '6497156a-33ea-4f8e-9cd8-ee517863a26a', price: 0 },
  { index: 9, word: '4409548b-f36d-4983-95b2-2f12e41e4f65', price: 3 }
]
[
  { index: 8, word: '6497156a-33ea-4f8e-9cd8-ee517863a26a', price: 0 },
  { index: 4, word: '8ae2f84b-af4f-4b7b-90ba-c181995a4cb7', price: 0 },
  { index: 2, word: 'afdbd5e1-a3a6-44cb-9153-3f2d097e045c', price: 0 },
  { index: 5, word: '3e5ce29e-0ab0-4ec4-85cf-087027fcc14b', price: 1 },
  { index: 1, word: '8a752c0f-77ab-4956-bc08-5acebaf0af8e', price: 2 },
  { index: 9, word: '4409548b-f36d-4983-95b2-2f12e41e4f65', price: 3 },
  { index: 7, word: 'eddc80ee-bbd0-4900-a77f-55e274fe3949', price: 9 },
  { index: 6, word: '8ed97234-a5fc-4a98-bcdf-9de83b0beca9', price: 9 },
  { index: 3, word: 'b7b93018-7222-4141-afba-119682d89fea', price: 9 },
  { index: 0, word: '35dd21a2-6ad9-400b-8c57-b3b781ed2200', price: 9 }
]
```