Skip to content

Instantly share code, notes, and snippets.

@jsnanigans
Created April 17, 2023 09:39
Show Gist options
  • Select an option

  • Save jsnanigans/a4a4d80ab29f04260703b0681477986f to your computer and use it in GitHub Desktop.

Select an option

Save jsnanigans/a4a4d80ab29f04260703b0681477986f to your computer and use it in GitHub Desktop.

Revisions

  1. jsnanigans created this gist Apr 17, 2023.
    5 changes: 5 additions & 0 deletions hashText.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    function hashText(text: string, seed = 42): number {
    const encoder = new TextEncoder();
    const inputUint8Array = encoder.encode(text);
    return murmur3_32(inputUint8Array, inputUint8Array.length, seed);
    }
    53 changes: 53 additions & 0 deletions murmur3_32.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,53 @@
    function murmur3_32(key: Uint8Array, len: number, seed: number): number {
    const c1 = 0xcc9e2d51;
    const c2 = 0x1b873593;
    const r1 = 15;
    const r2 = 13;
    const m = 5;
    const n = 0xe6546b64;

    let hash = seed;

    function rol(value: number, shift: number): number {
    return (value << shift) | (value >>> (32 - shift));
    }

    for (let i = 0; i < len - (len % 4); i += 4) {
    let k =
    key[i] | (key[i + 1] << 8) | (key[i + 2] << 16) | (key[i + 3] << 24);

    k = (k * c1) | 0;
    k = rol(k, r1);
    k = (k * c2) | 0;

    hash ^= k;
    hash = rol(hash, r2);
    hash = (hash * m + n) | 0;
    }

    let remainingBytes = 0;
    switch (len % 4) {
    case 3:
    remainingBytes ^= key[len - 3] << 16;
    break;
    case 2:
    remainingBytes ^= key[len - 2] << 8;
    break;
    case 1:
    remainingBytes ^= key[len - 1];
    remainingBytes = (remainingBytes * c1) | 0;
    remainingBytes = rol(remainingBytes, r1);
    remainingBytes = (remainingBytes * c2) | 0;
    hash ^= remainingBytes;
    }

    hash ^= len;

    hash ^= hash >>> 16;
    hash = (hash * 0x85ebca6b) | 0;
    hash ^= hash >>> 13;
    hash = (hash * 0xc2b2ae35) | 0;
    hash ^= hash >>> 16;

    return hash >>> 0;
    }
    42 changes: 42 additions & 0 deletions test.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    import hashText from "src/lib/hashText";

    // mock "new TextEncoder()" and set it on the global.TextEncoder is not defined
    class TextEncoder {
    encode(input: string): Uint8Array {
    return new Uint8Array(input.split("").map((char) => char.charCodeAt(0)));
    }
    }

    (globalThis as any).TextEncoder = TextEncoder;

    describe('hashText', () => {
    it('should return the same hash for the same input and seed', () => {
    const input = 'Hello, world!';

    const hash1 = hashText(input);
    const hash2 = hashText(input);

    expect(hash1).toStrictEqual(hash2);
    });

    it('should return different hashes for different inputs with the same seed', () => {
    const input1 = 'Hello, world!';
    const input2 = 'Second String';

    const hash1 = hashText(input1);
    const hash2 = hashText(input2);

    expect(hash1).not.toStrictEqual(hash2);
    });

    it('should return different hashes for the same input with different seeds', () => {
    const input = 'Hello, world!';
    const seed1 = 42;
    const seed2 = 84;

    const hash1 = hashText(input, seed1);
    const hash2 = hashText(input, seed2);

    expect(hash1).not.toStrictEqual(hash2);
    });
    });