Skip to content

Commit

Permalink
do not use default exports, only named
Browse files Browse the repository at this point in the history
  • Loading branch information
philihp committed Dec 15, 2023
1 parent f8efbf0 commit a854175
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 40 deletions.
42 changes: 20 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,42 @@ const shuffledDeck = shuffle(sortedDeck)
// [ '3♥', '3♦', 'K♥', '6♦', 'J♣', '5♠', 'A♠', ...
```

The parameters are also curried, so it can be used in [pipelines](https://github.com/tc39/proposal-pipeline-operator).

```js
import { shuffle } from 'fast-shuffle'

const randomCapitalLetter =
['a', 'b', 'c', 'd', 'e', 'f'] // :: () -> [a]
|> shuffle, // :: [a] -> [a]
|> _ => _[0] // :: [a] -> a
|> _ => _.toUpperCase() // :: a -> a
```

The named `shuffle` export seen above uses `Math.random` for entropy. If you import it without the brackets, you'll get a deterministic shuffler which takes an int for its random seed (e.g. `Date.now()`).

```js
import shuffle from 'fast-shuffle' // note the change
import { fnShuffle } from 'fast-shuffle' // note the change

const letters = ['a', 'b', 'c', 'd', 'e']
const shuffleRed = shuffle(12345)
const shuffleRed = fnShuffle(12345)
shuffleRed(letters) // [ 'a', 'b', 'c', 'd', 'e' ]
shuffleRed(letters) // [ 'a', 'd', 'b', 'e', 'c' ]
shuffleRed(letters) // [ 'c', 'a', 'e', 'b', 'd' ]
shuffleRed(letters) // [ 'b', 'c', 'e', 'a', 'd' ]

const shuffleBlue = shuffle(12345)
const shuffleBlue = fnShuffle(12345)
shuffleBlue(letters) // [ 'a', 'b', 'c', 'd', 'e' ]
shuffleBlue(letters) // [ 'a', 'd', 'b', 'e', 'c' ]
shuffleBlue(letters) // [ 'c', 'a', 'e', 'b', 'd' ]
shuffleBlue(letters) // [ 'b', 'c', 'e', 'a', 'd' ]
```

If you give it an array of your array and a random seed, you'll get a shuffled array and a new random seed back. This is a pure function, so you can use it in your Redux reducers.
The parameters are also curried, so it can be used in [pipelines](https://github.com/tc39/proposal-pipeline-operator).

```js
import { fnShuffle } from 'fast-shuffle'

const randomCapitalLetter =
['a', 'b', 'c', 'd', 'e', 'f'] // :: () -> [a]
|> fnShuffle(Math.random), // :: [a] -> [a]
|> _ => _[0] // :: [a] -> a
|> _ => _.toUpperCase() // :: a -> a
```

If you give it an array of your array and a random seed, you'll get a shuffled array and a new random seed back. This is a pure function and the original array is not mutated, so you can use it in your Redux reducers. The returned, shuffled array is a shallow copy, so if you use this in React, [you will often avoid unnecessary rerenders](https://redux.js.org/faq/performance).

```js
import { SHUFFLE_DECK } from './actions'
import shuffle from 'fast-shuffle'
import { fnShuffle } from 'fast-shuffle'

const initialState = {
...
Expand All @@ -73,7 +73,7 @@ const dealerApp = (state = initialState, action) => {
switch (action.type) {
...
case SHUFFLE_DECK:
const [ deck, randomizer ] = shuffle([state.deck, state.randomizer])
const [ deck, randomizer ] = fnShuffle([state.deck, state.randomizer])
return {
...state,
deck,
Expand All @@ -86,14 +86,12 @@ const dealerApp = (state = initialState, action) => {
}
```

Shuffle doesn't mutate the original array, instead it gives you back a shallow copy. This is important for React and [performance reasons](https://redux.js.org/faq/performance).

## Why not use existing libraries?

1. It doesn't mutate your source array, so it's safe for Redux reducers.

2. The parameters are curried in [the correct order](https://www.youtube.com/watch?v=m3svKOdZijA), so you can use it within `|>` or Ramda pipes.

3. It's stupid-fast and scales to large arrays without breaking a sweat.
3. You can make it a deterministic pure function, useful for shuffling in tests.

4. You can BYO-RNG.
4. It's stupid-fast and scales to large arrays without breaking a sweat.
34 changes: 17 additions & 17 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { pipe } from 'ramda'
import fastShuffle, { shuffle } from '..'
import { shuffle, fnShuffle } from '..'

describe('default', () => {
it('shuffles the array', () => {
expect.assertions(2)
const pseudoShuffle = fastShuffle(12345)
const pseudoShuffle = fnShuffle(12345)
const d1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const d2 = pseudoShuffle(d1)
expect(d2).toStrictEqual(expect.arrayContaining(d1))
Expand All @@ -13,15 +13,15 @@ describe('default', () => {

it('does not mutate the source', () => {
expect.assertions(1)
const pseudoShuffle = fastShuffle(12345)
const pseudoShuffle = fnShuffle(12345)
const d1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
pseudoShuffle(d1)
expect(d1).toMatchObject(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'])
})

it('does a shallow clone', () => {
expect.assertions(3)
const pseudoShuffle = fastShuffle(12345)
const pseudoShuffle = fnShuffle(12345)
const d1 = [
{ name: 'Alice', money: 10 },
{ name: 'Betty', money: 20 },
Expand All @@ -35,7 +35,7 @@ describe('default', () => {

it('can be sorted back into the source array', () => {
expect.assertions(1)
const pseudoShuffle = fastShuffle(12345)
const pseudoShuffle = fnShuffle(12345)
const d1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'].sort()
const d2 = pseudoShuffle(d1)
const d3 = d2.sort()
Expand All @@ -46,13 +46,13 @@ describe('default', () => {
expect.assertions(1)
const d = new Array(10000)
const rng = jest.fn()
fastShuffle(rng)(d)
fnShuffle(rng)(d)
expect(rng).toHaveBeenCalledTimes(d.length)
})

it('can be piped as a curried function', () => {
expect.assertions(1)
const pseudoShuffle = fastShuffle(12345)
const pseudoShuffle = fnShuffle(12345)
const letters = () => ['a', 'b', 'c', 'd']
const head = (array: any[]) => array?.[0]
const drawCard = pipe(letters, pseudoShuffle, head)
Expand All @@ -66,7 +66,7 @@ describe('default', () => {
0.2045602793853012, 0.8264361317269504, 0.5677250262815505, 0.5320779164321721, 0.5955447026062757,
]
// @ts-ignore
const pseudoShuffle = fastShuffle(() => noise.pop())
const pseudoShuffle = fnShuffle(() => noise.pop())
const d1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const d2 = pseudoShuffle(d1)
expect(d1).not.toStrictEqual(d2)
Expand All @@ -79,7 +79,7 @@ describe('default', () => {
0.2045602793853012, 0.8264361317269504, 0.5677250262815505, 0.5320779164321721, 0.5955447026062757,
]
// @ts-ignore
const pseudoShuffle = fastShuffle(() => noise.pop())
const pseudoShuffle = fnShuffle(() => noise.pop())
const d1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const d2 = pseudoShuffle(d1)
expect(d1).not.toStrictEqual(d2)
Expand All @@ -89,7 +89,7 @@ describe('default', () => {
it('also, rather than curried, accepts a seed and the source array', () => {
expect.assertions(1)
const d1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const d2 = fastShuffle(12345, d1)
const d2 = fnShuffle(12345, d1)
expect(d2).toStrictEqual(['C', 'G', 'H', 'B', 'F', 'D', 'E', 'A'])
})
})
Expand All @@ -104,36 +104,36 @@ describe('shuffle', () => {
})
})

describe('fastShuffle for reducers', () => {
describe('fnShuffle for reducers', () => {
it('accepts [array, number]', () => {
expect.assertions(2)
const d1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const [d2, seedState] = fastShuffle([d1, 12345])
const [d2, seedState] = fnShuffle([d1, 12345])
expect(seedState).toBeDefined()
expect(d2).toStrictEqual(['C', 'G', 'H', 'B', 'F', 'D', 'E', 'A'])
})

it('returns different arrays with different seed ints', () => {
expect.assertions(2)
const s1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const [d1] = fastShuffle([s1, 12345])
const [d2] = fastShuffle([s1, 67890])
const [d1] = fnShuffle([s1, 12345])
const [d2] = fnShuffle([s1, 67890])
expect(d1).not.toStrictEqual(d2)
expect(d1.sort()).toStrictEqual(d2.sort())
})

it('finds its own seed, if not given one', () => {
expect.assertions(1)
const s1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const [d1] = fastShuffle([s1, undefined])
const [d1] = fnShuffle([s1, undefined])
expect(s1.every((r) => d1.includes(r))).toBe(true)
})

it('nondeterministically seeds, if no seed provided', () => {
expect.assertions(2)
const s1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const [d1, r1] = fastShuffle([s1, undefined])
const [d2, r2] = fastShuffle([s1, undefined])
const [d1, r1] = fnShuffle([s1, undefined])
const [d2, r2] = fnShuffle([s1, undefined])
expect(d1.every((r: any) => d2.includes(r))).toBe(true)
expect(r1).not.toStrictEqual(r2)
})
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ function fastShuffle(randomSeed: number | (() => number) | [deck: unknown[], ran

export const shuffle = <T>(deck: T[]) => fastShuffle(randomInt(), deck)

export default fastShuffle
export const fnShuffle = fastShuffle

0 comments on commit a854175

Please sign in to comment.