@bgord/tools 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clock.vo.d.ts +25 -0
- package/dist/clock.vo.js +45 -0
- package/dist/dll.service.d.ts +29 -0
- package/dist/dll.service.js +148 -0
- package/dist/email-mask.service.d.ts +6 -0
- package/dist/email-mask.service.js +14 -0
- package/dist/feature-flag.vo.d.ts +11 -0
- package/dist/feature-flag.vo.js +15 -0
- package/dist/filter.vo.d.ts +17 -0
- package/dist/filter.vo.js +21 -0
- package/dist/hour.vo.d.ts +24 -0
- package/dist/hour.vo.js +52 -0
- package/dist/image.vo.d.ts +5 -0
- package/dist/image.vo.js +3 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/leap-year-checker.service.d.ts +4 -0
- package/dist/leap-year-checker.service.js +9 -0
- package/dist/mean.service.d.ts +4 -0
- package/dist/mean.service.js +11 -0
- package/dist/mime-types.vo.d.ts +2 -0
- package/dist/mime-types.vo.js +7 -0
- package/dist/mime.vo.d.ts +1 -1
- package/dist/min-max-scaler.service.d.ts +36 -0
- package/dist/min-max-scaler.service.js +58 -0
- package/dist/minute.vo.d.ts +14 -0
- package/dist/minute.vo.js +34 -0
- package/dist/money.vo.d.ts +24 -0
- package/dist/money.vo.js +64 -0
- package/dist/outlier-detector.service.d.ts +6 -0
- package/dist/outlier-detector.service.js +13 -0
- package/dist/pagination.service.d.ts +58 -0
- package/dist/pagination.service.js +56 -0
- package/dist/percentage.service.d.ts +4 -0
- package/dist/percentage.service.js +11 -0
- package/dist/population-standard-deviation.service.d.ts +4 -0
- package/dist/population-standard-deviation.service.js +16 -0
- package/dist/random.service.d.ts +8 -0
- package/dist/random.service.js +19 -0
- package/dist/reordering.service.d.ts +61 -0
- package/dist/reordering.service.js +121 -0
- package/dist/revision.vo.d.ts +20 -0
- package/dist/revision.vo.js +45 -0
- package/dist/simple-linear-regression.service.d.ts +18 -0
- package/dist/simple-linear-regression.service.js +50 -0
- package/dist/stepper.service.d.ts +23 -0
- package/dist/stepper.service.js +34 -0
- package/dist/streak-calculator.service.d.ts +14 -0
- package/dist/streak-calculator.service.js +27 -0
- package/dist/sum.service.d.ts +3 -0
- package/dist/sum.service.js +5 -0
- package/dist/thousands-separator.service.d.ts +4 -0
- package/dist/thousands-separator.service.js +6 -0
- package/dist/visually-unambiguous-characters-generator.service.d.ts +4 -0
- package/dist/visually-unambiguous-characters-generator.service.js +35 -0
- package/dist/z-score.service.d.ts +8 -0
- package/dist/z-score.service.js +16 -0
- package/package.json +2 -2
- package/readme.md +1 -0
- package/src/api-key.vo.ts +1 -0
- package/src/clock.vo.ts +67 -0
- package/src/dll.service.ts +185 -0
- package/src/email-mask.service.ts +22 -0
- package/src/feature-flag.vo.ts +19 -0
- package/src/filter.vo.ts +38 -0
- package/src/hour.vo.ts +71 -0
- package/src/image.vo.ts +9 -0
- package/src/index.ts +27 -0
- package/src/leap-year-checker.service.ts +11 -0
- package/src/mean.service.ts +14 -0
- package/src/mime-types.vo.ts +9 -0
- package/src/mime.vo.ts +3 -1
- package/src/min-max-scaler.service.ts +94 -0
- package/src/minute.vo.ts +46 -0
- package/src/money.vo.ts +99 -0
- package/src/outlier-detector.service.ts +20 -0
- package/src/package-version.vo.ts +2 -0
- package/src/pagination.service.ts +111 -0
- package/src/percentage.service.ts +17 -0
- package/src/population-standard-deviation.service.ts +21 -0
- package/src/random.service.ts +29 -0
- package/src/rate-limiter.service.ts +1 -3
- package/src/reordering.service.ts +169 -0
- package/src/revision.vo.ts +61 -0
- package/src/simple-linear-regression.service.ts +74 -0
- package/src/stepper.service.ts +50 -0
- package/src/streak-calculator.service.ts +42 -0
- package/src/sum.service.ts +5 -0
- package/src/thousands-separator.service.ts +7 -0
- package/src/visually-unambiguous-characters-generator.service.ts +42 -0
- package/src/z-score.service.ts +24 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
import { DoublyLinkedList, Node } from "./dll.service";
|
|
4
|
+
|
|
5
|
+
export const ReorderingItemPositionValue = z.number().int().min(0);
|
|
6
|
+
|
|
7
|
+
export type ReorderingItemPositionValueType = z.infer<typeof ReorderingItemPositionValue>;
|
|
8
|
+
|
|
9
|
+
export const ReorderingCorrelationId = z.string().min(1);
|
|
10
|
+
|
|
11
|
+
export type ReorderingCorrelationIdType = z.infer<typeof ReorderingCorrelationId>;
|
|
12
|
+
|
|
13
|
+
export const ReorderingItemId = z.uuid();
|
|
14
|
+
|
|
15
|
+
export type ReorderingItemIdType = z.infer<typeof ReorderingItemId>;
|
|
16
|
+
|
|
17
|
+
export const Reordering = z.object({
|
|
18
|
+
correlationId: ReorderingCorrelationId,
|
|
19
|
+
id: ReorderingItemId,
|
|
20
|
+
position: ReorderingItemPositionValue,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type ReorderingType = z.infer<typeof Reordering>;
|
|
24
|
+
|
|
25
|
+
export type WithReorderingPositionValue<T> = T & {
|
|
26
|
+
position: ReorderingItemPositionValueType;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class ReorderingPosition {
|
|
30
|
+
readonly value: ReorderingItemPositionValueType;
|
|
31
|
+
|
|
32
|
+
constructor(value: ReorderingItemPositionValueType) {
|
|
33
|
+
if (!ReorderingItemPositionValue.safeParse(value).success) {
|
|
34
|
+
throw new Error("Position is not a positive integer");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.value = value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
eq(another: ReorderingPosition): boolean {
|
|
41
|
+
return this.value === another.value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class ReorderingItem {
|
|
46
|
+
constructor(
|
|
47
|
+
readonly id: ReorderingItemIdType,
|
|
48
|
+
readonly position: ReorderingPosition,
|
|
49
|
+
) {}
|
|
50
|
+
|
|
51
|
+
eq(anotherItemId: ReorderingItem["id"]): boolean {
|
|
52
|
+
return this.id === anotherItemId;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
enum ReorderingTransferDirection {
|
|
57
|
+
upwards = "upwards",
|
|
58
|
+
downwards = "downwards",
|
|
59
|
+
noop = "noop",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class ReorderingTransfer {
|
|
63
|
+
readonly id: ReorderingItem["id"];
|
|
64
|
+
|
|
65
|
+
readonly to: ReorderingPosition;
|
|
66
|
+
|
|
67
|
+
constructor(config: {
|
|
68
|
+
id: ReorderingItem["id"];
|
|
69
|
+
to: ReorderingItemPositionValueType;
|
|
70
|
+
}) {
|
|
71
|
+
this.id = config.id;
|
|
72
|
+
this.to = new ReorderingPosition(config.to);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getDirection(currentPosition: ReorderingPosition): ReorderingTransferDirection {
|
|
76
|
+
if (this.to.value === currentPosition.value) return ReorderingTransferDirection.noop;
|
|
77
|
+
|
|
78
|
+
if (this.to.value > currentPosition.value) {
|
|
79
|
+
return ReorderingTransferDirection.downwards;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return ReorderingTransferDirection.upwards;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class ReorderingCalculator {
|
|
87
|
+
private dll: DoublyLinkedList<ReorderingItem>;
|
|
88
|
+
|
|
89
|
+
constructor() {
|
|
90
|
+
this.dll = DoublyLinkedList.fromArray<ReorderingItem>([]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static fromArray(ids: ReorderingItem["id"][]) {
|
|
94
|
+
const reordering = new ReorderingCalculator();
|
|
95
|
+
for (const id of ids) {
|
|
96
|
+
reordering.add(id);
|
|
97
|
+
}
|
|
98
|
+
return reordering;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
add(id: ReorderingItem["id"]): ReorderingItem {
|
|
102
|
+
const position = new ReorderingPosition(this.dll.getSize());
|
|
103
|
+
const item = new ReorderingItem(id, position);
|
|
104
|
+
this.dll.append(new Node(item));
|
|
105
|
+
return item;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
delete(id: ReorderingItem["id"]) {
|
|
109
|
+
const item = this.dll.find((x) => x.data.eq(id));
|
|
110
|
+
if (!item) {
|
|
111
|
+
throw new Error("Cannot find Item");
|
|
112
|
+
}
|
|
113
|
+
this.dll.remove(item);
|
|
114
|
+
this.recalculate();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
transfer(transfer: ReorderingTransfer): ReturnType<ReorderingCalculator["read"]> {
|
|
118
|
+
const current = this.dll.find((node) => node.data.eq(transfer.id));
|
|
119
|
+
const target = this.dll.find((node) => node.data.position.eq(transfer.to));
|
|
120
|
+
|
|
121
|
+
if (!current) {
|
|
122
|
+
throw new Error("Cannot find current Item");
|
|
123
|
+
}
|
|
124
|
+
if (!target) {
|
|
125
|
+
throw new Error("Cannot find target Item");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const direction = transfer.getDirection(current.data.position);
|
|
129
|
+
|
|
130
|
+
if (direction === ReorderingTransferDirection.noop) return this.read();
|
|
131
|
+
if (direction === ReorderingTransferDirection.upwards) {
|
|
132
|
+
this.dll.remove(current);
|
|
133
|
+
this.dll.insertBefore(current, target);
|
|
134
|
+
this.recalculate();
|
|
135
|
+
}
|
|
136
|
+
if (direction === ReorderingTransferDirection.downwards) {
|
|
137
|
+
this.dll.remove(current);
|
|
138
|
+
this.dll.insertAfter(current, target);
|
|
139
|
+
this.recalculate();
|
|
140
|
+
}
|
|
141
|
+
return this.read();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
read() {
|
|
145
|
+
const ids = Array.from(this.dll).map((item) => item.data.id);
|
|
146
|
+
const items = Array.from(this.dll).map((item) => item.data);
|
|
147
|
+
return { ids, items };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private recalculate() {
|
|
151
|
+
this.dll = ReorderingCalculator.fromArray(this.read().ids).dll;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export class ReorderingIntegrator {
|
|
156
|
+
static appendPosition(reordering: ReorderingType[]) {
|
|
157
|
+
return function <T extends { id: ReorderingItemIdType }>(item: T): WithReorderingPositionValue<T> {
|
|
158
|
+
return {
|
|
159
|
+
...item,
|
|
160
|
+
position: reordering.find((x) => x.id === item.id)?.position ?? 0,
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static sortByPosition() {
|
|
166
|
+
return (a: WithReorderingPositionValue<unknown>, b: WithReorderingPositionValue<unknown>) =>
|
|
167
|
+
a.position - b.position;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
import { ETag, WeakETag } from "./etags.vo";
|
|
4
|
+
|
|
5
|
+
export const RevisionValue = z.number().int().min(0);
|
|
6
|
+
|
|
7
|
+
export type RevisionValueType = z.infer<typeof RevisionValue>;
|
|
8
|
+
|
|
9
|
+
export class Revision {
|
|
10
|
+
readonly value: RevisionValueType;
|
|
11
|
+
|
|
12
|
+
static initial: RevisionValueType = 0;
|
|
13
|
+
|
|
14
|
+
constructor(value: unknown) {
|
|
15
|
+
const result = RevisionValue.safeParse(value);
|
|
16
|
+
|
|
17
|
+
if (!result.success) throw new InvalidRevisionError();
|
|
18
|
+
|
|
19
|
+
this.value = result.data;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
matches(another: RevisionValueType): boolean {
|
|
23
|
+
return this.value === another;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
validate(another: RevisionValueType): void {
|
|
27
|
+
if (!this.matches(another)) throw new RevisionMismatchError();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
next(): Revision {
|
|
31
|
+
return new Revision(this.value + 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static fromETag(etag: ETag | null): Revision {
|
|
35
|
+
if (!etag) {
|
|
36
|
+
throw new InvalidRevisionError();
|
|
37
|
+
}
|
|
38
|
+
return new Revision(etag.revision);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static fromWeakETag(weakEtag: WeakETag | null): Revision {
|
|
42
|
+
if (!weakEtag) {
|
|
43
|
+
throw new InvalidRevisionError();
|
|
44
|
+
}
|
|
45
|
+
return new Revision(weakEtag.revision);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class RevisionMismatchError extends Error {
|
|
50
|
+
constructor() {
|
|
51
|
+
super();
|
|
52
|
+
Object.setPrototypeOf(this, RevisionMismatchError.prototype);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class InvalidRevisionError extends Error {
|
|
57
|
+
constructor() {
|
|
58
|
+
super();
|
|
59
|
+
Object.setPrototypeOf(this, InvalidRevisionError.prototype);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { RoundToNearest, RoundingStrategy } from "./rounding.service";
|
|
2
|
+
import { Sum } from "./sum.service";
|
|
3
|
+
|
|
4
|
+
export type SLRPairType = { x: number; y: number };
|
|
5
|
+
|
|
6
|
+
export type SLRParamsType = { a: number; b: number };
|
|
7
|
+
|
|
8
|
+
export type SLRPredictionType = number;
|
|
9
|
+
|
|
10
|
+
export class SimpleLinearRegression {
|
|
11
|
+
private readonly rounding: RoundingStrategy = new RoundToNearest();
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly params: SLRParamsType,
|
|
15
|
+
rounding?: RoundingStrategy,
|
|
16
|
+
) {
|
|
17
|
+
this.rounding = rounding ?? this.rounding;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static fromPairs(pairs: SLRPairType[], rounding?: RoundingStrategy) {
|
|
21
|
+
const n = pairs.length;
|
|
22
|
+
|
|
23
|
+
if (n < 2) {
|
|
24
|
+
throw new Error("At least two pairs needed");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const x = pairs.map((pair) => pair.x);
|
|
28
|
+
const y = pairs.map((pair) => pair.y);
|
|
29
|
+
const xx = x.map((x) => x ** 2);
|
|
30
|
+
const xy = pairs.map((pair) => pair.x * pair.y);
|
|
31
|
+
|
|
32
|
+
const sX = Sum.of(x);
|
|
33
|
+
if (sX >= Number.MAX_SAFE_INTEGER) {
|
|
34
|
+
throw new Error("Sum of x values is too big");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sY = Sum.of(y);
|
|
38
|
+
if (sY >= Number.MAX_SAFE_INTEGER) {
|
|
39
|
+
throw new Error("Sum of y values is too big");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sSX = Sum.of(xx);
|
|
43
|
+
if (sSX >= Number.MAX_SAFE_INTEGER) {
|
|
44
|
+
throw new Error("Sum of x squared values is too big");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sXY = Sum.of(xy);
|
|
48
|
+
if (sXY >= Number.MAX_SAFE_INTEGER) {
|
|
49
|
+
throw new Error("Sum of x times y values is too big");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bDenominator = sSX - sX ** 2 / n;
|
|
53
|
+
|
|
54
|
+
if (bDenominator === 0) {
|
|
55
|
+
throw new Error("Unable to create the model");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const b = (sXY - (sX * sY) / n) / bDenominator;
|
|
59
|
+
const a = (sY - b * sX) / n;
|
|
60
|
+
|
|
61
|
+
return new SimpleLinearRegression({ a, b }, rounding);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
predict(x: SLRPairType["x"], strategy?: RoundingStrategy): SLRPredictionType {
|
|
65
|
+
const rounding = strategy ?? this.rounding;
|
|
66
|
+
const prediction = this.params.b * x + this.params.a;
|
|
67
|
+
|
|
68
|
+
return rounding.round(prediction);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
inspect(): SimpleLinearRegression["params"] {
|
|
72
|
+
return this.params;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
type StepType = number;
|
|
2
|
+
|
|
3
|
+
type StepperConfigType = { total: StepType };
|
|
4
|
+
|
|
5
|
+
export class Stepper {
|
|
6
|
+
static readonly DEFAULT_CURRENT = 1;
|
|
7
|
+
|
|
8
|
+
private current: StepType = Stepper.DEFAULT_CURRENT;
|
|
9
|
+
|
|
10
|
+
private readonly total: StepType;
|
|
11
|
+
|
|
12
|
+
constructor(config: StepperConfigType) {
|
|
13
|
+
if (!Number.isInteger(config.total)) {
|
|
14
|
+
throw new Error("Total value is not an integer");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (config.total <= Stepper.DEFAULT_CURRENT) {
|
|
18
|
+
throw new Error("Total value should be greater than one");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.total = config.total;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
continue(): Stepper {
|
|
25
|
+
this.current = Math.min(this.current + 1, this.total);
|
|
26
|
+
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
read() {
|
|
31
|
+
return {
|
|
32
|
+
finished: this.isFinished(),
|
|
33
|
+
raw: { current: this.current, total: this.total },
|
|
34
|
+
formatted: `${this.current}/${this.total}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
reset(): Stepper {
|
|
39
|
+
this.current = Stepper.DEFAULT_CURRENT;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
format(): string {
|
|
44
|
+
return this.read().formatted;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
isFinished(): boolean {
|
|
48
|
+
return this.current === this.total;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { format, isAfter, isEqual, subDays } from "date-fns";
|
|
2
|
+
|
|
3
|
+
type DateType = string;
|
|
4
|
+
|
|
5
|
+
export type StreakType = {
|
|
6
|
+
cutoff: DateType;
|
|
7
|
+
dates: DateType[];
|
|
8
|
+
streak: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class StreakCalculator {
|
|
12
|
+
private readonly cutoff: DateType;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
const today = new Date();
|
|
16
|
+
this.cutoff = StreakCalculator.format(today);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
calculate(_dates: DateType[]): StreakType {
|
|
20
|
+
const dates = Array.from(new Set(_dates));
|
|
21
|
+
|
|
22
|
+
let streak = 0;
|
|
23
|
+
let streakTail = this.cutoff;
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < dates.length; i++) {
|
|
26
|
+
const date = dates[i] as string;
|
|
27
|
+
|
|
28
|
+
if (isAfter(date, streakTail)) continue;
|
|
29
|
+
|
|
30
|
+
if (isEqual(streakTail, date) || isAfter(date, streakTail)) {
|
|
31
|
+
streakTail = StreakCalculator.format(subDays(date, 1));
|
|
32
|
+
streak++;
|
|
33
|
+
} else break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { cutoff: this.cutoff, dates, streak };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static format(date: Parameters<typeof format>[0]): DateType {
|
|
40
|
+
return format(date, "yyyy-MM-dd");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Random } from "./random.service";
|
|
2
|
+
|
|
3
|
+
export class VisuallyUnambiguousCharactersGenerator {
|
|
4
|
+
static chars = [
|
|
5
|
+
"a",
|
|
6
|
+
"b",
|
|
7
|
+
"c",
|
|
8
|
+
"d",
|
|
9
|
+
"e",
|
|
10
|
+
"f",
|
|
11
|
+
"h",
|
|
12
|
+
"i",
|
|
13
|
+
"j",
|
|
14
|
+
"k",
|
|
15
|
+
"m",
|
|
16
|
+
"n",
|
|
17
|
+
"o",
|
|
18
|
+
"p",
|
|
19
|
+
"r",
|
|
20
|
+
"s",
|
|
21
|
+
"t",
|
|
22
|
+
"w",
|
|
23
|
+
"x",
|
|
24
|
+
"y",
|
|
25
|
+
"3",
|
|
26
|
+
"4",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
static generate(length = 1): string {
|
|
30
|
+
return Array.from({ length })
|
|
31
|
+
.map(
|
|
32
|
+
() =>
|
|
33
|
+
VisuallyUnambiguousCharactersGenerator.chars[
|
|
34
|
+
Random.generate({
|
|
35
|
+
min: 0,
|
|
36
|
+
max: VisuallyUnambiguousCharactersGenerator.chars.length - 1,
|
|
37
|
+
})
|
|
38
|
+
],
|
|
39
|
+
)
|
|
40
|
+
.join("");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Mean } from "./mean.service";
|
|
2
|
+
import { PopulationStandardDeviation } from "./population-standard-deviation.service";
|
|
3
|
+
import { RoundToDecimal, RoundingStrategy } from "./rounding.service";
|
|
4
|
+
|
|
5
|
+
export class ZScore {
|
|
6
|
+
private readonly mean: number;
|
|
7
|
+
private readonly standardDeviation: number;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
values: number[],
|
|
11
|
+
private readonly rounding: RoundingStrategy = new RoundToDecimal(2),
|
|
12
|
+
) {
|
|
13
|
+
if (values.length < 2) {
|
|
14
|
+
throw new Error("At least two values are needed");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.mean = Mean.calculate(values);
|
|
18
|
+
this.standardDeviation = PopulationStandardDeviation.calculate(values);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
calculate(value: number): number {
|
|
22
|
+
return this.rounding.round((value - this.mean) / this.standardDeviation);
|
|
23
|
+
}
|
|
24
|
+
}
|