From 0f13456e33eea094b5a26b175c4a8d9c9fd3856a Mon Sep 17 00:00:00 2001 From: Samuel Shuert Date: Fri, 24 Oct 2025 16:03:00 +0000 Subject: [PATCH] feat: search engine Co-authored-by: Max Mahn --- ts/searchEngine/index.test.ts | 65 +++++++++++++++++++++++++++++++++++ ts/searchEngine/index.ts | 20 +++++++++++ 2 files changed, 85 insertions(+) diff --git a/ts/searchEngine/index.test.ts b/ts/searchEngine/index.test.ts index 083b0d2..f79d7f2 100644 --- a/ts/searchEngine/index.test.ts +++ b/ts/searchEngine/index.test.ts @@ -67,3 +67,68 @@ describe("Search Index", () => { expect(index.getPagesForKeyword("pineapples")).toBeArrayOfSize(2); }); }); + +describe("Search Algorithm", () => { + let index: SearchIndex; + beforeEach(() => { + index = new SearchIndex(); + index.addPage( + "https://www.beans.com", + "beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans", + ); + index.addPage("https://www.beans-are-ok.com", "beans are ok I guess"); + index.addPage( + "https://www.example.com/cats", + "This is a sample web page about cats", + ); + index.addPage( + "https://www.example.com/dogs", + "This is a sample web page about dogs and training", + ); + index.addPage( + "https://www.training.com", + "This is a general training website", + ); + index.addPage( + "https://www.pineapple-world.com", + "We have lots of pineapples. You've never seen this many pineapples before.", + ); + index.addPage( + "https://www.pineapple-is-my-favorite-fruit.com", + "I love pineapples, it's all I eat. I mean I REALLY LOVE PINEAPPLES", + ); + index.addPage( + "https://www.example.com/ml", + "This is a page about machine learning", + ); + }); + + it("should return relevant pages for a single keyword search", () => { + const results = index.search("cats"); + expect(results).toContain("https://www.example.com/cats"); + }); + + it("should return relevant pages for a multi keyword search", () => { + const results = index.search("dogs training"); + expect(results).toContainAllValues([ + "https://www.example.com/dogs", + "https://www.training.com", + ]); + expect(results.indexOf("https://www.example.com/dogs")).toBe(0); + expect(results.indexOf("https://www.training.com")).toBe(1); + }); + + it("should return relevant pages for a phrase search", () => { + const results = index.search("machine learning"); + expect(results).toContain("https://www.example.com/ml"); + }); + + it("should rank results properly by relevance", () => { + const results = index.search("beans"); + expect(results.indexOf("https://www.beans.com")).toBe(0); + expect(results.indexOf("https://www.beans-are-ok.com")).toBe(1); + const results2 = index.search("beans beans"); + expect(results2.indexOf("https://www.beans.com")).toBe(0); + expect(results2.indexOf("https://www.beans-are-ok.com")).toBe(1); + }); +}); diff --git a/ts/searchEngine/index.ts b/ts/searchEngine/index.ts index d0f8a8f..f9c80e4 100644 --- a/ts/searchEngine/index.ts +++ b/ts/searchEngine/index.ts @@ -178,4 +178,24 @@ export class SearchIndex { .sort((a, b) => a[1] - b[1]) .map(([url, _]) => url); } + + search(query: string): string[] { + const urls = new Map(); + const keys = this.extractKeywords(query); + for (const [key, count] of keys.entries()) { + const pages = this.index.get(key); + if (!pages) continue; + for (const [url, v] of pages) { + urls.set( + url, + urls.has(url) + ? (urls.get(url) || 0) + v * count + : v * count, + ); + } + } + return Array.from(urls.entries()) + .sort((a, b) => b[1] - a[1]) + .map(([url, _]) => url); + } } -- 2.51.2