Skip to content

Instantly share code, notes, and snippets.

@danielweck
Last active January 12, 2022 13:15
Show Gist options
  • Save danielweck/fb48cafa2e6543e81f313e961ea8496c to your computer and use it in GitHub Desktop.
Save danielweck/fb48cafa2e6543e81f313e961ea8496c to your computer and use it in GitHub Desktop.
String hashing used for CSS-in-JS, cache / memoizing, etc.

(it turns out the meaning of life is not 42, i's 5381 and 33!)

StackOverflow

"cyrb53" (actually 64 bits (2x 32 bits in parallel), but limited to Javascript 53-bit integers)

Uses Math.imul().

https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480

Twind

"cyrb32"

Uses Math.imul().

https://github.com/tw-in-js/twind/blob/173d9f27f4c43d43e595c9fb78a500926b7e0bdb/src/internal/util.ts#L120-L128

export const cyrb32: Hasher = (value: string): string => {
  let h = 9

  for (let index = value.length; index--; ) {
    h = Math.imul(h ^ value.charCodeAt(index), 0x5f356495)
  }

  return 'tw-' + ((h ^ (h >>> 9)) >>> 0).toString(36)
}

Murmur Hash v2 and v3

https://github.com/unjs/murmurhash-es

/**
 * JS Implementation of MurmurHash2
 *
 *
 * @param {Uint8Array | string} str ASCII only
 * @param {number} seed Positive integer only
 * @return {number} 32-bit positive integer hash
 */
export function murmurHashV2(str, seed = 0) {
  if (typeof str === 'string') {
    str = createBuffer(str);
  }
  let l = str.length
  let h = seed ^ l
  let i = 0
  let k
  while (l >= 4) {
    k =
      ((str[i] & 0xff)) |
      ((str[++i] & 0xff) << 8) |
      ((str[++i] & 0xff) << 16) |
      ((str[++i] & 0xff) << 24);

    k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
    k ^= k >>> 24;
    k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));

    h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;

    l -= 4;
    ++i;
  }

  switch (l) {
    case 3: h ^= (str[i + 2] & 0xff) << 16;
    case 2: h ^= (str[i + 1] & 0xff) << 8;
    case 1: h ^= (str[i] & 0xff);
      h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
  }

  h ^= h >>> 13;
  h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
  h ^= h >>> 15;

  return h >>> 0;
};
/**
 * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
 *
 * @param {Uint8Array | string} key ASCII only
 * @param {number} seed Positive integer only
 * @return {number} 32-bit positive integer hash
 */
export function murmurHashV3(key, seed = 0) {
  if (typeof key === 'string') {
    key = createBuffer(key);
  }

  let remainder, bytes, h1, h1b, c1, c2, k1, i;

  remainder = key.length & 3; // key.length % 4
  bytes = key.length - remainder;
  h1 = seed;
  c1 = 0xcc9e2d51;
  c2 = 0x1b873593;
  i = 0;

  while (i < bytes) {
    k1 =
      ((key[i] & 0xff)) |
      ((key[++i] & 0xff) << 8) |
      ((key[++i] & 0xff) << 16) |
      ((key[++i] & 0xff) << 24);
    ++i;

    k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
    k1 = (k1 << 15) | (k1 >>> 17);
    k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;

    h1 ^= k1;
    h1 = (h1 << 13) | (h1 >>> 19);
    h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
    h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
  }

  k1 = 0;

  switch (remainder) {
    case 3: k1 ^= (key[i + 2] & 0xff) << 16;
    case 2: k1 ^= (key[i + 1] & 0xff) << 8;
    case 1: k1 ^= (key[i] & 0xff);

      k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
      k1 = (k1 << 15) | (k1 >>> 17);
      k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
      h1 ^= k1;
  }

  h1 ^= key.length;

  h1 ^= h1 >>> 16;
  h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
  h1 ^= h1 >>> 13;
  h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
  h1 ^= h1 >>> 16;

  return h1 >>> 0;
}

Beamwind

"cyrb32"

Uses Math.imul() polyfill.

https://github.com/kenoxa/beamwind/blob/e503a49330b02247abf3ffd9bc85e2f0b6f04c5c/packages/core/src/hash.ts#L4-L35

const imul =
  Math.imul ||
  ((opA: number, opB: number): number => {
    /* eslint-disable unicorn/prefer-math-trunc */
    // Ensure that opB is an integer. opA will automatically be coerced.
    opB |= 0

    // Floating points give us 53 bits of precision to work with plus 1 sign bit
    // automatically handled for our convienence:
    // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001
    //    0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/
    let result = (opA & 0x003fffff) * opB

    // 2. We can remove an integer coersion from the statement above because:
    //    0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001
    //    0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/
    if (opA & 0xffc00000 /* !== 0 */) result += ((opA & 0xffc00000) * opB) | 0

    return result | 0
    /* eslint-enable unicorn/prefer-math-trunc */
  })

// Based on https://stackoverflow.com/a/52171480
export const cyrb32: Hasher = (value: string): string => {
  let h = 9

  for (let index = value.length; index--; ) {
    h = imul(h ^ value.charCodeAt(index), 0x5f356495)
  }

  return '_' + ((h ^ (h >>> 9)) >>> 0).toString(36)
}

Goober

https://github.com/cristianbote/goober/blob/942ab335d5fd4f125d0ae9e340b7004a45b52171/src/core/to-hash.js#L10-L11

    let i = 0, l = str.length, out = 11;
    while (i < l) out = (101 * out + str.charCodeAt(i++)) >>> 0
    const c = 'go' + out;

See improved perf proposal: cristianbote/goober#271

Nano CSS

https://github.com/streamich/nano-css/blob/8d5a374545f0a48f0dfb683b21fc0a214d4b34d8/index.js#L5-L11

var hash = function (str) {
    var h = 5381, i = str.length;

    while (i) h = (h * 33) ^ str.charCodeAt(--i);

    return '_' + (h >>> 0).toString(36);
};

CSSTag

https://github.com/sgtpep/csstag/blob/8e13922dfc5590c0bd47c29f3aeb3db0303cb1cd/src/index.js#L59-L63

const hashCode = string =>
  [...string].reduce(
    (hash, char) => ((hash << 5) - hash + char.charCodeAt(0)) | 0,
    0
  );

WindiCSS

https://github.com/windicss/windicss/blob/83aecadb6f97ca492e972acb8fa323a8007b5543/src/utils/tools.ts#L3-L10

export function hash(str: string): string {
  str = str.replace(/\r/g, '');
  let hash = 5381;
  let i = str.length;

  while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
  return (hash >>> 0).toString(36);
}

Radium

"djb2"

https://github.com/FormidableLabs/radium/blob/e167b5c1bd85edfd74ae99a274a0ac586a70d177/src/hash.js#L6-L20

export default function hash(text: string): string {
  if (!text) {
    return '';
  }

  let hashValue = 5381;
  let index = text.length - 1;

  while (index) {
    hashValue = (hashValue * 33) ^ text.charCodeAt(index);
    index -= 1;
  }

  return (hashValue >>> 0).toString(16);
}

Sindresorhus rev-hash

(truncated MD5)

https://github.com/sindresorhus/rev-hash/blob/37c360d3d49d81244bfe3ea8ae02475c2e483d67/index.js#L2-L10

const crypto = require('crypto');

module.exports = input => {
	if (typeof input !== 'string' && !Buffer.isBuffer(input)) {
		throw new TypeError('Expected a Buffer or string');
	}

	return crypto.createHash('md5').update(input).digest('hex').slice(0, 10);
};

Stilr

https://github.com/kodyl/stilr/blob/d5d547d1b37a1f16e08eba727979d94450229fba/lib/utils/index.js#L13-L21

export function createHash(str) {
  let i = str.length;
  if (i === 0) return 0;

  let hash = 5381;
  while (i) hash = (hash * 33) ^ str.charCodeAt(--i);

  return hash >>> 0;
}

Hash-string

https://github.com/MatthewBarker/hash-string/blob/fa275e1adb3c8ba638929113112fbd008f68d9f5/source/hash-string.js#L6-L17

function hash(text) {
    'use strict';

    var hash = 5381,
        index = text.length;

    while (index) {
        hash = (hash * 33) ^ text.charCodeAt(--index);
    }

    return hash >>> 0;
}

Styled Components

"djb2"-ish

https://github.com/styled-components/styled-components/blob/9bef00cb03ab36c5e7d1faaf75e1f9419ae72f73/packages/styled-components/src/utils/hash.js#L4-L22

export const SEED = 5381;

// When we have separate strings it's useful to run a progressive
// version of djb2 where we pretend that we're still looping over
// the same string
export const phash = (h: number, x: string): number => {
  let i = x.length;

  while (i) {
    h = (h * 33) ^ x.charCodeAt(--i);
  }

  return h;
};

// This is a djb2 hashing function
export const hash = (x: string): number => {
  return phash(SEED, x);
};

String-hash

https://github.com/darkskyapp/string-hash/blob/cb38ab492aba198b9658b286bb2391278bb6992b/index.js#L3-L15

function hash(str) {
  var hash = 5381,
      i    = str.length;

  while(i) {
    hash = (hash * 33) ^ str.charCodeAt(--i);
  }

  /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
   * integers. Since we want the results to be always positive, convert the
   * signed int to an unsigned by doing an unsigned bitshift. */
  return hash >>> 0;
}

Sindresorhus sdbm

https://github.com/sindresorhus/sdbm/blob/0f9a94776ceede0e749670aeb2bdf229d0962362/index.js#L3-L12

const sdbm = string => {
	let hash = 0;

	for (let i = 0; i < string.length; i++) {
		hash = string.charCodeAt(i) + (hash << 6) + (hash << 16) - hash;
	}

	// Convert it to an unsigned 32-bit integer
	return hash >>> 0;
};

Sindresorhus fnv1

https://github.com/sindresorhus/fnv1a/blob/03fe6a4625f5995e8e5dc0d7a1f25759e38c2886/index.js#L25-L45

function fnv1a(string) {
	// Handle Unicode code points > 0x7f
	let hash = Number(FNV_OFFSETS[32]);
	let isUnicoded = false;

	for (let i = 0; i < string.length; i++) {
		let characterCode = string.charCodeAt(i);

		// Non-ASCII characters trigger the Unicode escape logic
		if (characterCode > 0x7F && !isUnicoded) {
			string = unescape(encodeURIComponent(string));
			characterCode = string.charCodeAt(i);
			isUnicoded = true;
		}

		hash ^= characterCode;
		hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
	}

	return hash >>> 0;
}

Sindresorhus djb2

https://github.com/sindresorhus/djb2a/blob/cadc330f1261983f5bc447178e2bb94a5e524d7f/index.js#L6-L16

const MAGIC_CONSTANT = 5381;

const djb2a = string => {
	let hash = MAGIC_CONSTANT;

	for (let i = 0; i < string.length; i++) {
		// Equivalent to: `hash * 33 ^ string.charCodeAt(i)`
		hash = ((hash << 5) + hash) ^ string.charCodeAt(i);
	}

	// Convert it to an unsigned 32-bit integer
	return hash >>> 0;
};

Hash-it

"dbj2" spinoff

https://github.com/planttheidea/hash-it/blob/04190ed1c91e223354865084b0b2d2d64f290281/src/utils.js#L48-L62

export function getIntegerHashValue(string) {
  let index = string.length,
    hashA = 5381,
    hashB = 52711,
    charCode;

  while (index--) {
    charCode = charCodeAt.call(string, index);

    hashA = (hashA * 33) ^ charCode;
    hashB = (hashB * 33) ^ charCode;
  }

  return (hashA >>> 0) * 4096 + (hashB >>> 0);
}

EmotionJS

Based on "murmur" hash (see below)

https://github.com/emotion-js/emotion/blob/5cdbbb330256b11ee1c67d961a9733cc68e4d1c7/packages/hash/src/index.js#L6-L64

export default function murmur2(str: string) {
  // 'm' and 'r' are mixing constants generated offline.
  // They're not really 'magic', they just happen to work well.

  // const m = 0x5bd1e995;
  // const r = 24;

  // Initialize the hash

  var h = 0

  // Mix 4 bytes at a time into the hash

  var k,
    i = 0,
    len = str.length
  for (; len >= 4; ++i, len -= 4) {
    k =
      (str.charCodeAt(i) & 0xff) |
      ((str.charCodeAt(++i) & 0xff) << 8) |
      ((str.charCodeAt(++i) & 0xff) << 16) |
      ((str.charCodeAt(++i) & 0xff) << 24)

    k =
      /* Math.imul(k, m): */
      (k & 0xffff) * 0x5bd1e995 + (((k >>> 16) * 0xe995) << 16)
    k ^= /* k >>> r: */ k >>> 24

    h =
      /* Math.imul(k, m): */
      ((k & 0xffff) * 0x5bd1e995 + (((k >>> 16) * 0xe995) << 16)) ^
      /* Math.imul(h, m): */
      ((h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0xe995) << 16))
  }

  // Handle the last few bytes of the input array

  switch (len) {
    case 3:
      h ^= (str.charCodeAt(i + 2) & 0xff) << 16
    case 2:
      h ^= (str.charCodeAt(i + 1) & 0xff) << 8
    case 1:
      h ^= str.charCodeAt(i) & 0xff
      h =
        /* Math.imul(h, m): */
        (h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0xe995) << 16)
  }

  // Do a few final mixes of the hash to ensure the last few
  // bytes are well-incorporated.

  h ^= h >>> 13
  h =
    /* Math.imul(h, m): */
    (h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0xe995) << 16)

  return ((h ^ (h >>> 15)) >>> 0).toString(36)
}

Linaria

"murmur" hash

https://github.com/callstack/linaria/blob/03631ce932be7f74b3df5234eb6fa086ce83d334/packages/babel/src/utils/slugify.ts#L11-L55

function doHash(str: string, seed: number = 0) {
  const m = 0x5bd1e995;
  const r = 24;
  let h = seed ^ str.length;
  let length = str.length;
  let currentIndex = 0;

  while (length >= 4) {
    let k = UInt32(str, currentIndex);

    k = Umul32(k, m);
    k ^= k >>> r;
    k = Umul32(k, m);

    h = Umul32(h, m);
    h ^= k;

    currentIndex += 4;
    length -= 4;
  }

  switch (length) {
    case 3:
      h ^= UInt16(str, currentIndex);
      h ^= str.charCodeAt(currentIndex + 2) << 16;
      h = Umul32(h, m);
      break;

    case 2:
      h ^= UInt16(str, currentIndex);
      h = Umul32(h, m);
      break;

    case 1:
      h ^= str.charCodeAt(currentIndex);
      h = Umul32(h, m);
      break;
  }

  h ^= h >>> 13;
  h = Umul32(h, m);
  h ^= h >>> 15;

  return h >>> 0;
}

Atlassian Compiled

Murmur

https://github.com/atlassian-labs/compiled/blob/0f213f010f68bdb8a36e48d0e0cc0522901339dd/packages/utils/src/hash.tsx#L8-L44

  let l = str.length;
  let h = seed ^ l;
  let i = 0;
  let k;

  while (l >= 4) {
    k =
      (str.charCodeAt(i) & 0xff) |
      ((str.charCodeAt(++i) & 0xff) << 8) |
      ((str.charCodeAt(++i) & 0xff) << 16) |
      ((str.charCodeAt(++i) & 0xff) << 24);

    k = (k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16);
    k ^= k >>> 24;
    k = (k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16);
    h = ((h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
    l -= 4;

    ++i;
  }

  switch (l) {
    case 3:
      h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
    case 2:
      h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
    case 1:
      h ^= str.charCodeAt(i) & 0xff;
      h = (h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16);
  }

  h ^= h >>> 13;
  h = (h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16);
  h ^= h >>> 15;

  return (h >>> 0).toString(36);

Murmur hash

https://github.com/garycourt/murmurhash-js/blob/0197ce38bedac0e05f40b9d7152095d06db8292c/murmurhash3_gc.js#L14-L64

function murmurhash3_32_gc(key, seed) {
	var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i;
	
	remainder = key.length & 3; // key.length % 4
	bytes = key.length - remainder;
	h1 = seed;
	c1 = 0xcc9e2d51;
	c2 = 0x1b873593;
	i = 0;
	
	while (i < bytes) {
	  	k1 = 
	  	  ((key.charCodeAt(i) & 0xff)) |
	  	  ((key.charCodeAt(++i) & 0xff) << 8) |
	  	  ((key.charCodeAt(++i) & 0xff) << 16) |
	  	  ((key.charCodeAt(++i) & 0xff) << 24);
		++i;
		
		k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
		k1 = (k1 << 15) | (k1 >>> 17);
		k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;

		h1 ^= k1;
        h1 = (h1 << 13) | (h1 >>> 19);
		h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
		h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
	}
	
	k1 = 0;
	
	switch (remainder) {
		case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
		case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
		case 1: k1 ^= (key.charCodeAt(i) & 0xff);
		
		k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
		k1 = (k1 << 15) | (k1 >>> 17);
		k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
		h1 ^= k1;
	}
	
	h1 ^= key.length;

	h1 ^= h1 >>> 16;
	h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
	h1 ^= h1 >>> 13;
	h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
	h1 ^= h1 >>> 16;

	return h1 >>> 0;
}

Stylis

https://github.com/thysultan/stylis.js/blob/d09db4c354110c41654fd817ce101976b078d8f7/src/Utility.js#L18-L20

export function hash (value, length) {
	return (((((((length << 2) ^ charat(value, 0)) << 2) ^ charat(value, 1)) << 2) ^ charat(value, 2)) << 2) ^ charat(value, 3)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment