data-structure-typed/src/utils/utils.ts
2023-08-16 01:35:16 +08:00

561 lines
17 KiB
TypeScript

import _ from 'lodash';
import type {AnyFunction, JSONObject, JSONSerializable} from './types';
export function randomText(length: number) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
export const uuidV4 = function () {
return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, function (c) {
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
export class IncrementId {
private _id: string;
private readonly _prefix: string;
constructor(prefix?: string) {
this._prefix = prefix ? prefix : '';
this._id = this._prefix + '0';
}
getId() {
const {_id, _prefix} = this;
if (!_id) {
this._id = _prefix + '0';
} else {
const idNumStr = _id.substr(_prefix.length, _id.length - _prefix.length);
const newIdNum = parseInt(idNumStr, 10) + 1;
this._id = _prefix + newIdNum.toString();
}
return this._id;
}
}
export function incrementId(prefix?: string) {
const _prefix = prefix ? prefix : '';
let _id = _prefix + '0';
return function id() {
const idNumStr = _id.substr(_prefix.length, _id.length - _prefix.length);
const newIdNum = parseInt(idNumStr, 10) + 1;
_id = _prefix + newIdNum.toString();
return _id;
};
}
export const getValue = <T, K extends keyof T>(obj: T, names: K[]): Array<T[K]> => names.map(i => obj[i]);
export const isObject = (object: string | JSONObject | boolean | AnyFunction | number) => object != null && typeof object === 'object';
export const looseEqual = (a: any, b: any): boolean => a == b;
export const strictEqual = (a: any, b: any): boolean => a === b;
export const strictObjectIsEqual = (a: any, b: any): boolean => Object.is(a, b);
export const deepObjectStrictEqual = (object1: JSONSerializable, object2: JSONSerializable) => {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
const val1 = object1[key];
const val2 = object2[key];
const areObjects = isObject(val1) && isObject(val2);
if (
areObjects && !deepObjectStrictEqual(val1, val2) ||
!areObjects && val1 !== val2
) {
return false;
}
}
return true;
};
export function reverseColor(oldColor: string) {
const oldColorTemp = '0x' + oldColor.replace(/#/g, '');
const str = '000000' + (0xFFFFFF - Number(oldColorTemp)).toString(16);
return '#' + str.substring(str.length - 6, str.length);
}
export const isSameStructure = (objA: unknown, objB: unknown) => {
const objATraversable = objA as JSONSerializable;
const objBTraversable = objB as JSONSerializable;
const objAKeys = Object.keys(objATraversable);
const objBKeys = Object.keys(objBTraversable);
let isSame = true;
if (objAKeys.length !== objBKeys.length) {
return isSame = false;
} else {
objAKeys.forEach((i) => {
if (!objBKeys.includes(i)) {
return isSame = false;
}
});
return isSame;
}
};
export const isLeafParent = (obj: JSONObject) => {
let isLeaf = true;
Object.values(obj).forEach(value => {
if (typeof value === 'object' && value instanceof Array) {
value.forEach(item => {
if (typeof item === 'object') {
return false;
}
});
return isLeaf = true;
}
if (!['string', 'boolean', 'number', 'undefined', 'function'].includes(typeof value) && (value !== null)) {
return isLeaf = false;
}
});
return isLeaf;
};
export const addDays = (date: Date, days: number): Date => {
date.setDate(date.getDate() + days);
return date;
};
export class WaitManager {
private _time30 = 20000;
private readonly _nXSpeed: number = 1;
constructor(nXSpeed?: number) {
if (nXSpeed === undefined) nXSpeed = 1;
this._nXSpeed = nXSpeed;
}
private _time1 = 1000;
get time1(): number {
return this._time1 / this._nXSpeed;
}
private _time2 = 2000;
get time2(): number {
return this._time2 / this._nXSpeed;
}
private _time3 = 3000;
get time3(): number {
return this._time3 / this._nXSpeed;
}
private _time4 = 4000;
get time4(): number {
return this._time4 / this._nXSpeed;
}
private _time10 = 10000;
get time10(): number {
return this._time10 / this._nXSpeed;
}
private _time20 = 20000;
get time20(): number {
return this._time20 / this._nXSpeed;
}
get time50(): number {
return this._time30 / this._nXSpeed;
}
private _time60 = 60000;
get time60(): number {
return this._time60 / this._nXSpeed;
}
private _cusTime = 1000;
get cusTime(): number {
return this._cusTime / this._nXSpeed;
}
set cusTime(v: number) {
this._cusTime = v;
}
}
export const wait = async (ms: number, resolveValue?: any) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const finalResolveValue = resolveValue || true;
resolve(finalResolveValue);
}, ms);
});
};
export function extractValue<Item>(data: { key: string, value: Item }[]) {
let result: Item[] = [];
if (data && data.length > 0) {
result = data.map(item => item.value);
}
return result;
}
export function keyValueToArray<Item>(data: { [key: string]: Item }) {
const itemArray: Array<Item> = [];
const keys = Object.keys(data);
for (const i of keys) {
itemArray.push({...data[i], _id: i});
}
return itemArray;
}
export function minuted(time: number) {
const minutes = Math.floor(time / 60000).toString();
const seconds = Math.floor((time % 60000) / 1000).toString().padStart(2, '0');
return `${minutes}:${seconds}`;
}
export function randomDate(start?: Date, end?: Date, specificProbabilityStart?: Date, specificProbability?: number) {
if (!start) start = new Date('1970-1-1');
if (!end) end = new Date();
if (specificProbabilityStart) {
if (!specificProbability) specificProbability = 0.5;
if (Math.random() <= specificProbability) {
return new Date(specificProbabilityStart.getTime() + Math.random() * (end.getTime() - specificProbabilityStart.getTime()));
}
}
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
}
export const capitalizeWords = (str: string) => str.replace(/(?:^|\s)\S/g, (a: string) => a.toUpperCase());
export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
export const comparerArray = <T>(otherArray: T[], limitKeys?: string[]) => {
return function (current: T) {
return otherArray.filter(function (other: T) {
if (!limitKeys) {
return _.isEqual(current, other);
} else {
// TODO
}
}).length == 0;
};
};
export const onlyInA = <T>(a: T[], b: T[]) => a.filter(comparerArray(b));
export const onlyInB = <T>(a: T[], b: T[]) => b.filter(comparerArray(a));
export const diffAB = <T>(a: T[], b: T[]) => onlyInA(a, b).concat(onlyInB(a, b));
export class StringUtil {
// camelCase
static toCamelCase(str: string) {
return _.camelCase(str);
}
// snake_case
static toSnakeCase(str: string) {
return _.snakeCase(str);
}
// PascalCase
static toPascalCase(str: string) {
return _.startCase(_.camelCase(str)).replace(/ /g, '');
}
// CONSTANT_CASE
static toConstantCase(str: string) {
return _.upperCase(str).replace(/ /g, '_');
}
// kebab-case
static toKebabCase(str: string) {
return _.kebabCase(str);
}
// lowercase
static toLowerCase(str: string) {
return _.lowerCase(str).replace(/ /g, '');
}
// Title Case
static toTitleCase(str: string) {
return _.startCase(_.camelCase(str));
}
// Sentence case
static toSentenceCase(str: string) {
return _.upperFirst(_.lowerCase(str));
}
// path/case
static toPathCase(str: string) {
return _.lowerCase(str).replace(/ /g, '/');
}
// dot.case
static toDotCase(str: string) {
return _.lowerCase(str).replace(/ /g, '.');
}
}
export type CaseType =
'camel'
| 'snake'
| 'pascal'
| 'constant'
| 'kebab'
| 'lower'
| 'title'
| 'sentence'
| 'path'
| 'dot';
export const deepKeysConvert = (obj: any, toType?: CaseType): any => {
const _toType = toType || 'snake';
if (Array.isArray(obj)) {
return obj.map(v => deepKeysConvert(v, _toType));
} else if (obj !== null && obj.constructor === Object) {
return Object.keys(obj).reduce(
(result, key) => {
let newKey = '';
switch (_toType) {
case 'camel':
newKey = StringUtil.toCamelCase(key);
break;
case 'snake':
newKey = StringUtil.toSnakeCase(key);
break;
case 'pascal':
newKey = StringUtil.toPascalCase(key);
break;
case 'constant':
newKey = StringUtil.toConstantCase(key);
break;
case 'kebab':
newKey = StringUtil.toKebabCase(key);
break;
case 'lower':
newKey = StringUtil.toLowerCase(key);
break;
case 'title':
newKey = StringUtil.toTitleCase(key);
break;
case 'sentence':
newKey = StringUtil.toSentenceCase(key);
break;
case 'path':
newKey = StringUtil.toPathCase(key);
break;
case 'dot':
newKey = StringUtil.toDotCase(key);
break;
default:
newKey = StringUtil.toDotCase(key);
break;
}
return {
...result,
[newKey]: deepKeysConvert(obj[key], _toType),
};
},
{},
);
}
return obj;
};
export const deepRemoveByKey = (obj: any, keysToBeRemoved: string[]) => {
const result = _.transform(obj, function (result: JSONSerializable, value: any, key: string) {
if (_.isObject(value)) {
value = deepRemoveByKey(value, keysToBeRemoved);
}
if (!keysToBeRemoved.includes(key)) {
_.isArray(obj) ? result.push(value) : result[key] = value;
}
});
return result as typeof obj;
};
export const deepRenameKeys = (obj: JSONSerializable, keysMap: { [key in string]: string }) => {
return _.transform(obj, function (result: JSONSerializable, value: any, key: string | number) {
const currentKey = keysMap[key] || key;
result[currentKey] = _.isObject(value) ? deepRenameKeys(value, keysMap) : value;
});
};
export const deepReplaceValues = (obj: JSONSerializable, keyReducerMap: { [key in string]: (item: JSONSerializable) => any }) => {
const newObject = _.clone(obj) as JSONSerializable;
_.each(obj, (val: any, key: string) => {
for (const item in keyReducerMap) {
if (key === item) {
newObject[key] = keyReducerMap[item](newObject);
} else if (typeof (val) === 'object' || val instanceof Array) {
newObject[key] = deepReplaceValues(val, keyReducerMap);
}
}
});
return newObject;
};
// TODO determine depth and pass root node as a param through callback
export const deepAdd = (obj: JSONSerializable, keyReducerMap: { [key in string]: (item: JSONSerializable) => any }, isItemRootParent?: boolean) => {
const newObject = _.clone(obj) as JSONObject | [];
if (_.isObject(newObject) && !_.isArray(newObject)) {
for (const item in keyReducerMap) {
newObject[item] = keyReducerMap[item](newObject);
}
}
_.each(obj, (val: any, key: string | number) => {
if (_.isObject(val)) {
for (const item in keyReducerMap) {
// @ts-ignore
newObject[key] = deepAdd(val, keyReducerMap, isItemRootParent);
}
}
});
return newObject;
};
const styleString = (color: string) => `color: ${color}; font-weight: bold`;
const styleHeader = (header: string) => `%c[${header}]`;
export const bunnyConsole = {
log: (headerLog = 'bunny', ...args: any[]) => {
return console.log(styleHeader(headerLog), styleString('black'), ...args);
},
warn: (headerLog = 'bunny', ...args: any[]) => {
return console.warn(styleHeader(headerLog), styleString('orange'), ...args);
},
error: (headerLog = 'bunny', ...args: any[]) => {
return console.error(styleHeader(headerLog), styleString('red'), ...args);
}
};
export const timeStart = () => {
return performance ? performance.now() : new Date().getTime();
};
export const timeEnd = (startTime: number, headerLog?: string, consoleConditionFn?: (timeSpent: number) => boolean) => {
const timeSpent = (performance ? performance.now() : new Date().getTime()) - startTime;
const isPassCondition = consoleConditionFn ? consoleConditionFn(timeSpent) : true;
if (isPassCondition) {
bunnyConsole.log(headerLog ? headerLog : 'time spent', timeSpent.toFixed(2));
}
};
export const arrayRemove = function <T>(array: T[], predicate: (item: T, index: number, array: T[]) => boolean): T[] {
let i = -1, len = array ? array.length : 0;
const result = [];
while (++i < len) {
const value = array[i];
if (predicate(value, i, array)) {
result.push(value);
Array.prototype.splice.call(array, i--, 1);
len--;
}
}
return result;
};
export function memo() {
const cache: { [k: string]: any } = {};
// eslint-disable-next-line @typescript-eslint/ban-types
return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const cacheKey = `__cacheKey__${args.toString()}`;
// eslint-disable-next-line no-prototype-builtins
if (!cache.hasOwnProperty(cacheKey)) {
cache[cacheKey] = originalMethod.apply(this, args);
}
return cache[cacheKey];
}
}
}
export function zip<T = number, T1 = number>(array1: T[], array2: T1[], options?: { isToObj: boolean }) {
const zipped: [T, T1][] = [];
const zippedObjCoords: { x: T, y: T1 }[] = [];
const {isToObj} = options ? options : {isToObj: false};
for (let i = 0; i < array1.length; i++) {
if (isToObj) {
zippedObjCoords.push({x: array1[i], y: array2[i]})
} else {
zipped.push([array1[i], array2[i]]);
}
}
return isToObj ? zippedObjCoords : zipped;
}
/**
* data-structure-typed
*
* @author Tyler Zeng
* @copyright Copyright (c) 2022 Tyler Zeng <zrwusa@gmail.com>
* @license MIT License
*/
import {Thunk, ToThunkFn, TrlAsyncFn, TrlFn} from './types';
export const THUNK_SYMBOL = Symbol('thunk')
export const isThunk = (fnOrValue: any) => {
return typeof fnOrValue === 'function' && fnOrValue.__THUNK__ === THUNK_SYMBOL
}
export const toThunk = (fn: ToThunkFn): Thunk => {
const thunk = () => fn()
thunk.__THUNK__ = THUNK_SYMBOL
return thunk
}
export const trampoline = (fn: TrlFn) => {
const cont = (...args: [...Parameters<TrlFn>]) => toThunk(() => fn(...args))
return Object.assign(
(...args: [...Parameters<TrlFn>]) => {
let result = fn(...args)
while (isThunk(result) && typeof result === 'function') {
result = result()
}
return result
},
{cont}
)
}
export const trampolineAsync = (fn: TrlAsyncFn) => {
const cont = (...args: [...Parameters<TrlAsyncFn>]) => toThunk(() => fn(...args))
return Object.assign(
async (...args: [...Parameters<TrlAsyncFn>]) => {
let result = await fn(...args)
while (isThunk(result) && typeof result === 'function') {
result = await result()
}
return result
},
{cont}
)
}