Easy color hashing with Erlang’s phash2
Discover a pretty cool hashing function in Erlang.
This is my new favorite erlang function that’s doesn’t have a direct Elixir version: phash2 .
Here’s the Erlang documentation:
https://www.erlang.org/docs/18/man/erlang#phash2-2
erlang:phash2(Term) -> Hash erlang:phash2(Term, Range) -> Hash Types: Term = term() Range = integer() >= 1 1..2^32 Hash = integer() >= 0 0..Range-1 Portable hash function that gives the same hash for the same Erlang term regardless of machine architecture and ERTS version (the BIF was introduced in ERTS 5.2). The function returns a hash value for Term within the range 0..Range-1. The maximum value for Range is 2^32. When without argument Range, a value in the range 0..2^27-1 is returned. This BIF is always to be used for hashing terms. It distributes small integers better than phash/2, and it is faster for bignums and binaries. Notice that the range 0..Range-1 is different from the range of phash/2, which is 1..Range.
Hashing functions are nothing special, but it’s cool that:
- You can pass anything to phash2 , it doesn’t have to be a string.
- You can constrain the integer output to a range.
So we can for instance hash a struct, or a string:
iex(1)> :erlang.phash2("hello world")
33550279
iex(2)> :erlang.phash2(%{name: "Alexander"})
46087436
And we can do this:
iex(1)> :erlang.phash2("hello", 360)
59
iex(2)> :erlang.phash2("world", 360)
102
We can use the ability to constrain the output to constrain the result to some integer range like a valid RGB or HSL color. Let’s try that!
defmodule ColorHash do
def hash(string) do
# Adding an extra value here just forces each value to be different.
hue = :erlang.phash2(string <> ":h", 360)
saturation = :erlang.phash2(string <> ":s", 100)
lightness = :erlang.phash2(string <> ":l", 100)
{hue, saturation, lightness}
end
end
We could feed these colors into like, chat bubbles on a website to color them based on user names. But they’re a little ugly if you just use every saturation value. Here’s a little tweak on the above that lets you constrain the values to something a little more sensible:
defmodule ColorHash do
@hue_range {0, 360}
@saturation_range {50, 80}
@lightness_range {85, 100}
def hash(string) do
{min_h, max_h} = @hue_range
{min_s, max_s} = @saturation_range
{min_l, max_l} = @lightness_range
# Adding an extra value here just forces each value to be different.
hue = :erlang.phash2(string <> ":h", max_h - min_h) + min_h
saturation = :erlang.phash2(string <> ":s", max_s - min_s) + min_s
lightness = :erlang.phash2(string <> ":l", max_l - min_l) + min_l
{hue, saturation, lightness}
end
# Expected CSS format.
def hsl_to_string({h, s, l}) do
"hsl(#{h} #{s}% #{l}%)"
end
end
Again, a great option for adding a little unique color to an app for each user.