← All projects

Algorithms · Search applications

Steam Game Similarity.

Random-hyperplane hashing, connected to an interactive search experience.

Open the interactive app inside this portfolio. A recorded demo is also available.

My role
LSH implementation · Application integration
Collaboration
Two-person project
Status
Interactive demo available
Steam Game Similarity architecture. The implemented LSH query path. Hash buckets select candidates before cosine reranking; the candidate set can still include the full dataset.
The implemented LSH query path. Hash buckets select candidates before cosine reranking; the candidate set can still include the full dataset. Open full-size diagram ↗

Diagram description. Conceptual source-based data flow. A query vector is hashed using default settings of ten tables and sixteen random hyperplanes per table. Bucket entries are unioned into unique candidates, then cosine similarities are computed using normalized game vectors. The implementation fully sorts candidate scores and returns recommendations, explicitly excluding the query ID. This is not a benchmark or a screenshot of measured data.

My contribution

I implemented the LSH path and interface integration. My teammate built the random-projection forest and data preprocessing.

Making an algorithm usable

Steam Game Similarity is an interactive comparison of three approaches to finding related games: brute-force cosine search, a random-projection forest and random-hyperplane locality-sensitive hashing. The application turns those methods into an understandable workflow: choose a game, select a search method and inspect the recommendations.

I implemented the LSH path and the Streamlit interface and integration helpers. My teammate Colby developed the preprocessing and RP Forest. Keeping those responsibilities explicit matters: the shared application compares three methods, while my main algorithmic contribution is the hash-based candidate search. My neighbor-search implementation, LSH source, application helpers.

Retrieve candidates, then compare them precisely

Each game is represented by a normalized feature vector. My LSH implementation creates several sets of random hyperplanes. For each table, the signs of a vector's dot products with those planes form a hash key; vectors with the same key enter the same bucket. Hash construction.

A query follows the same hashing path, collects its matching buckets and deduplicates their members. It then computes cosine similarity for those candidates, sorts the scores and returns the nearest results. The index-based query explicitly removes the input game's ID. This keeps candidate retrieval separate from final ranking and gives both stages a clear responsibility. Query implementation.

The tradeoff is approximation. A relevant game can fall outside every retrieved bucket, and a small candidate set can return fewer than the requested number of results. Adding tables can improve the chance of retrieving a neighbor while increasing index size and query work. Changing the number of planes affects how selectively the buckets divide the data. These choices need to be evaluated together rather than reduced to a single latency number.

What the implementation costs

Let N be the number of games, d the vector dimension, T the table count and P the planes per table. Construction performs O(NTPd) projection work, alongside expected hash-table operations. The inspected defaults are 10 tables and 16 planes per table. LSH construction and defaults.

At query time, let B be the total bucket entries visited before deduplication and C the number of unique candidates. Projection, retrieval and scoring contribute O(TPd + B + Cd). The code then uses a full NumPy sort of the candidate scores, typically adding O(C log C) work. It does not use a top-k heap.

These are source-derived estimates, not benchmark results or a guarantee of sublinear search. If buckets become broad enough that C approaches N, much of the advantage disappears. Memory also includes projection vectors, per-table index entries and bucket keys in addition to the original feature matrix. Implementation details.

The application changes the timing question

The Streamlit helpers cache approximate index construction so interactive queries can reuse it. The brute-force path, however, reloads the matrix and fits a nearest-neighbor model within its helper call. As a result, the displayed timings mix different amounts of preparation and query work. They should not be interpreted as a controlled speedup comparison. Caching and query helpers, loading and brute-force path.

That distinction is useful engineering context: a responsive product workflow and a fair algorithm benchmark answer different questions. A stronger comparison would preload the same data, separate construction from queries, fix seeds and query sets, and measure recall@k alongside candidate counts, memory and latency distributions. The current LSH projections do not use an explicit fixed seed.

Demonstration and remaining work

The live application provides an interactive path through the search methods. A recorded demonstration is available when the hosted app is waking or unavailable. The recording shows the application workflow; it is not a substitute for a repeatable benchmark.

The repository's main evidence is its implementation and demonstration, rather than an automated regression suite. Useful next checks include empty candidate sets, duplicate vectors, self-exclusion and recall against exact neighbors. The project gives me a concrete way to discuss algorithm design, application integration and why a measurement's setup matters as much as its displayed result. Audited source revision.

Source evidence

Implementation and recorded evidence checked on November 5, 2026.

Keep exploringLitter to Life