This interactive notebook demonstrates how a polynomial rolling hash processes a text to generate hash values for fixed-size substrings. The rolling hash algorithm efficiently computes the hash of each sliding window using previously computed values, making it especially useful in applications such as:
Duplicate content detection
Plagiarism detection
Malware signature scanning
String search algorithms like Rabin-Karp
In this demo, you can:
Input a custom text
Adjust the window size for substring analysis
Choose the base and modulus used in the hash function
The table below shows:
The computed hash values
How many times each hash appears
The (unique) substrings associated with each hash
When two or more different substrings yield the same hash, a collision occurs — an interesting and sometimes problematic property of hashing algorithms. Here, you can explore how often and under what parameters such collisions appear.
from%20collections%20import%20defaultdict%0Aimport%20unicodedata%0Aimport%20re%0A%0A%0Adef%20rolling_hashes(text%2C%20window_size%2C%20base%2C%20mod)%3A%0A%20%20%20%20hashes%20%3D%20%5B%5D%0A%20%20%20%20hash_val%20%3D%200%0A%20%20%20%20power%20%3D%201%0A%0A%20%20%20%20%23%20Precompute%20base%5E(window_size-1)%0A%20%20%20%20for%20i%20in%20range(window_size%20-%201)%3A%0A%20%20%20%20%20%20%20%20power%20%3D%20(power%20*%20base)%20%25%20mod%0A%0A%20%20%20%20for%20i%20in%20range(len(text)%20-%20window_size%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Compute%20initial%20hash%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20j%20in%20range(window_size)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20hash_val%20%3D%20(hash_val%20*%20base%20%2B%20character_to_number(text%5Bj%5D))%20%25%20mod%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Rolling%20update%0A%20%20%20%20%20%20%20%20%20%20%20%20hash_val%20%3D%20((hash_val%20-%20character_to_number(text%5Bi%20-%201%5D)%20*%20power)%20*%20base%20%2B%20character_to_number(text%5Bi%20%2B%20window_size%20-%201%5D))%20%25%20mod%0A%20%20%20%20%20%20%20%20%20%20%20%20hash_val%20%3D%20(hash_val%20%2B%20mod)%20%25%20mod%20%20%23%20ensure%20non-negative%0A%0A%20%20%20%20%20%20%20%20substr%20%3D%20text%5Bi%3Ai%20%2B%20window_size%5D%0A%20%20%20%20%20%20%20%20hashes.append((hash_val%2C%20substr))%0A%0A%20%20%20%20return%20hashes%0A%0A%0Adef%20normalize_text(text)%3A%0A%20%20%20%20text%20%3D%20unicodedata.normalize(%22NFKD%22%2C%20text)%0A%20%20%20%20text%20%3D%20''.join(c%20for%20c%20in%20text%20if%20not%20unicodedata.combining(c))%0A%20%20%20%20text%20%3D%20text.lower()%0A%20%20%20%20text%20%3D%20re.sub(r%22%5B%5Ea-z%20%5D%2B%22%2C%20%22%22%2C%20text)%0A%20%20%20%20return%20text%0A%0A%0Adef%20character_to_number(x%3A%20str)%3A%0A%20%20%20%20%20%20%20%20return%20ord(x)
import%20marimo%20as%20mo%0A%0A%0Atext_input%20%3D%20mo.ui.text_area(value%3D%22To%20be%2C%20or%20not%20to%20be%2C%20that%20is%20the%20question.%22%2C%20label%3D%22Input%20text%22%2C%20full_width%3DTrue)%0Anormalize_input%20%3D%20mo.ui.checkbox(value%3DTrue%2C%20label%3D%22Normalize%20text%20(convert%20to%20lowercase%2C%20remove%20accents%20and%20non-alpha%20characters)%22)%0Awindow_size_input%20%3D%20mo.ui.number(label%3D%22Window%20Size%22%2C%20value%3D5%2C%20start%3D1%2C%20stop%3D10)%0Abase_input%20%3D%20mo.ui.number(label%3D%22Base%22%2C%20value%3D31%2C%20start%3D1)%0Amod_input%20%3D%20mo.ui.number(label%3D%22Modulus%22%2C%20value%3D101%2C%20start%3D1)%0Amapping_input%20%3D%20mo.ui.dropdown(label%3D%22Mapping%22%2C%20value%3D%22ASCII%20Code%22%2C%20options%3D%5B%22ASCII%20Code%22%2C%20%22Custom%20Function%22%5D)%0Amapping_code%20%3D%20mo.ui.code_editor(%22%22%22CHAR_TO_NUM%20%3D%20%7B%0A%20%20%20%20%22%20%22%3A%200%2C%0A%20%20%20%20%22a%22%3A%201%2C%0A%20%20%20%20%22b%22%3A%202%2C%0A%20%20%20%20%22c%22%3A%203%2C%0A%20%20%20%20%22d%22%3A%204%2C%0A%20%20%20%20%22e%22%3A%205%2C%0A%20%20%20%20%22f%22%3A%206%2C%0A%20%20%20%20%22g%22%3A%207%2C%0A%20%20%20%20%22h%22%3A%208%2C%0A%20%20%20%20%22i%22%3A%209%2C%0A%20%20%20%20%22j%22%3A%2010%2C%0A%20%20%20%20%22k%22%3A%2011%2C%0A%20%20%20%20%22l%22%3A%2012%2C%0A%20%20%20%20%22m%22%3A%2013%2C%0A%20%20%20%20%22n%22%3A%2014%2C%0A%20%20%20%20%22o%22%3A%2015%2C%0A%20%20%20%20%22p%22%3A%2016%2C%0A%20%20%20%20%22q%22%3A%2017%2C%0A%20%20%20%20%22r%22%3A%2018%2C%0A%20%20%20%20%22s%22%3A%2019%2C%0A%20%20%20%20%22t%22%3A%2020%2C%0A%20%20%20%20%22u%22%3A%2021%2C%0A%20%20%20%20%22v%22%3A%2022%2C%0A%20%20%20%20%22w%22%3A%2023%2C%0A%20%20%20%20%22x%22%3A%2024%2C%0A%20%20%20%20%22y%22%3A%2025%2C%0A%20%20%20%20%22z%22%3A%2026%0A%7D%0A%0Adef%20character_to_number(x)%3A%0A%20%20%20%20return%20CHAR_TO_NUM%5Bx%5D%22%22%22%2C%20theme%3D%22dark%22)
ui_elements%20%3D%20%5Btext_input%2C%20normalize_input%2C%20window_size_input%2C%20base_input%2C%20mod_input%2C%20mapping_input%5D%0Aif%20mapping_input.value%20%3D%3D%20%22Custom%20Function%22%3A%0A%20%20%20%20ui_elements.append(mapping_code)%0Amo.vstack(ui_elements)
text%20%3D%20text_input.value%0Aif%20normalize_input.value%3A%0A%20%20%20%20text%20%3D%20normalize_text(text)%0Aif%20mapping_input.value%20%3D%3D%20%22Custom%20Function%22%3A%0A%20%20%20%20exec(mapping_code.value)%0Aelse%3A%0A%20%20%20%20exec(%22character_to_number%20%3D%20lambda%20x%3A%20ord(x)%22)%0Awindow_size%20%3D%20int(window_size_input.value)%0Abase%20%3D%20int(base_input.value)%0Amod%20%3D%20int(mod_input.value)%0A%0Ahash_results%20%3D%20rolling_hashes(text%2C%20window_size%2C%20base%2C%20mod)%0A%0Ahash_map%20%3D%20defaultdict(set)%0Afor%20h%2C%20substr%20in%20hash_results%3A%0A%20%20%20%20hash_map%5Bh%5D.add(substr)%0A%0Atable%20%3D%20%5B%0A%20%20%20%20%7B%22Hash%22%3A%20h%2C%20%22Number%20Unique%20Values%22%3A%20len(subs)%2C%20%22Unique%20Values%22%3A%20subs%7D%0A%20%20%20%20for%20h%2C%20subs%20in%20hash_map.items()%0A%5D%0Atable%20%3D%20sorted(table%2C%20key%3Dlambda%20x%3A%20x%5B%22Number%20Unique%20Values%22%5D%2C%20reverse%3DTrue)%0Ahashes_with_collisions%20%3D%20sum(1%20for%20i%20in%20table%20if%20i%5B%22Number%20Unique%20Values%22%5D%20%3E%201)%0Anum_collisions%20%3D%20sum(i%5B%22Number%20Unique%20Values%22%5D%20for%20i%20in%20table%20if%20i%5B%22Number%20Unique%20Values%22%5D%20%3E%201)%0Anum_substrings%20%3D%20sum(i%5B%22Number%20Unique%20Values%22%5D%20for%20i%20in%20table)
Execution Results
mo.md(%0A%20%20%20%20f%22%22%22%0A**Total%20number%20of%20colliding%20hashes%3A**%20%7Bhashes_with_collisions%7D%0A**Total%20number%20of%20unique%20substrings%20involved%20in%20collisions%3A**%20%7Bnum_collisions%7D%0A**Total%20number%20of%20unique%20substrings**%3A%20%7Bnum_substrings%7D%0A%22%22%22%0A)
Below is the list of hashes and their associated values, sorted in descending order by the number of unique substrings that produce each hash.
mo.ui.table(table)
import%20anywidget%0Aimport%20traitlets%0A%0Aclass%20SlidingWindowWidget(anywidget.AnyWidget)%3A%0A%20%20%20%20_esm%20%3D%20r%22%22%22%0A%20%20%20%20import%20%7B%20gsap%20%7D%20from%20%22https%3A%2F%2Fcdn.skypack.dev%2Fgsap%22%3B%0A%20%20%20%20import%20%7B%20GSDevTools%20%7D%20from%20%22https%3A%2F%2Fcdn.skypack.dev%2Fgsap%2FGSDevTools%22%3B%0A%0A%20%20%20%20gsap.registerPlugin(GSDevTools)%3B%0A%0A%20%20%20%20function%20render(%7B%20model%2C%20el%20%7D)%20%7B%0A%20%20%20%20%20%20%20%20const%20text%20%3D%20model.get(%22text%22)%20%7C%7C%20%22hello%20world%22%3B%0A%20%20%20%20%20%20%20%20const%20windowSize%20%3D%20model.get(%22window_size%22)%20%7C%7C%204%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Limpiar%20contenido%0A%20%20%20%20%20%20%20%20el.innerHTML%20%3D%20%22%22%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Contenedor%20general%0A%20%20%20%20%20%20%20%20const%20container%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20container.style.display%20%3D%20%22flex%22%3B%0A%20%20%20%20%20%20%20%20container.style.position%20%3D%20%22relative%22%3B%0A%20%20%20%20%20%20%20%20container.style.gap%20%3D%20%226px%22%3B%0A%20%20%20%20%20%20%20%20container.style.fontFamily%20%3D%20%22monospace%22%3B%0A%20%20%20%20%20%20%20%20container.style.fontSize%20%3D%20%2220px%22%3B%0A%20%20%20%20%20%20%20%20container.style.marginBottom%20%3D%20%2220px%22%3B%0A%20%20%20%20%20%20%20%20container.style.padding%20%3D%20%2210px%22%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Dibujar%20caracteres%0A%20%20%20%20%20%20%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20text.length%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20span%20%3D%20document.createElement(%22span%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20span.textContent%20%3D%20text%5Bi%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20span.style.fontFamily%20%3D%20%22monospace%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20span.style.padding%20%3D%20%228px%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20span.style.border%20%3D%20%221px%20solid%20%23aaa%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20span.style.minWidth%20%3D%20%2220px%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20span.style.textAlign%20%3D%20%22center%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20container.appendChild(span)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Crear%20overlay%0A%20%20%20%20%20%20%20%20const%20overlay%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20overlay.style.position%20%3D%20%22absolute%22%3B%0A%20%20%20%20%20%20%20%20overlay.style.top%20%3D%20%220%22%3B%0A%20%20%20%20%20%20%20%20overlay.style.left%20%3D%20%220%22%3B%0A%20%20%20%20%20%20%20%20overlay.style.width%20%3D%20%60%24%7BwindowSize%20*%2038%7Dpx%60%3B%0A%20%20%20%20%20%20%20%20overlay.style.height%20%3D%20%2272px%22%3B%0A%20%20%20%20%20%20%20%20overlay.style.border%20%3D%20%222px%20solid%20red%22%3B%0A%20%20%20%20%20%20%20%20overlay.style.backgroundColor%20%3D%20%22rgba(255%2C%200%2C%200%2C%200.1)%22%3B%0A%20%20%20%20%20%20%20%20overlay.style.zIndex%20%3D%20%2210%22%3B%0A%20%20%20%20%20%20%20%20overlay.style.pointerEvents%20%3D%20%22none%22%3B%0A%20%20%20%20%20%20%20%20container.appendChild(overlay)%3B%0A%0A%20%20%20%20%20%20%20%20el.appendChild(container)%3B%0A%20%20%20%20%20%20%20%20const%20timeline%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20timeline.id%20%3D%20%22mycon%22%3B%0A%20%20%20%20%20%20%20%20el.appendChild(timeline)%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Crear%20animaci%C3%B3n%20con%20GSAP%0A%20%20%20%20%20%20%20%20const%20tl%20%3D%20gsap.timeline(%7B%20paused%3A%20true%20%7D)%3B%0A%0A%20%20%20%20%20%20%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%3D%20text.length%20-%20windowSize%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20console.log%0A%20%20%20%20%20%20%20%20%20%20%20%20tl.to(overlay%2C%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%3A%20i%20*%2034%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20duration%3A%200.5%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ease%3A%20%22power1.inOut%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20DevTools%20para%20control%20manual%0A%20%20%20%20%20%20%20%20setTimeout(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20GSDevTools.create(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fid%3A%20%22%23myid%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20animation%3A%20tl%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20container%3A%20timeline%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20css%3A%20%7Bposition%3A%20%22relative%22%7D%0A%20%20%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20%7D%2C%202000)%3B%0A%20%20%20%20%20%20%20%20tl.play()%3B%0A%20%20%20%20%7D%0A%20%20%20%20export%20default%20%7B%20render%20%7D%3B%0A%20%20%20%20%22%22%22%0A%0A%20%20%20%20text%20%3D%20traitlets.Unicode(%22rolling%20hash%20demo%22).tag(sync%3DTrue)%0A%20%20%20%20window_size%20%3D%20traitlets.Int(5).tag(sync%3DTrue)
SlidingWindowWidget(text%3D%22Cheese%20Bytes%20Demo%22%2C%20window_size%3D5)