In this link, it says that truncated MD5 is uniformly distributed. I wanted to check it using PySpark and I created 1,000,000 UUIDs in Python first as shown below. Then truncated the first three characters from MD5. But the plot I get is not similar to the cumulative distribution function of a uniform distribution. I tried with UUID1 and UUID4 and the results are similar. What is the right way of conforming the uniform distribution of truncated MD5?
import uuid
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.distributions.empirical_distribution import ECDF
import pandas as pd
import pyspark.sql.functions as f
%matplotlib inline
### Generate 1,000,000 UUID1
uuid1 = [str(uuid.uuid1()) for i in range(1000000)] # make a UUID based on the host ID and current time
uuid1_df = pd.DataFrame({'uuid1':uuid1})
uuid1_spark_df = spark.createDataFrame(uuid1_df)
uuid1_spark_df = uuid1_spark_df.withColumn('hash', f.md5(f.col('uuid1')))\
.withColumn('truncated_hash3', f.substring(f.col('hash'), 1, 3))
count_by_truncated_hash3_uuid1 = uuid1_spark_df.groupBy('truncated_hash3').count()
uuid1_count_list = [row[1] for row in count_by_truncated_hash3_uuid1.collect()]
ecdf = ECDF(np.array(uuid1_count_list))
plt.figure(figsize = (14, 8))
plt.plot(ecdf.x,ecdf.y)
plt.show()
EDIT: I added the histogram. As you can see below, it looks more like normal distribution.
plt.figure(figsize = (14, 8))
plt.hist(uuid1_count_list)
plt.title('Histogram of counts in each truncated hash')
plt.show()
Here is a quick-and-dirty way to demonstrate this:
import hashlib
import matplotlib.pyplot as plt
import numpy as np
import random
def random_string(n):
"""Returns a uniformly distributed random string of length n."""
return ''.join(chr(random.randint(0, 255)) for _ in range(n))
# Generate 100K random strings
data = [random_string(10) for _ in range(100000)]
# Compute MD5 hashes
md5s = [hashlib.md5(d.encode()).digest() for d in data]
# Truncate each MD5 to the first three characters and convert to int
truncated_md5s = [md5[0] * 0x10000 + md5[1] * 0x100 + md5[2] for md5 in md5s]
# (Rather crudely) compute and plot the ECDF
hist = np.histogram(truncated_md5s, bins=1000)
plt.plot(hist[1], np.cumsum([0] + list(hist[0])))