diff --git a/CHANGELOG.md b/CHANGELOG.md index 73ad154..2261e53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # deverything +## 0.36.0 + +### Minor Changes + +- non-uniform distro + ## 0.35.0 ### Minor Changes diff --git a/README.md b/README.md index 08de595..9a65ef2 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,10 @@ Contributions always welcome! - `getKeys()` get the keys of an object, includes symbols - `getUrlSearchParam()` - `getUrlSearchParams()` +- `incrementalId()` autoincremental SQL-like, process-unique numeric id +- `keysLength()` - `last()` get the last element of an array +- `lastIndex()` get the last index of an array - ⭐ `merge()` deep merge objects - `moveToFirst()` move array element to first - `moveToIndex()` move array element to desired index @@ -101,7 +104,7 @@ These functions are optimized for low entropy random data generation useful for - `randomAddress()` - `randomAlphaNumericCode()` -- ⭐ `randomArrayItem()` +- ⭐ `randomArrayItem()` now supporting non-uniform distribution - `randomBankAccount()` - `randomBool()` - `randomChar()` @@ -136,9 +139,9 @@ These functions are optimized for low entropy random data generation useful for - `randomLastName()` - `randomFullName()` - `randomNumericCode()` -- `randomNumericId()` autoincremental process-unique id - `randomParagraph()` - `randomPassword()` +- `randomPath()` /path/to/something - `randomPhoneNumber()` - `randomString()` - `randomUUID()` lightweight uuid generation, passing UUID validation diff --git a/package.json b/package.json index 9b4c83f..f459f8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "deverything", - "version": "0.35.0", + "version": "0.36.0", "description": "Everything you need for Dev", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/src/constants/addresses.ts b/src/constants/addresses.ts index 06cba24..99ad7f1 100644 --- a/src/constants/addresses.ts +++ b/src/constants/addresses.ts @@ -1,4 +1,5 @@ -export type Address = { +// Prefix with Random to avoid name collision with the Address type from other libs +export type RandomAddress = { city: string; country: string; countryCode: string; @@ -9,7 +10,7 @@ export type Address = { zip: string; }; -export const UK_ADDRESSES: Address[] = [ +export const UK_ADDRESSES: RandomAddress[] = [ { city: "London", country: "United Kingdom", @@ -29,7 +30,7 @@ export const UK_ADDRESSES: Address[] = [ }, ]; -export const US_ADDRESSES: Address[] = [ +export const US_ADDRESSES: RandomAddress[] = [ { city: "New York", country: "United States", @@ -51,7 +52,7 @@ export const US_ADDRESSES: Address[] = [ }, ]; -export const EUROPE_ADDRESSES: Address[] = [ +export const EUROPE_ADDRESSES: RandomAddress[] = [ { city: "Paris", country: "France", @@ -88,7 +89,7 @@ export const EUROPE_ADDRESSES: Address[] = [ }, ]; -export const MIXED_ADDRESSES: Address[] = [ +export const MIXED_ADDRESSES: RandomAddress[] = [ { city: "Moscow", country: "Russia", @@ -165,7 +166,7 @@ export const MIXED_ADDRESSES: Address[] = [ }, ]; -export const ADDRESSES: Address[] = [ +export const ADDRESSES: RandomAddress[] = [ ...UK_ADDRESSES, ...US_ADDRESSES, ...EUROPE_ADDRESSES, diff --git a/src/constants/words.ts b/src/constants/words.ts index e5eb0a6..95b3efb 100644 --- a/src/constants/words.ts +++ b/src/constants/words.ts @@ -1,37 +1,48 @@ export const VERBS = [ - "abide", - "abound", - "accept", - "accomplish", - "achieve", - "acquire", "act", - "adapt", "add", - "adjust", - "admire", - "admit", - "adopt", - "advance", - "advise", - "afford", "agree", - "alert", "allow", "be", + "catch", + "create", + "delete", + "discover", + "eat", + "explore", "go", + "help", + "imagine", + "jump", + "merge", "need", + "offer", + "play", + "read", + "run", + "search", + "skip", + "solve", + "speak", + "think", + "try", "work", + "write", ]; export const NOUNS = [ + "city", + "coffee", "courage", + "fact", "family", + "flower", "food", "friend", "fun", "hope", "justice", + "key", "life", "love", "music", @@ -48,29 +59,31 @@ export const ADJECTIVES = [ "fascinating", "interesting", "playful", + "predictable", "remarkable", + "soothing", "sunny", "unforgettable", "wonderful", - "predictable", ]; -// TODO: curate export const ADVERBS = [ - "abnormally", - "aboard", - "absentmindedly", - "absolutely", - "absurdly", - "abundantly", - "abysmally", - "academically", - "acceleratedly", - "accentually", - "acceptably", - "accessibly", "accidentally", "accommodatingly", + "boldly", + "briskly", + "carefully", + "efficiently", + "freely", + "gently", + "happily", + "lightly", + "loudly", + "quickly", + "quietly", + "suddenly", + "unexpectedly", + "wisely", ]; export const LONG_WORDS = [ diff --git a/src/helpers/incrementalId.ts b/src/helpers/incrementalId.ts new file mode 100644 index 0000000..4b51bda --- /dev/null +++ b/src/helpers/incrementalId.ts @@ -0,0 +1,5 @@ +let id = 1; // don't start with 0, to be closer to SQL autoincrement + +export const incrementalId = () => { + return id++; +}; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index ebc1e2e..b967bc3 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -11,7 +11,10 @@ export * from "./firstValue"; export * from "./getKeys"; export * from "./getUrlSearchParam"; export * from "./getUrlSearchParams"; +export * from "./incrementalId"; +export * from "./keysLength"; export * from "./last"; +export * from "./lastIndex"; export * from "./merge"; export * from "./moveToFirst"; export * from "./moveToLast"; diff --git a/src/helpers/keysLength.ts b/src/helpers/keysLength.ts new file mode 100644 index 0000000..c66387c --- /dev/null +++ b/src/helpers/keysLength.ts @@ -0,0 +1,5 @@ +import { PlainObject } from "../types"; + +export const keysLength = (obj: T) => { + return Object.keys(obj).length; +}; diff --git a/src/helpers/last.ts b/src/helpers/last.ts index 1017fef..e3070c4 100644 --- a/src/helpers/last.ts +++ b/src/helpers/last.ts @@ -1 +1,4 @@ -export const last = (arr?: T[]): T | undefined => arr?.[arr.length - 1]; +import { lastIndex } from "./lastIndex"; + +// Assume the array is not empty, otherwise the result needs to be banged all the time +export const last = (arr: T[]): T => arr[lastIndex(arr)]; diff --git a/src/helpers/lastIndex.ts b/src/helpers/lastIndex.ts new file mode 100644 index 0000000..0516b0f --- /dev/null +++ b/src/helpers/lastIndex.ts @@ -0,0 +1,3 @@ +export const lastIndex = (array: any[]): number => { + return array.length - 1; +}; diff --git a/src/random/index.ts b/src/random/index.ts index f37ae4f..a7958ad 100644 --- a/src/random/index.ts +++ b/src/random/index.ts @@ -25,6 +25,7 @@ export * from "./randomNumericCode"; export * from "./randomNumericId"; export * from "./randomParagraph"; export * from "./randomPassword"; +export * from "./randomPath"; export * from "./randomPhoneNumber"; export * from "./randomString"; export * from "./randomUUID"; diff --git a/src/random/randomArrayItem.test.ts b/src/random/randomArrayItem.test.ts index 55b1f22..b630f7d 100644 --- a/src/random/randomArrayItem.test.ts +++ b/src/random/randomArrayItem.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from "@jest/globals"; import { randomArrayItem } from "./randomArrayItem"; +import { array } from "../helpers"; describe(`randomArrayItem`, () => { it(`returns no item`, () => { @@ -9,4 +10,30 @@ describe(`randomArrayItem`, () => { expect(randomArrayItem([0])).toBe(0); expect(typeof randomArrayItem([0, 1])).toBe("number"); }); + it(`with weights non 1 sum`, () => { + const stats: { [key: number]: number } = { + 0: 0, + 1: 0, + }; + + array(100000).forEach(() => { + const random = randomArrayItem([0, 1], { weights: [22, 23] }); + stats[random]++; + }); + expect(stats[0]).toBeLessThan(stats[1]); + }); + it(`with weights 1 sum`, () => { + const stats: { [key: number]: number } = { + 0: 0, + 1: 0, + 2: 0, + }; + + array(100000).forEach(() => { + const random = randomArrayItem([0, 1, 2], { weights: [0.1, 0.2, 0.7] }); + stats[random]++; + }); + expect(stats[0]).toBeLessThan(stats[1]); + expect(stats[1]).toBeLessThan(stats[2]); + }); }); diff --git a/src/random/randomArrayItem.ts b/src/random/randomArrayItem.ts index 10c7e50..9a3f987 100644 --- a/src/random/randomArrayItem.ts +++ b/src/random/randomArrayItem.ts @@ -1,5 +1,26 @@ +import { lastIndex } from "../helpers/lastIndex"; +import { last } from "../helpers/last"; import { randomInt } from "./randomInt"; +import { sum } from "../math/sum"; -export const randomArrayItem = (array: T[]): T => { - return array[randomInt(0, array.length - 1)]; +export const randomArrayItem = ( + array: T[], + { weights }: { weights?: number[] } = {} +): T => { + if (weights && weights.length === array.length) { + const totalWeight = sum(weights); + let random = Math.random() * totalWeight; + + for (let i = 0; i < weights.length; i++) { + random -= weights[i]; + if (random <= 0) { + return array[i]; + } + } + + // In case of rounding errors, return the last item + return last(array); + } + + return array[randomInt(0, lastIndex(array))]; }; diff --git a/src/random/randomNumericId.test.ts b/src/random/randomNumericId.test.ts deleted file mode 100644 index 05c508e..0000000 --- a/src/random/randomNumericId.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { test, describe, expect } from "@jest/globals"; -import { randomNumericId } from "./randomNumericId"; - -describe("randomNumericId", () => { - test(`generates multiple valid ids`, async () => { - expect(randomNumericId()).toBeGreaterThan(0); - expect(randomNumericId()).toBeTruthy(); - }); -}); diff --git a/src/random/randomNumericId.ts b/src/random/randomNumericId.ts index 78b765d..dbff056 100644 --- a/src/random/randomNumericId.ts +++ b/src/random/randomNumericId.ts @@ -1,5 +1,8 @@ let id = 1; // don't start with 0, to be closer to SQL autoincrement +/** + * @deprecated use incrementalId() instead, as this one is not random and could cause confusion + */ export const randomNumericId = () => { return id++; }; diff --git a/src/random/randomPath.test.ts b/src/random/randomPath.test.ts new file mode 100644 index 0000000..2631f7f --- /dev/null +++ b/src/random/randomPath.test.ts @@ -0,0 +1,8 @@ +import { describe, it, expect } from "@jest/globals"; +import { randomPath } from "./randomPath"; + +describe(`randomPath`, () => { + it(`works`, () => { + expect(randomPath().includes("/")).toBe(true); + }); +}); diff --git a/src/random/randomPath.ts b/src/random/randomPath.ts new file mode 100644 index 0000000..a70906c --- /dev/null +++ b/src/random/randomPath.ts @@ -0,0 +1,10 @@ +import { array } from "../helpers"; +import { randomWord } from "./randomWord"; + +export const randomPath = ({ + depth = 3, +}: { + depth?: number; +} = {}) => { + return array(depth, randomWord).join("/"); +}; diff --git a/src/random/randomWord.ts b/src/random/randomWord.ts index e092de6..a96d7a0 100644 --- a/src/random/randomWord.ts +++ b/src/random/randomWord.ts @@ -1,6 +1,14 @@ -import { WORDS } from "../constants/words"; +import { NOUNS, VERBS, WORDS } from "../constants/words"; import { randomArrayItem } from "./randomArrayItem"; export const randomWord = () => { return randomArrayItem(WORDS); }; + +export const randomNoun = () => { + return randomArrayItem(NOUNS); +}; + +export const randomVerb = () => { + return randomArrayItem(VERBS); +}; diff --git a/src/validators/isLastIndex.ts b/src/validators/isLastIndex.ts index 6dc2802..ca587d5 100644 --- a/src/validators/isLastIndex.ts +++ b/src/validators/isLastIndex.ts @@ -1,3 +1,5 @@ +import { lastIndex } from "../helpers/lastIndex"; + export const isLastIndex = (index: number, array: any[]): boolean => { - return index === array.length - 1; + return index === lastIndex(array); };