magic-utils-yonava 1.0.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.
@@ -0,0 +1,129 @@
1
+ import { expect, test } from 'vitest';
2
+
3
+ import { delta } from '.';
4
+
5
+ test('deepDelta standard', () => {
6
+ const yona = {
7
+ name: 'yona',
8
+ sex: 'm',
9
+ residence: 'amherst',
10
+ school: {
11
+ name: 'umass',
12
+ year: 'senior',
13
+ info: {
14
+ major: ['cs'],
15
+ minor: [],
16
+ start: '2023',
17
+ },
18
+ },
19
+ test: {
20
+ hello: 'world',
21
+ removeMe: {
22
+ removeMe: 'removeMe',
23
+ removeMe2: {},
24
+ },
25
+ test2: {
26
+ test3: 'secret',
27
+ },
28
+ },
29
+ };
30
+
31
+ const dila = {
32
+ name: 'dila',
33
+ sex: 'f',
34
+ residence: 'amherst',
35
+ school: {
36
+ name: 'umass',
37
+ year: 'junior',
38
+ info: {
39
+ major: ['cs', 'japanese'],
40
+ minor: [],
41
+ start: '2022',
42
+ },
43
+ },
44
+ test: {
45
+ hello: 'world',
46
+ removeMe: {
47
+ removeMe: 'removeMe',
48
+ removeMe2: {},
49
+ },
50
+ test2: {
51
+ test3: 'secret changed',
52
+ },
53
+ },
54
+ };
55
+
56
+ const expected = {
57
+ name: 'dila',
58
+ sex: 'f',
59
+ school: {
60
+ year: 'junior',
61
+ info: {
62
+ major: ['cs', 'japanese'],
63
+ start: '2022',
64
+ },
65
+ },
66
+ test: {
67
+ test2: {
68
+ test3: 'secret changed',
69
+ },
70
+ },
71
+ };
72
+
73
+ const result = delta(yona, dila);
74
+ expect(result).toEqual(expected);
75
+ });
76
+
77
+ test('deepDelta works if new object has more keys', () => {
78
+ const yona = {
79
+ name: 'yona',
80
+ };
81
+
82
+ const dila = {
83
+ name: 'dila',
84
+ favoriteColor: 'blue',
85
+ anime: {
86
+ naruto: 'meh',
87
+ bleach: 'good',
88
+ },
89
+ };
90
+
91
+ const expected = {
92
+ name: 'dila',
93
+ favoriteColor: 'blue',
94
+ anime: {
95
+ naruto: 'meh',
96
+ bleach: 'good',
97
+ },
98
+ };
99
+
100
+ const result = delta(yona, dila);
101
+
102
+ expect(result).toEqual(expected);
103
+ });
104
+
105
+ test('deepDelta works on fields that are null or undefined', () => {
106
+ const colorGetter = () => 'blue';
107
+
108
+ const yona = {
109
+ name: 'yona',
110
+ favoriteColor: {
111
+ color: colorGetter,
112
+ },
113
+ sbahn: 's1',
114
+ };
115
+
116
+ const dila = {
117
+ name: 'dila',
118
+ favoriteColor: null,
119
+ sbahn: undefined,
120
+ };
121
+
122
+ const expected = {
123
+ name: 'dila',
124
+ sbahn: undefined,
125
+ };
126
+
127
+ const result = delta(yona, dila);
128
+ expect(result).toEqual(expected);
129
+ });
@@ -0,0 +1,48 @@
1
+ // recursively compare two objects and return the delta
2
+
3
+ // -----------------
4
+
5
+ const isObj = (obj: any) =>
6
+ Object.prototype.toString.call(obj) === '[object Object]';
7
+
8
+ /**
9
+ * gets the delta between two objects
10
+ *
11
+ * @param oldObject
12
+ * @param newObject
13
+ * @returns an object with only the changes, the values are the new values
14
+ */
15
+ export const delta = (
16
+ oldObject: Record<any, any>,
17
+ newObject: Record<any, any>,
18
+ ) => {
19
+ const output: Record<any, any> = {};
20
+
21
+ if (!oldObject) return newObject;
22
+ if (!newObject) return null;
23
+
24
+ const oldObjectKeys = Object.keys(oldObject);
25
+ const newObjectKeys = Object.keys(newObject);
26
+
27
+ for (const key of newObjectKeys) {
28
+ if (!oldObjectKeys.includes(key)) {
29
+ output[key] = newObject[key];
30
+ }
31
+ }
32
+
33
+ for (const key of oldObjectKeys) {
34
+ if (isObj(oldObject[key])) {
35
+ const diffObj = delta(oldObject[key], newObject[key]);
36
+ if (diffObj) output[key] = diffObj;
37
+ continue;
38
+ }
39
+
40
+ if (Array.isArray(oldObject[key])) {
41
+ if (JSON.stringify(oldObject[key]) !== JSON.stringify(newObject[key]))
42
+ output[key] = newObject[key];
43
+ continue;
44
+ } else if (oldObject[key] !== newObject[key]) output[key] = newObject[key];
45
+ }
46
+
47
+ return Object.keys(output).length ? output : null;
48
+ };
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { deepMerge } from './deepMerge';
4
+
5
+ describe('deepMerge', () => {
6
+ it('merges flat objects with rightmost priority', () => {
7
+ const result = deepMerge({ a: 1 }, { a: 2, b: 3 });
8
+ expect(result).toEqual({ a: 2, b: 3 });
9
+ });
10
+
11
+ it('merges nested objects deeply', () => {
12
+ const result = deepMerge({ a: { b: 1, c: 2 } }, { a: { b: 3, d: 4 } });
13
+ expect(result).toEqual({ a: { b: 3, c: 2, d: 4 } });
14
+ });
15
+
16
+ it('overwrites arrays instead of merging them', () => {
17
+ const result = deepMerge(
18
+ { a: [1, 2], b: { c: [3, 4] } },
19
+ { a: [5], b: { c: [6] } },
20
+ );
21
+ expect(result).toEqual({ a: [5], b: { c: [6] } });
22
+ });
23
+
24
+ it('handles primitive overwrites correctly', () => {
25
+ const result = deepMerge({ a: { b: 1 } }, { a: 5 });
26
+ expect(result).toEqual({ a: 5 });
27
+ });
28
+
29
+ it('returns non-object when passed a primitive last', () => {
30
+ const result = deepMerge({ a: 1 }, 5);
31
+ expect(result).toBe(5);
32
+ });
33
+
34
+ it('handles merging with undefined and null safely', () => {
35
+ const result = deepMerge({ a: { b: 1 } }, undefined, null, { a: { c: 2 } });
36
+ expect(result).toEqual({ a: { b: 1, c: 2 } });
37
+ });
38
+
39
+ it('handles empty inputs', () => {
40
+ const result = deepMerge();
41
+ expect(result).toEqual({});
42
+ });
43
+ });
44
+
45
+ describe('deepMerge - special cases', () => {
46
+ it('overwrites Date objects', () => {
47
+ const date1 = new Date('2020-01-01');
48
+ const date2 = new Date('2021-01-01');
49
+
50
+ const result = deepMerge({ date: date1 }, { date: date2 });
51
+ expect(result.date).toBe(date2);
52
+ });
53
+
54
+ it('overwrites Map objects', () => {
55
+ const map1 = new Map([['a', 1]]);
56
+ const map2 = new Map([['b', 2]]);
57
+
58
+ const result = deepMerge({ map: map1 }, { map: map2 });
59
+ expect(result.map).toBe(map2);
60
+ });
61
+
62
+ it('overwrites Set objects', () => {
63
+ const set1 = new Set([1, 2]);
64
+ const set2 = new Set([3, 4]);
65
+
66
+ const result = deepMerge({ set: set1 }, { set: set2 });
67
+ expect(result.set).toBe(set2);
68
+ });
69
+
70
+ it('overwrites functions', () => {
71
+ const fn1 = () => 1;
72
+ const fn2 = () => 2;
73
+
74
+ const result = deepMerge({ fn: fn1 }, { fn: fn2 });
75
+ expect(result.fn).toBe(fn2);
76
+ expect(result.fn()).toBe(2);
77
+ });
78
+
79
+ it('overwrites class instances', () => {
80
+ class Example {
81
+ x = 1;
82
+ }
83
+ const obj1 = { instance: new Example() };
84
+ const obj2 = { instance: { y: 2 } };
85
+
86
+ const result = deepMerge(obj1, obj2);
87
+ expect(result.instance).toEqual({ y: 2 });
88
+ });
89
+ });
@@ -0,0 +1,37 @@
1
+ export const isPlainObject = (
2
+ obj: any,
3
+ ): obj is Record<string | number | symbol, unknown> =>
4
+ !!obj &&
5
+ typeof obj === 'object' &&
6
+ Object.getPrototypeOf(obj) === Object.prototype;
7
+
8
+ /**
9
+ * Deeply merges multiple objects. Properties from later objects overwrite those from earlier ones.
10
+ * Non-object values (including arrays) are replaced, not merged.
11
+ *
12
+ * @param {...any[]} objects - Objects to merge. The rightmost object's properties take precedence.
13
+ * @returns {any} - A new deeply merged object.
14
+ *
15
+ * @example
16
+ * const result = deepMerge(
17
+ * { a: 1, b: { c: 2 } },
18
+ * { b: { d: 3 }, e: 4 }
19
+ * );
20
+ * // result: { a: 1, b: { c: 2, d: 3 }, e: 4 }
21
+ */
22
+ export const deepMerge = (...objects: any[]): any =>
23
+ objects.reduce((acc, obj) => {
24
+ if (obj === undefined || obj === null) return acc;
25
+
26
+ if (!isPlainObject(obj)) return obj;
27
+
28
+ Object.keys(obj).forEach((key) => {
29
+ if (isPlainObject(obj[key]) && isPlainObject(acc[key])) {
30
+ acc[key] = deepMerge(acc[key], obj[key]);
31
+ } else {
32
+ acc[key] = obj[key];
33
+ }
34
+ });
35
+
36
+ return acc;
37
+ }, {});
package/src/fps.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { onMounted, onUnmounted, ref } from "vue";
2
+
3
+ export function useFPS() {
4
+ const fps = ref(0);
5
+ const frameTime = ref(0);
6
+ const slowFrameCount = ref(0);
7
+ const slowFrameRatio = ref(0);
8
+
9
+ let frameId: number;
10
+ let intervalId: NodeJS.Timeout;
11
+
12
+ let lastMeasure = performance.now();
13
+ let lastFrame = performance.now();
14
+ let frames = 0;
15
+ let slowFrames = 0;
16
+
17
+ const SLOW_FRAME_THRESHOLD = 33.3; // ms
18
+
19
+ const update = () => {
20
+ const now = performance.now();
21
+ const delta = now - lastFrame;
22
+ frameTime.value = delta;
23
+ lastFrame = now;
24
+
25
+ if (delta > SLOW_FRAME_THRESHOLD) {
26
+ slowFrames++;
27
+ }
28
+
29
+ frames++;
30
+ frameId = requestAnimationFrame(update);
31
+ };
32
+
33
+ const measure = () => {
34
+ const now = performance.now();
35
+ const delta = now - lastMeasure;
36
+
37
+ fps.value = Math.round((frames * 1000) / delta);
38
+ slowFrameCount.value = slowFrames;
39
+ slowFrameRatio.value = frames > 0 ? slowFrames / frames : 0;
40
+
41
+ frames = 0;
42
+ slowFrames = 0;
43
+ lastMeasure = now;
44
+ };
45
+
46
+ onMounted(() => {
47
+ lastFrame = performance.now();
48
+ lastMeasure = performance.now();
49
+ frameId = requestAnimationFrame(update);
50
+ intervalId = setInterval(measure, 500);
51
+ });
52
+
53
+ onUnmounted(() => {
54
+ cancelAnimationFrame(frameId);
55
+ clearInterval(intervalId);
56
+ });
57
+
58
+ return {
59
+ fps,
60
+ frameTime,
61
+ slowFrameCount,
62
+ slowFrameRatio,
63
+ };
64
+ }
@@ -0,0 +1,36 @@
1
+
2
+ export const gcd = (a: number, b: number): number => (b ? gcd(b, a % b) : a);
3
+
4
+ export const isNullOrUnd = (input: any) =>
5
+ input === null || input === undefined;
6
+
7
+ export const isFraction = (input: string) => {
8
+ const fraction = input.trim().split('/').filter(Boolean);
9
+ if (fraction.length !== 2) return false;
10
+ const [numerator, denominator] = fraction.map(Number);
11
+ if (isNullOrUnd(numerator) || isNullOrUnd(denominator)) return false;
12
+ return true;
13
+ };
14
+
15
+ export const decimalToFraction = (decimalInput: number) => {
16
+ const decimal = Math.round(decimalInput * 1e10) / 1e10;
17
+ const len = decimal.toString().length - 2;
18
+ let denominator = 10 ** len;
19
+ let numerator = decimal * denominator;
20
+ const divisor = gcd(numerator, denominator);
21
+ numerator /= divisor;
22
+ denominator /= divisor;
23
+ if (denominator === 1) return numerator.toString();
24
+ return `${numerator}/${denominator}`;
25
+ };
26
+
27
+ /**
28
+ * @param fractionInput a string representing a fraction
29
+ * @returns the decimal representation of the fraction or undefined if the input is not a fraction
30
+ */
31
+ export const fractionToDecimal = (fractionInput: string) => {
32
+ if (!isFraction(fractionInput)) return;
33
+ const fraction = fractionInput.split('/');
34
+ const [numerator, denominator] = fraction.map(Number);
35
+ return numerator / denominator;
36
+ };
package/src/hashing.ts ADDED
@@ -0,0 +1,9 @@
1
+ export const djb2Hasher = (str: string) => {
2
+ let hash = 5381;
3
+ for (let i = 0; i < str.length; i++) {
4
+ hash = (hash << 5) + hash + str.charCodeAt(i);
5
+ }
6
+ return hash >>> 0;
7
+ };
8
+
9
+ export type DJB2Hash = ReturnType<typeof djb2Hasher>;
package/src/id.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * generates a new, random, id
3
+ * @example generateId() // 'abc123'
4
+ */
5
+ export const generateId = () => Math.random().toString(36).substring(2, 9);
@@ -0,0 +1,49 @@
1
+
2
+
3
+ type LocalStorageGetter = (...args: any[]) => string;
4
+ type LocalStorageRecord = Record<string, string | LocalStorageGetter>;
5
+
6
+ /**
7
+ * a registry for all localStorage keys this application uses
8
+ */
9
+ export const localKeys = {
10
+ /** nodes in graph product */
11
+ nodes: (key: string) => `nodes-${key}` as const,
12
+ /** edges in graph product */
13
+ edges: (key: string) => `edges-${key}` as const,
14
+ /** graph product simulation speed */
15
+ simulationPlaybackSpeed: 'simulation-playback-speed',
16
+ /** graph theme set by user - {@link Graph.preferredTheme} */
17
+ preferredTheme: 'preferred-theme',
18
+ } as const satisfies LocalStorageRecord;
19
+
20
+ /**
21
+ * all return values of localStorage are, by default, string.
22
+ * this type allows string to be narrowed to types such as 'true' | 'false'
23
+ */
24
+ type TypeOverride = {};
25
+
26
+ type LocalObj = typeof localKeys;
27
+
28
+ /**
29
+ * @example
30
+ * type T = TypeOrReturnType<number> // number
31
+ * type TFunc = TypeOrReturnType<() => number> // number
32
+ */
33
+ type TypeOrReturnType<T> = T extends (...args: any[]) => infer U ? U : T;
34
+
35
+ type LocalKeys = TypeOrReturnType<LocalObj[keyof LocalObj]>;
36
+ type LocalType<T extends LocalKeys> = T extends keyof TypeOverride
37
+ ? TypeOverride[T]
38
+ : string;
39
+
40
+ /**
41
+ * perform **type safe** localStorage actions
42
+ */
43
+ export const local = {
44
+ get: <T extends LocalKeys>(key: T) => localStorage.getItem(key),
45
+ set: <T extends LocalKeys, K extends LocalType<T>>(key: T, value: K) =>
46
+ localStorage.setItem(key, value),
47
+ remove: <T extends LocalKeys>(key: T) => localStorage.removeItem(key),
48
+ clear: localStorage.clear,
49
+ };
@@ -0,0 +1,57 @@
1
+ import { describe, expect, test } from 'vitest';
2
+
3
+ import {
4
+ average,
5
+ gcd,
6
+ getPrimeFactors,
7
+ lowestPrimeFactor,
8
+ roundToNearestN,
9
+ } from './math';
10
+
11
+ describe('roundToNearestN', () => {
12
+ test('rounds a number to the nearest multiple of n', () => {
13
+ const roundToNearest5 = roundToNearestN(5);
14
+ expect(roundToNearest5(13)).toBe(15);
15
+ expect(roundToNearest5(12)).toBe(10);
16
+ });
17
+ });
18
+
19
+ describe('getPrimeFactors', () => {
20
+ test('returns the prime factors of a number', () => {
21
+ expect(getPrimeFactors(12)).toEqual([2, 2, 3]);
22
+ expect(getPrimeFactors(15)).toEqual([3, 5]);
23
+ });
24
+
25
+ test('edge case: 1', () => {
26
+ expect(getPrimeFactors(1)).toEqual([]);
27
+ });
28
+ });
29
+
30
+ describe('lowestPrimeFactor', () => {
31
+ test('returns the lowest prime factor of a number', () => {
32
+ expect(lowestPrimeFactor(12)).toBe(2);
33
+ expect(lowestPrimeFactor(15)).toBe(3);
34
+ });
35
+
36
+ test('edge case: 1', () => {
37
+ expect(lowestPrimeFactor(1)).toBe(1);
38
+ });
39
+ });
40
+
41
+ describe('gcd', () => {
42
+ test('returns the greatest common divisor of two numbers', () => {
43
+ expect(gcd(12, 15)).toBe(3);
44
+ expect(gcd(12, 18)).toBe(6);
45
+ });
46
+ });
47
+
48
+ describe('average', () => {
49
+ test('returns the average of a list of numbers', () => {
50
+ expect(average([1, 2, 3, 4, 5])).toBe(3);
51
+ expect(average([1, 2, 3, 4, 5, 6])).toBe(3.5);
52
+ });
53
+
54
+ test('edge case: empty list', () => {
55
+ expect(average([])).toBe(0);
56
+ });
57
+ });
package/src/math.ts ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * the golden ratio constant.
3
+ * {@link} https://en.wikipedia.org/wiki/Golden_ratio
4
+ */
5
+ export const GOLDEN_RATIO = 1.618;
6
+
7
+ /**
8
+ * rounds a number to the nearest multiple of another number
9
+ *
10
+ * @param n the number to round
11
+ * @param nearest the number to round to
12
+ * @returns the rounded number
13
+ * @example const roundToNearest5 = roundToNearestN(5);
14
+ * roundToNearest5(13) // 15
15
+ * roundToNearest5(12) // 10
16
+ */
17
+ export const roundToNearestN = (nearest: number) => (n: number) => {
18
+ return Math.round(n / nearest) * nearest;
19
+ };
20
+
21
+ /**
22
+ * get the prime factors of a number
23
+ *
24
+ * @param num the number to get the prime factors of
25
+ * @returns the prime factors of the number
26
+ * @example getPrimeFactors(12) // [2, 2, 3]
27
+ * getPrimeFactors(15) // [3, 5]
28
+ */
29
+ export const getPrimeFactors = (num: number) => {
30
+ const factors: number[] = [];
31
+ let divisor = 2;
32
+
33
+ while (num >= 2) {
34
+ if (num % divisor === 0) {
35
+ factors.push(divisor);
36
+ num = num / divisor;
37
+ } else {
38
+ divisor++;
39
+ }
40
+ }
41
+
42
+ return factors;
43
+ };
44
+
45
+ /**
46
+ * get the lowest prime factor of a number
47
+ *
48
+ * @param num the number to get the lowest prime factor of
49
+ * @returns the lowest prime factor of the number
50
+ * @example
51
+ * lowestPrimeFactor(12) // 12 = 2 * 2 * 3, min(2, 2, 3) = 2
52
+ * lowestPrimeFactor(15) // 15 = 3 * 5, min(3, 5) = 3
53
+ */
54
+ export const lowestPrimeFactor = (num: number) => {
55
+ if (num === 1) return 1; // 1 has no prime factors
56
+ return Math.min(...getPrimeFactors(num));
57
+ };
58
+
59
+ /**
60
+ * get the greatest common divisor of two numbers.
61
+ * {@link} https://en.wikipedia.org/wiki/Euclidean_algorithm
62
+ * {@link} https://en.wikipedia.org/wiki/Greatest_common_divisor
63
+ *
64
+ * @param a the first number
65
+ * @param b the second number
66
+ * @returns the greatest common divisor of the two numbers
67
+ * @example gcd(12, 15) // 3
68
+ * gcd(12, 18) // 6
69
+ */
70
+ export const gcd = (a: number, b: number): number => {
71
+ if (b === 0) return a;
72
+ return gcd(b, a % b);
73
+ };
74
+
75
+ /**
76
+ * check if two numbers are within a certain tolerance of each other
77
+ */
78
+ export const within = (tolerance: number) => (a: number, b: number) => {
79
+ return Math.abs(a - b) <= tolerance;
80
+ };
81
+
82
+ /**
83
+ * get the average of an array of numbers
84
+ *
85
+ * @param arr the array of numbers to average
86
+ * @returns the average of the array
87
+ * @example average([1, 2, 3]) // 2
88
+ * average([1, 2, 3, 4]) // 2.5
89
+ */
90
+ export const average = (arr: number[]) => {
91
+ if (arr.length === 0) return 0;
92
+ return arr.reduce((acc, val) => acc + val, 0) / arr.length;
93
+ };
@@ -0,0 +1,41 @@
1
+ import type { RemoveAnyArray } from '../types';
2
+
3
+ /**
4
+ * taking some data that may be a plain value or a function that returns that value
5
+ *
6
+ * @template T - the type of the value
7
+ * @template K - the type of the arguments necessary in order to resolve the value
8
+ */
9
+ export type MaybeGetter<T, K extends any[] = []> = T | ((...arg: K) => T);
10
+
11
+ /**
12
+ * the value of a MaybeGetter
13
+ */
14
+ export type UnwrapMaybeGetter<T> =
15
+ T extends MaybeGetter<infer U, infer _> ? U : T;
16
+
17
+ /**
18
+ * the parameters of a MaybeGetter
19
+ */
20
+ export type MaybeGetterParams<T> = RemoveAnyArray<
21
+ T extends MaybeGetter<infer _, infer K> ? K : []
22
+ >;
23
+
24
+ /**
25
+ * unwraps a MaybeGetter into a value of type T.
26
+ *
27
+ * @param value - the value to unwrap
28
+ * @param args - the arguments to pass to the getter if the value is a getter
29
+ * @returns T, which is UnwrapMaybeGetter<MaybeGetter<T, K>>
30
+ * @example getValue(5) // 5
31
+ * getValue(() => 5) // 5
32
+ */
33
+ export const getValue = <T, K extends any[]>(
34
+ value: MaybeGetter<T, K>,
35
+ ...args: K
36
+ ) => {
37
+ if (typeof value === 'function') {
38
+ return (value as (...args: K) => T)(...args);
39
+ }
40
+ return value;
41
+ };
package/src/mouse.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
3
+ */
4
+ export const MOUSE_BUTTONS = {
5
+ left: 0,
6
+ middle: 1,
7
+ right: 2,
8
+ } as const;
package/src/random.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * given two numbers, this function returns a random number between them (inclusive)
3
+ *
4
+ * @param min - the lowest number
5
+ * @param max - the highest number
6
+ * @returns a random number between min and max
7
+ */
8
+ export const getRandomInRange = (min: number, max: number) =>
9
+ Math.round(Math.random() * (max - min) + min);
10
+
11
+ export const getRandomPointOnCanvas = (
12
+ canvas: HTMLCanvasElement,
13
+ buffer = 50,
14
+ ) => ({
15
+ x: getRandomInRange(buffer, canvas.width - buffer),
16
+ y: getRandomInRange(buffer, canvas.height - buffer),
17
+ });
18
+
19
+ /**
20
+ * get a random element from an array
21
+ *
22
+ * @param array
23
+ * @returns random element from given array
24
+ */
25
+ export const getRandomElement = <T>(array: ArrayLike<T>) => {
26
+ return array[Math.floor(Math.random() * array.length)];
27
+ };