已发布 上游基线 bf46254 原文 ↗ 在 GitHub 编辑

Order

使用可定制的工具在 TypeScript 中比较、排序并管理值的先后顺序。

Order 模块提供了一种比较值并确定其先后顺序的方式。它定义了一个接口 Order<A>,表示用于比较两个类型为 A 的值的单个函数。该函数返回 -101,分别表示第一个值小于、等于或大于第二个值。

Order 的基本结构如下:

interface Order<A> {
  (first: A, second: A): -1 | 0 | 1
}

使用内置的 Order

Order 模块为常见数据类型提供了若干内置的比较器:

Order说明
String用于比较字符串。
Number用于比较数字。
BigInt用于比较大整数。
Date用于比较 Date 对象。

示例(使用内置的比较器)

import { Order } from "effect"

console.log(Order.String("apple", "banana"))
// Output: -1, as "apple" < "banana"

Order.String("apple", "banana") // => -1

console.log(Order.Number(1, 1))
// Output: 0, as 1 = 1

Order.Number(1, 1) // => 0

console.log(Order.BigInt(2n, 1n))
// Output: 1, as 2n > 1n

Order.BigInt(2n, 1n) // => 1

对数组排序

你可以使用这些比较器对数组排序。Array 模块提供了一个 sort 函数,它会在不改变原数组的前提下对数组排序。

示例(使用 Order 对数组排序)

import { Order, Array } from "effect"

const strings = ["b", "a", "d", "c"]

const result = Array.sort(strings, Order.String)

console.log(strings) // Original array remains unchanged
strings // => ["b", "a", "d", "c"]

console.log(result) // Sorted array
result // => ["a", "b", "c", "d"]

你也可以把 Order 作为比较器传给 JavaScript 原生的 Array.sort 方法,但请记住,这会修改原数组。

示例(在原生 Array.prototype.sort 中使用 Order

import { Order } from "effect"

const strings = ["b", "a", "d", "c"]

strings.sort(Order.String) // Modifies the original array

console.log(strings)
strings // => ["a", "b", "c", "d"]

派生 Order

对于更复杂的数据结构,你可能需要自定义的排序规则。Order 模块允许你通过 Order.mapInput 函数,从已有的 Order 实例派生出新的实例。

示例(为对象创建自定义的 Order)

假设你有一个 Person 对象列表,想按姓名升序对它们排序。为此,你可以创建一个自定义的 Order

import { Order } from "effect"

// Define the Person interface
interface Person {
  readonly name: string
  readonly age: number
}

// Create a custom order to sort Person objects by name in ascending order
//
//      ┌─── Order<Person>
//      ▼
const byName = Order.mapInput(Order.String, (person: Person) => person.name)

// "Alice" sorts before "Bob"
byName({ name: "Alice", age: 25 }, { name: "Bob", age: 30 }) // => -1

Order.mapInput 函数接收两个参数:

  1. 你想用作基础的现有 Order(这里是 Order.String,用于比较字符串)。
  2. 一个从你的数据结构中提取排序所用值的函数(这里是 (person: Person) => person.name)。

定义好自定义的 Order 后,你就可以用它来排序 Person 对象组成的数组:

示例(使用自定义的 Order 排序对象)

import { Order, Array } from "effect"

// Define the Person interface
interface Person {
  readonly name: string
  readonly age: number
}

// Create a custom order to sort Person objects by name in ascending order
const byName = Order.mapInput(Order.String, (person: Person) => person.name)

const persons: ReadonlyArray<Person> = [
  { name: "Charlie", age: 22 },
  { name: "Alice", age: 25 },
  { name: "Bob", age: 30 },
]

// Sort persons array using the custom order
const sortedPersons = Array.sort(persons, byName)

console.log(sortedPersons)

sortedPersons // => [{ name: "Alice", age: 25 }, { name: "Bob", age: 30 }, { name: "Charlie", age: 22 }]

组合 Order

Order 模块允许你组合多个 Order 实例,以构建复杂的排序规则。当需要按多个属性排序时,这很有用。

示例(按多个条件排序)

假设你有一个人员列表,其中每个人用一个带有 nameage 的对象表示。你希望先按姓名排序,对于姓名相同的人再按年龄排序。

import { Order, Array } from "effect"

// Define the Person interface
interface Person {
  readonly name: string
  readonly age: number
}

// Create an Order to sort people by their names in ascending order
const byName = Order.mapInput(Order.String, (person: Person) => person.name)

// Create an Order to sort people by their ages in ascending order
const byAge = Order.mapInput(Order.Number, (person: Person) => person.age)

// Combine orders to sort by name, then by age
const byNameAge = Order.combine(byName, byAge)

const result = Array.sort(
  [
    { name: "Bob", age: 20 },
    { name: "Alice", age: 18 },
    { name: "Bob", age: 18 },
  ],
  byNameAge,
)

console.log(result)
/*
Output:
[
  { name: 'Alice', age: 18 }, // Sorted by name
  { name: 'Bob', age: 18 },   // Sorted by age within the same name
  { name: 'Bob', age: 20 }
]
*/

result // => [{ name: "Alice", age: 18 }, { name: "Bob", age: 18 }, { name: "Bob", age: 20 }]

其他实用函数

Order 模块还提供了用于常见比较操作的额外函数,让你更容易处理有先后顺序的值。

反转顺序

Order.flip 会反转比较的顺序。如果你有一个用于升序的 Order,把它反转就会得到降序。

示例(反转 Order)

import { Order } from "effect"

const ascendingOrder = Order.Number

const descendingOrder = Order.flip(ascendingOrder)

console.log(ascendingOrder(1, 3))
// Output: -1 (1 < 3 in ascending order)

ascendingOrder(1, 3) // => -1

console.log(descendingOrder(1, 3))
// Output: 1 (1 > 3 in descending order)

descendingOrder(1, 3) // => 1

比较值

这些函数让你可以在值之间执行简单的比较:

API说明
isLessThan检查一个值是否严格小于另一个值。
isGreaterThan检查一个值是否严格大于另一个值。
isLessThanOrEqualTo检查一个值是否小于或等于另一个值。
isGreaterThanOrEqualTo检查一个值是否大于或等于另一个值。

示例(使用比较函数)

import { Order } from "effect"

console.log(Order.isLessThan(Order.Number)(1, 2))
// Output: true (1 < 2)

Order.isLessThan(Order.Number)(1, 2) // => true

console.log(Order.isGreaterThan(Order.Number)(5, 3))
// Output: true (5 > 3)

Order.isGreaterThan(Order.Number)(5, 3) // => true

console.log(Order.isLessThanOrEqualTo(Order.Number)(2, 2))
// Output: true (2 <= 2)

Order.isLessThanOrEqualTo(Order.Number)(2, 2) // => true

console.log(Order.isGreaterThanOrEqualTo(Order.Number)(4, 4))
// Output: true (4 >= 4)

Order.isGreaterThanOrEqualTo(Order.Number)(4, 4) // => true

查找最小值和最大值

Order.minOrder.max 函数会根据给定的排序规则,返回两个值中的最小值或最大值。

示例(查找最小值和最大值)

import { Order } from "effect"

console.log(Order.min(Order.Number)(3, 1))
// Output: 1 (1 is the minimum)

Order.min(Order.Number)(3, 1) // => 1

console.log(Order.max(Order.Number)(5, 8))
// Output: 8 (8 is the maximum)

Order.max(Order.Number)(5, 8) // => 8

限制取值范围

Order.clamp 会把值限制在给定范围内。如果值超出该范围,它会被调整到最近的边界。

示例(把数字限制在某个范围内)

import { Order } from "effect"

// Define a function to clamp numbers between 20 and 30
const clampNumbers = Order.clamp(Order.Number)({
  minimum: 20,
  maximum: 30,
})

// Value 26 is within the range [20, 30], so it remains unchanged
console.log(clampNumbers(26))
clampNumbers(26) // => 26

// Value 10 is below the minimum bound, so it is clamped to 20
console.log(clampNumbers(10))
clampNumbers(10) // => 20

// Value 40 is above the maximum bound, so it is clamped to 30
console.log(clampNumbers(40))
clampNumbers(40) // => 30

检查值是否在范围内

Order.isBetween 检查一个值是否落在指定的闭区间内。

示例(检查数字是否落在某个范围内)

import { Order } from "effect"

// Create a function to check if numbers are between 20 and 30
const betweenNumbers = Order.isBetween(Order.Number)({
  minimum: 20,
  maximum: 30,
})

// Value 26 falls within the range [20, 30], so it returns true
console.log(betweenNumbers(26))
betweenNumbers(26) // => true

// Value 10 is below the minimum bound, so it returns false
console.log(betweenNumbers(10))
betweenNumbers(10) // => false

// Value 40 is above the maximum bound, so it returns false
console.log(betweenNumbers(40))
betweenNumbers(40) // => false