Files
MWClash/lib/common/indexing.dart
chen08209 4370df6a61 Add sqlite store
Optimize android quick action

Optimize backup and restore

Optimize more details
2026-01-26 18:51:44 +08:00

261 lines
6.3 KiB
Dart

import 'dart:math';
class Indexing {
static const String digits =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
static const String integerZero = 'a0';
static const String smallestInteger = 'A00000000000000000000000000';
static Indexing? _instance;
Indexing._internal();
factory Indexing() {
_instance ??= Indexing._internal();
return _instance!;
}
int _getIntegerLength(String head) {
if (head.compareTo('a') >= 0 && head.compareTo('z') <= 0) {
return head.codeUnitAt(0) - 'a'.codeUnitAt(0) + 2;
} else if (head.compareTo('A') >= 0 && head.compareTo('Z') <= 0) {
return 'Z'.codeUnitAt(0) - head.codeUnitAt(0) + 2;
} else {
throw Exception('Invalid order key head: $head');
}
}
bool _validateInteger(String integer) {
if (integer.length != _getIntegerLength(integer[0])) {
throw Exception('Invalid integer part of order key: $integer');
}
return true;
}
String? _incrementInteger(String x) {
_validateInteger(x);
String head = x[0];
List<String> digs = x.substring(1).split('');
bool carry = true;
for (int i = digs.length - 1; carry && i >= 0; i--) {
int d = digits.indexOf(digs[i]) + 1;
if (d == digits.length) {
digs[i] = '0';
} else {
digs[i] = digits[d];
carry = false;
}
}
if (carry) {
if (head == 'Z') {
return 'a0';
}
if (head == 'z') {
return null;
}
String h = String.fromCharCode(head.codeUnitAt(0) + 1);
if (h.compareTo('a') > 0) {
digs.add('0');
} else {
digs.removeLast();
}
return h + digs.join('');
} else {
return head + digs.join('');
}
}
String? _decrementInteger(String x) {
_validateInteger(x);
String head = x[0];
List<String> digs = x.substring(1).split('');
bool borrow = true;
for (int i = digs.length - 1; borrow && i >= 0; i--) {
int d = digits.indexOf(digs[i]) - 1;
if (d == -1) {
digs[i] = digits[digits.length - 1];
} else {
digs[i] = digits[d];
borrow = false;
}
}
if (borrow) {
if (head == 'a') {
return 'Z${digits[digits.length - 1]}';
}
if (head == 'A') {
return null;
}
String h = String.fromCharCode(head.codeUnitAt(0) - 1);
if (h.compareTo('Z') < 0) {
digs.add(digits[digits.length - 1]);
} else {
digs.removeLast();
}
return h + digs.join('');
} else {
return head + digs.join('');
}
}
String _midpoint(String a, String? b) {
if (b != null && a.compareTo(b) >= 0) {
throw Exception(
'Second order key must be greater than the first: $a, $b',
);
}
if (a.isNotEmpty && a[a.length - 1] == '0' ||
(b != null && b.isNotEmpty && b[b.length - 1] == '0')) {
throw Exception('Trailing zeros are not allowed: $a, $b');
}
if (b != null) {
int n = 0;
while ((n < a.length ? a[n] : '0') == b[n]) {
n++;
}
if (n > 0) {
return b.substring(0, n) +
_midpoint(
a.substring(min(n, a.length)),
b.substring(min(n, b.length)),
);
}
}
int digitA = (a.isNotEmpty) ? digits.indexOf(a[0]) : 0;
int digitB = (b != null && b.isNotEmpty)
? digits.indexOf(b[0])
: digits.length;
if (digitB - digitA > 1) {
int midDigit = (digitA + digitB + 1) ~/ 2;
return digits[midDigit];
} else {
if (b != null && b.length > 1) {
return b.substring(0, 1);
} else {
return digits[digitA] +
_midpoint(a.isNotEmpty ? a.substring(1) : '', null);
}
}
}
String _getIntegerPart(String key) {
int integerPartLength = _getIntegerLength(key[0]);
if (integerPartLength > key.length) {
throw Exception('Invalid order key: $key');
}
return key.substring(0, integerPartLength);
}
bool _validateOrderKey(String key) {
if (key == smallestInteger) {
throw Exception('Invalid order key: $key');
}
String i = _getIntegerPart(key);
String f = key.substring(i.length);
if (f.isNotEmpty && f[f.length - 1] == '0') {
throw Exception('Invalid order key: $key');
}
return true;
}
String? generateKeyBetween(String? a, String? b) {
if (a != null) {
_validateOrderKey(a);
}
if (b != null) {
_validateOrderKey(b);
}
if (a != null && b != null && a.compareTo(b) >= 0) {
throw Exception(
'Second order key must be greater than the first: $a, $b',
);
}
if (a == null && b == null) {
return integerZero;
}
if (a == null) {
b = b!;
String ib = _getIntegerPart(b);
String fb = b.substring(ib.length);
if (ib == smallestInteger) {
return ib + _midpoint('', fb);
}
return ib.compareTo(b) < 0 ? ib : _decrementInteger(ib);
}
if (b == null) {
String ia = _getIntegerPart(a);
String fa = a.substring(ia.length);
String? i = _incrementInteger(ia);
return i ?? ia + _midpoint(fa, null);
}
String ia = _getIntegerPart(a);
String fa = a.substring(ia.length);
String ib = _getIntegerPart(b);
String fb = b.substring(ib.length);
if (ia == ib) {
return ia + _midpoint(fa, fb);
}
String? i = _incrementInteger(ia);
return (i == null || i.compareTo(b) < 0) ? i : ia + _midpoint(fa, null);
}
List<String?> generateNKeysBetween(String? a, String? b, int n) {
if (n <= 0) {
return [];
}
if (n == 1) {
return [generateKeyBetween(a, b)];
}
if (b == null) {
String? c = generateKeyBetween(a, b);
List<String?> result = [c];
for (int i = 1; i < n; i++) {
c = generateKeyBetween(c, b);
result.add(c);
}
return result;
}
if (a == null) {
String? c = generateKeyBetween(a, b);
List<String?> result = [c];
for (int i = 1; i < n; i++) {
c = generateKeyBetween(a, c);
result.add(c);
}
return result.reversed.toList();
}
int mid = n ~/ 2;
String? c = generateKeyBetween(a, b);
return generateNKeysBetween(a, c, mid)
.followedBy([c])
.followedBy(generateNKeysBetween(c, b, n - mid - 1))
.toList();
}
}
final indexing = Indexing();