xref: /aosp_15_r20/external/perfetto/ui/src/base/comparison_utils.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2023 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {isString} from './object_utils';
16
17export type ComparisonFn<X> = (a: X, b: X) => number;
18
19export type SortDirection = 'DESC' | 'ASC';
20
21// Having a comparison function of type S and a getter that returns value of
22// type S from value of type T, values of type T can be compared.
23export function comparingBy<T, S>(
24  getter: (t: T) => S,
25  comparison: ComparisonFn<S>,
26): ComparisonFn<T> {
27  return (x, y) => {
28    return comparison(getter(x), getter(y));
29  };
30}
31
32export function withDirection<T>(
33  comparison: ComparisonFn<T>,
34  sortDirection?: SortDirection,
35): ComparisonFn<T> {
36  if (sortDirection !== 'DESC') {
37    return comparison;
38  }
39
40  return (x, y) => {
41    return comparison(y, x);
42  };
43}
44
45export type SortableValue =
46  | string
47  | number
48  | bigint
49  | null
50  | Uint8Array
51  | undefined;
52
53function columnTypeKind(a: SortableValue): number {
54  if (a === undefined) {
55    return 0;
56  }
57  if (a === null) {
58    return 1;
59  }
60  if (typeof a === 'number') {
61    return 2;
62  }
63  if (isString(a)) {
64    return 3;
65  }
66  // a instanceof Uint8Array
67  return 4;
68}
69
70export function compareUniversal(a: SortableValue, b: SortableValue): number {
71  if (a === undefined && b === undefined) {
72    return 0;
73  }
74  if (a === null && b === null) {
75    return 0;
76  }
77  if (typeof a === 'number' && typeof b === 'number') {
78    return a - b;
79  }
80  if (isString(a) && isString(b)) {
81    return a.localeCompare(b);
82  }
83  if (a instanceof Uint8Array && b instanceof Uint8Array) {
84    // Do the lexicographical comparison
85    for (let i = 0; i < a.length && i < b.length; i++) {
86      if (a[i] < b[i]) {
87        return -1;
88      }
89      if (a[i] > b[i]) {
90        return 1;
91      }
92    }
93    // No discrepancies found in the common prefix, compare lengths of arrays.
94    return a.length - b.length;
95  }
96
97  // Values are of different kinds, compare the kinds
98  return columnTypeKind(a) - columnTypeKind(b);
99}
100