-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathordering.ts
More file actions
154 lines (136 loc) · 4.18 KB
/
ordering.ts
File metadata and controls
154 lines (136 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
* Defines the {@linkcode Ordering} interface that wraps around a
* {@linkcode Comparator} and provides convenience methods.
* @packageDocumentation
*/
import {Comparator, reversed, join, keyed} from './comparator';
/**
* An ordering is a wrapper around a {@linkcode Comparator} that makes it easy
* to create more comparators based on the wrapped one.
*
* To make an ordering, call {@linkcode ordering} with a comparator. To
* retrieve the wrapped comparator from an ordering, access its `compare`
* property.
*
* An ordering can also be directly passed as a comparator to an array's `sort`
* method, without any unwrapping.
*
* @example
* ```ts
* const orderingByNumber = ordering(byNumber);
* orderingByNumber.compare === byNumber;
*
* const a = [1, 10, 3, 8, 5, 6, 7, 4, 9, 2, 0];
* a.sort(orderingByNumber);
* ```
*/
export interface Ordering<Element> extends Comparator<Element> {
/**
* The underlying comparator that drives this ordering.
*/
compare: Comparator<Element>;
/**
* Returns an ordering that is a reversal of the current one.
*
* @example
* ```ts
* const byNumberDescending = ordering(byNumber).reverse();
*
* const a = [1, 10, 3, 8, 5, 6, 7, 4, 9, 2, 0];
* a.sort(byNumberDescending);
* ```
*/
reversed(): Ordering<Element>;
/**
* Joins the given comparators or orderings into a new ordering so that when
* this ordering's comparison results in an equality, the next one is used as
* a fallback.
*
* @example
* ```ts
* const orderingByName = ordering(byString).on<Person>(p => p.name);
* const orderingByAge = ordering(byNumber).on<Person>(p => p.age);
*
* const orderingByNameThenByAge = orderingByName.join(orderingByAge);
* ```
*/
join(
...comparatorsOrOrderings: Array<Comparator<Element> | Ordering<Element>>
): Ordering<Element>;
/**
* Derives an `Ordering<T>` out of the current `Ordering<Element>` given a
* transformation function from `T` to `Element`.
*
* A common use case is to compare objects based on a specific property, given
* an existing comparator that already knows how to compare the type of that
* property.
*
* @example
* ```ts
* interface Person {
* name: string;
* age: number;
* }
* const byName = ordering(byString).on<Person>(p => p.name);
* const byAge = ordering(byNumber).on<Person>(p => p.age);
* ```
*/
on<T>(f: (data: T) => Element): Ordering<T>;
}
const methods = {
reversed<Element>(this: Ordering<Element>): Ordering<Element> {
const result = ordering<Element>(reversed(this.compare));
// Cache the current ordering as the reverse of the reversed ordering.
result.reversed = () => this;
return result;
},
join<Element>(
this: Ordering<Element>,
...comparatorsOrOrderings: Array<Comparator<Element> | Ordering<Element>>
): Ordering<Element> {
if (comparatorsOrOrderings.length === 0) {
return this;
}
const comparators = comparatorsOrOrderings.map(toComparator);
return ordering<Element>(join(this.compare, ...comparators));
},
on<Element, T>(
this: Ordering<Element>,
f: (data: T) => Element,
): Ordering<T> {
return ordering<T>(keyed(f, this.compare));
},
};
/**
* Wraps the given comparator in an {@linkcode Ordering}.
*/
export function ordering<Element>(
compare: Comparator<Element>,
): Ordering<Element> {
// Initialize main object.
const f = (function (a: Element, b: Element): number {
// Forward to the underlying comparator.
return f.compare(a, b);
} as any) as Ordering<Element>;
// Attach the underlying comparator.
f.compare = compare;
// Name it after the comparator it wraps.
Object.defineProperty?.(f, 'name', {
get: () => `ordering(${nameOfFunction(f.compare)})`,
});
// Copy methods over.
for (const methodName of Object.keys(methods) as Array<
keyof typeof methods
>) {
f[methodName] = methods[methodName] as any;
}
return f;
}
function toComparator<Element>(
f: Ordering<Element> | Comparator<Element>,
): Comparator<Element> {
return 'compare' in f ? f.compare : f;
}
function nameOfFunction(f: Function & {displayName?: string}): string {
return f?.displayName ?? f.name;
}