diff --git a/app/glossary/[term]/not-found.tsx b/app/glossary/[term]/not-found.tsx new file mode 100644 index 0000000..031b0bb --- /dev/null +++ b/app/glossary/[term]/not-found.tsx @@ -0,0 +1,95 @@ +import Link from "next/link"; +import PageLayout from "@/components/layout/PageLayout"; + +export default function TermNotFound() { + return ( + +
+
+ + + +

+ Term Not Found +

+

+ The glossary term you are looking for doesn't exist or has been + moved. +

+
+ + Browse Glossary + + + Return Home + +
+
+ +
+

+ Looking for algorithm concepts? +

+

+ Our glossary covers key algorithmic concepts like Big O notation, + time complexity, space complexity, and more. +

+
+ + Algorithm + + + Big O Notation + + + Time Complexity + + + Space Complexity + + + Sorting + + + Searching + +
+
+
+
+ ); +} diff --git a/app/glossary/[term]/page.tsx b/app/glossary/[term]/page.tsx new file mode 100644 index 0000000..5a8e529 --- /dev/null +++ b/app/glossary/[term]/page.tsx @@ -0,0 +1,187 @@ +import { notFound } from "next/navigation"; +import PageLayout from "@/components/layout/PageLayout"; +import Link from "next/link"; +import { + getGlossaryTermBySlug, + getRelatedTerms, +} from "@/lib/glossary/glossary"; +import { Metadata } from "next"; + +type TermPageProps = { + params: { + term: string; + }; +}; + +export async function generateMetadata({ + params, +}: TermPageProps): Promise { + const term = getGlossaryTermBySlug(params.term); + + if (!term) { + return { + title: "Term Not Found | Algorithm Visualizer Glossary", + description: "The requested glossary term could not be found.", + }; + } + + return { + title: `${term.term} - Algorithm Glossary`, + description: term.shortDefinition, + keywords: [ + term.term.toLowerCase(), + "algorithm", + term.category, + "computer science", + "programming", + "algorithm terminology", + ...(term.keywords || []), + ], + }; +} + +export default function GlossaryTermPage({ params }: TermPageProps) { + const term = getGlossaryTermBySlug(params.term); + + if (!term) { + notFound(); + } + + const relatedTerms = getRelatedTerms(term.slug); + + return ( + +
+ + +
+
+
+ + {term.category} + +

{term.term}

+
+ + + + + + Back to Glossary + +
+ +
+
+
+ + {term.examples && ( +
+

Examples

+
+
    + {term.examples.map((example, index) => ( +
  • {example}
  • + ))} +
+
+
+ )} + + {term.codeExample && ( +
+

Code Example

+
+
+                  {term.codeExample}
+                
+
+
+ )} + + {term.visualAid && ( +
+

Visual Explanation

+
+
+
+
+ )} +
+ + {relatedTerms.length > 0 && ( +
+

Related Terms

+
+ {relatedTerms.map((relatedTerm) => ( + +

{relatedTerm.term}

+

+ {relatedTerm.shortDefinition} +

+ + ))} +
+
+ )} + +
+

Further Learning

+

+ Want to see these concepts in action? +

+
+ + Explore Sorting Algorithms + + + Explore Searching Algorithms + + + Explore Graph Algorithms + +
+
+
+ + ); +} diff --git a/app/glossary/page.tsx b/app/glossary/page.tsx new file mode 100644 index 0000000..46b7815 --- /dev/null +++ b/app/glossary/page.tsx @@ -0,0 +1,192 @@ +"use client"; + +import { useState, useEffect } from "react"; +import PageLayout from "@/components/layout/PageLayout"; +import Link from "next/link"; +import { glossaryTerms } from "@/lib/glossary/glossary"; +import { groupTermsByFirstLetter } from "@/lib/utils"; + +export default function GlossaryPage() { + const [searchTerm, setSearchTerm] = useState(""); + const [filteredTerms, setFilteredTerms] = useState(glossaryTerms); + const [activeCategory, setActiveCategory] = useState("all"); + + // Group terms alphabetically + const groupedTerms = groupTermsByFirstLetter(filteredTerms); + + // Get all unique categories from glossary terms + const categories = Array.from( + new Set(glossaryTerms.map((term) => term.category)) + ); + + // Effect for filtering terms based on search and category + useEffect(() => { + let results = glossaryTerms; + + // Filter by category if not "all" + if (activeCategory !== "all") { + results = results.filter((term) => term.category === activeCategory); + } + + // Filter by search term + if (searchTerm) { + const lowercaseSearch = searchTerm.toLowerCase(); + results = results.filter( + (term) => + term.term.toLowerCase().includes(lowercaseSearch) || + term.shortDefinition.toLowerCase().includes(lowercaseSearch) + ); + } + + setFilteredTerms(results); + }, [searchTerm, activeCategory]); + + return ( + +
+ {/* Search and filter section */} +
+
+ +
+ setSearchTerm(e.target.value)} + /> +
+ + + +
+
+
+ + {/* Category filters */} +
+

+ Filter by Category +

+
+ + {categories.map((category) => ( + + ))} +
+
+
+ + {/* Alphabetical navigation */} +
+ {Object.keys(groupedTerms) + .sort() + .map((letter) => ( + + {letter} + + ))} +
+ + {/* Results count */} +
+ {filteredTerms.length === 0 ? ( +

No terms found matching your search.

+ ) : ( +

+ {filteredTerms.length}{" "} + {filteredTerms.length === 1 ? "term" : "terms"} found +

+ )} +
+ + {/* Glossary terms by alphabet */} + {Object.keys(groupedTerms) + .sort() + .map((letter) => ( +
+

+ {letter} +

+
+ {groupedTerms[letter].map((term) => ( + +

{term.term}

+

+ {term.shortDefinition} +

+
+ + {term.category} + + + Learn more + + + + +
+ + ))} +
+
+ ))} +
+
+ ); +} diff --git a/app/sitemap.ts b/app/sitemap.ts index e23369e..6448f2a 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,6 +1,7 @@ import { MetadataRoute } from "next"; import { availableAlgorithms } from "@/lib/algorithms/metadata"; import { APP_URL } from "@/constants/URL"; +import { glossaryTerms } from "@/lib/glossary/glossary"; export default function sitemap(): MetadataRoute.Sitemap { const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || APP_URL; @@ -43,6 +44,12 @@ export default function sitemap(): MetadataRoute.Sitemap { changeFrequency: "weekly", priority: 0.9, }, + { + url: `${baseUrl}/glossary`, + lastModified: new Date(), + changeFrequency: "monthly", + priority: 0.8, + }, { url: `${baseUrl}/difficulty/easy`, lastModified: new Date(), @@ -63,7 +70,6 @@ export default function sitemap(): MetadataRoute.Sitemap { }, ]; - // Generate algorithm-specific pages const algorithmPages: MetadataRoute.Sitemap = Object.entries( availableAlgorithms ).map((algorithm) => { @@ -75,5 +81,15 @@ export default function sitemap(): MetadataRoute.Sitemap { }; }); - return [...staticPages, ...algorithmPages]; + // Generate glossary term pages + const glossaryPages: MetadataRoute.Sitemap = glossaryTerms.map((term) => { + return { + url: `${baseUrl}/glossary/${term.slug}`, + lastModified: new Date(), + changeFrequency: "monthly", + priority: 0.7, + }; + }); + + return [...staticPages, ...algorithmPages, ...glossaryPages]; } diff --git a/components/glossary/GlossaryItem.tsx b/components/glossary/GlossaryItem.tsx new file mode 100644 index 0000000..4cf5df4 --- /dev/null +++ b/components/glossary/GlossaryItem.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import Link from "next/link"; + +interface GlossaryItemProps { + term: string; + definition: string | React.ReactNode; + examples?: string[] | React.ReactNode; + relatedTerms?: Array<{ + term: string; + link?: string; + }>; +} + +export default function GlossaryItem({ + term, + definition, + examples, + relatedTerms, +}: GlossaryItemProps) { + // Create an ID from the term for anchor linking + const termId = term.toLowerCase().replace(/\s+/g, "-"); + + return ( +
+

+ {term} + + + + + +

+ +
{definition}
+ + {examples && ( +
+

Examples

+ {Array.isArray(examples) ? ( +
    + {examples.map((example, index) => ( +
  • {example}
  • + ))} +
+ ) : ( + examples + )} +
+ )} + + {relatedTerms && relatedTerms.length > 0 && ( +
+

Related Terms

+
+ {relatedTerms.map((relatedTerm, index) => ( + + {relatedTerm.term} + + ))} +
+
+ )} +
+ ); +} diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx index 2c8a55c..83ae3a8 100644 --- a/components/layout/Navbar.tsx +++ b/components/layout/Navbar.tsx @@ -61,6 +61,7 @@ const navLinks = [ { href: "/searching", label: "Searching" }, { href: "/graph", label: "Graph" }, { href: "/difficulty", label: "Difficulty" }, + { href: "/glossary", label: "Glossary" }, ]; export default function Navbar() { diff --git a/lib/glossary/glossary.ts b/lib/glossary/glossary.ts new file mode 100644 index 0000000..ea8127f --- /dev/null +++ b/lib/glossary/glossary.ts @@ -0,0 +1,614 @@ +export interface GlossaryTerm { + slug: string; + term: string; + category: string; + shortDefinition: string; + fullDefinition: string; + examples?: string[]; + codeExample?: string; + visualAid?: string; + relatedTerms?: string[]; // Array of term slugs + keywords?: string[]; +} + +export const glossaryTerms: GlossaryTerm[] = [ + { + slug: "algorithm", + term: "Algorithm", + category: "fundamental", + shortDefinition: + "A step-by-step procedure for solving a problem or accomplishing a task.", + fullDefinition: ` +

An algorithm is a finite sequence of well-defined, computer-implementable instructions, typically used to solve a class of problems or to perform a computation.

+ +

Algorithms are unambiguous specifications for performing calculations, data processing, automated reasoning, and other tasks. They form the foundation of everything we do in computer science and programming.

+ +

A good algorithm generally has the following characteristics:

+
    +
  • Correctness: It should solve the problem it was designed to solve
  • +
  • Efficiency: It should use computational resources optimally
  • +
  • Finiteness: It must terminate after a finite number of steps
  • +
  • Deterministic: Given the same input, it should always produce the same output
  • +
+ +

Algorithms can be expressed in many ways, including natural language, pseudocode, flowcharts, and programming languages. The efficiency of algorithms is typically measured in terms of their time complexity (how long they take to run) and space complexity (how much memory they require).

+ `, + examples: [ + "Sorting algorithms arrange items in a specific order (e.g., Bubble Sort, Quick Sort)", + "Search algorithms locate items within a data structure (e.g., Binary Search)", + "Graph algorithms find paths, connectivity, or properties of graphs (e.g., Dijkstra's algorithm)", + "String matching algorithms find patterns in text (e.g., Boyer-Moore algorithm)", + ], + relatedTerms: [ + "big-o-notation", + "time-complexity", + "space-complexity", + "pseudocode", + ], + keywords: ["computation", "procedure", "process", "method", "technique"], + }, + { + slug: "big-o-notation", + term: "Big O Notation", + category: "analysis", + shortDefinition: + "A mathematical notation that describes the limiting behavior of a function when the argument tends towards infinity.", + fullDefinition: ` +

Big O Notation is a mathematical notation used in computer science to describe the performance or complexity of an algorithm. Specifically, it describes the worst-case scenario and can be used to describe the execution time or space requirements of an algorithm.

+ +

The "O" in Big O notation stands for "Order of," which indicates the rate of growth of an algorithm. This notation characterizes functions according to their growth rates: different functions with the same growth rate may be represented using the same O notation.

+ +

When analyzing algorithms, we're primarily concerned with how they scale - that is, how their performance changes as the input size grows. Big O notation allows us to express this relationship mathematically, ignoring constants and lower-order terms that become insignificant with large inputs.

+ +

Common Big O Complexities (from fastest to slowest):

+
    +
  • O(1) - Constant time: The operation takes the same amount of time regardless of the input size
  • +
  • O(log n) - Logarithmic time: Time increases logarithmically with input size
  • +
  • O(n) - Linear time: Time increases linearly with input size
  • +
  • O(n log n) - Linearithmic time: Common for efficient sorting algorithms
  • +
  • O(n²) - Quadratic time: Often seen in algorithms with nested loops
  • +
  • O(n³) - Cubic time: Typically found in algorithms with three nested loops
  • +
  • O(2^n) - Exponential time: Each additional element doubles the processing time
  • +
  • O(n!) - Factorial time: Used in algorithms that generate all permutations
  • +
+ `, + codeExample: `// O(1) - Constant time +function getFirstElement(array) { + return array[0]; +} + +// O(n) - Linear time +function findMax(array) { + let max = array[0]; + for (let i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; +} + +// O(n²) - Quadratic time +function bubbleSort(array) { + for (let i = 0; i < array.length; i++) { + for (let j = 0; j < array.length - i - 1; j++) { + if (array[j] > array[j + 1]) { + [array[j], array[j + 1]] = [array[j + 1], array[j]]; + } + } + } + return array; +}`, + visualAid: ` +
+
+

Growth Rate Comparison

+
+
+
+
+
+
+
+
+
+
+
+
+
O(1)
+
O(log n)
+
O(n)
+
O(n log n)
+
O(n²)
+
O(2^n)
+
O(n!)
+
+
+
+ `, + relatedTerms: [ + "time-complexity", + "space-complexity", + "asymptotic-analysis", + "algorithm", + ], + keywords: [ + "complexity", + "algorithm analysis", + "efficiency", + "performance", + "computational complexity", + ], + }, + { + slug: "time-complexity", + term: "Time Complexity", + category: "analysis", + shortDefinition: + "A measure of the amount of time an algorithm takes to complete as a function of the length of the input.", + fullDefinition: ` +

Time complexity is a computational concept that measures the amount of time an algorithm takes to complete as a function of the length of the input. It provides a way to express how the runtime of an algorithm grows as the size of the input increases.

+ +

When analyzing time complexity, we're usually concerned with the worst-case scenario—the maximum amount of time an algorithm could take given any valid input of size n. However, we sometimes also consider:

+ +
    +
  • Best-case complexity: The minimum time required for an algorithm (often not very useful)
  • +
  • Average-case complexity: The expected time for a typical input
  • +
  • Amortized complexity: The time per operation, averaged over a sequence of operations
  • +
+ +

Time complexity is commonly expressed using Big O notation, which gives us an asymptotic upper bound on the growth rate of the algorithm's runtime. This means we focus on how the algorithm scales with large inputs, rather than the exact number of operations for a specific input size.

+ +

When calculating time complexity, we generally follow these principles:

+
    +
  • Ignore constants: O(2n) is simplified to O(n)
  • +
  • Keep only the highest-order term: O(n² + n) is simplified to O(n²)
  • +
  • Focus on the dominant operations (usually comparisons or iterations)
  • +
+ `, + examples: [ + "O(1) - Constant time: Array access, basic arithmetic operations", + "O(log n) - Logarithmic time: Binary search, operations in balanced binary search trees", + "O(n) - Linear time: Linear search, traversing an array or linked list", + "O(n log n) - Linearithmic time: Efficient sorting algorithms like Merge Sort and Quick Sort", + "O(n²) - Quadratic time: Simple sorting algorithms like Bubble Sort, nested loops processing each element", + ], + relatedTerms: [ + "big-o-notation", + "space-complexity", + "algorithmic-efficiency", + ], + keywords: [ + "runtime", + "performance", + "efficiency", + "computational complexity", + "algorithmic analysis", + ], + }, + { + slug: "space-complexity", + term: "Space Complexity", + category: "analysis", + shortDefinition: + "A measure of the amount of memory an algorithm uses as a function of the length of the input.", + fullDefinition: ` +

Space complexity is a measure of the amount of memory or storage space that an algorithm requires as a function of the input size. It quantifies how much additional memory the algorithm needs to complete its execution beyond the space needed to store the input.

+ +

When analyzing space complexity, we consider:

+ +
    +
  • Auxiliary space: The extra space used by the algorithm (excluding input size)
  • +
  • Total space: The sum of auxiliary space and input size
  • +
+ +

Like time complexity, space complexity is typically expressed using Big O notation, representing the worst-case space usage. This helps us understand how an algorithm's memory requirements scale with larger inputs.

+ +

Space complexity considers various memory usages:

+
    +
  • Variables and constants
  • +
  • Data structures (arrays, lists, stacks, etc.)
  • +
  • Function call stack (especially important in recursive algorithms)
  • +
  • Allocation of objects or structures during execution
  • +
+ +

An important concept related to space complexity is the distinction between "in-place" algorithms (which use O(1) auxiliary space) and algorithms that require significant additional space proportional to the input size.

+ `, + examples: [ + "O(1) - Constant space: Algorithms that use a fixed amount of extra space regardless of input size (like iterative implementations of many algorithms)", + "O(log n) - Logarithmic space: Often seen in recursive implementations of divide and conquer algorithms (the call stack uses logarithmic space)", + "O(n) - Linear space: Algorithms that use extra space directly proportional to input size (like creating a new array of the same size)", + "O(n²) - Quadratic space: Algorithms that create data structures with size proportional to n² (like adjacency matrices for graphs)", + ], + relatedTerms: ["big-o-notation", "time-complexity", "in-place-algorithm"], + keywords: [ + "memory usage", + "storage requirements", + "auxiliary space", + "in-place", + "memory complexity", + ], + }, + { + slug: "sorting-algorithm", + term: "Sorting Algorithm", + category: "algorithms", + shortDefinition: + "Algorithms that arrange elements in a specific order, typically ascending or descending.", + fullDefinition: ` +

Sorting algorithms are procedures that arrange elements in a specific order, typically in ascending or descending order based on a comparison operator. They are fundamental algorithms studied in computer science and are essential in many applications.

+ +

Sorting algorithms can be categorized in several ways:

+ +
    +
  • By stability: Stable sorts preserve the relative order of equal elements, while unstable sorts may reorder equal elements
  • +
  • By adaptivity: Adaptive sorts perform better on partially sorted data
  • +
  • By method: Comparison-based sorts (like Quick Sort) vs. non-comparison sorts (like Radix Sort)
  • +
  • By memory usage: In-place sorts (minimal extra memory) vs. out-of-place sorts (require significant extra memory)
  • +
+ +

The efficiency of sorting algorithms is typically measured by their time complexity (how fast they run) and space complexity (how much memory they use). The best theoretical time complexity for comparison-based sorting is O(n log n), though specialized algorithms can perform better in specific scenarios.

+ +

Popular sorting algorithms include:

+
    +
  • Simple sorts: Bubble Sort, Insertion Sort, Selection Sort
  • +
  • Efficient comparison sorts: Merge Sort, Quick Sort, Heap Sort
  • +
  • Distribution sorts: Counting Sort, Bucket Sort, Radix Sort
  • +
+ `, + examples: [ + "Bubble Sort: Repeatedly steps through the list, compares adjacent elements, and swaps them if they're in the wrong order (O(n²) time complexity)", + "Merge Sort: Divides the array into halves, recursively sorts them, then merges them back together (O(n log n) time complexity)", + "Quick Sort: Selects a 'pivot' element and partitions the array around it, then recursively sorts the sub-arrays (average O(n log n) time complexity)", + "Heap Sort: Builds a max heap from the array and repeatedly extracts the maximum element (O(n log n) time complexity)", + ], + relatedTerms: [ + "bubble-sort", + "quick-sort", + "merge-sort", + "heap-sort", + "algorithm", + "time-complexity", + ], + keywords: [ + "ordering", + "arranging", + "comparison sort", + "sorting techniques", + "data organization", + ], + }, + { + slug: "searching-algorithm", + term: "Searching Algorithm", + category: "algorithms", + shortDefinition: + "Algorithms designed to retrieve specific information from a data structure.", + fullDefinition: ` +

Searching algorithms are methods designed to find an item or items with specified properties within a collection of data. These algorithms are fundamental in computer science and have numerous applications, from finding records in databases to locating specific elements in arrays.

+ +

Search algorithms can be broadly classified into two categories:

+ +
    +
  • Sequential Search: Examines each element in the data structure one after another until the target is found or all elements have been checked
  • +
  • Interval Search: Designed for sorted data structures and are more efficient than sequential search by repeatedly targeting the center of the search space and eliminating half of the elements at each step
  • +
+ +

The efficiency of searching algorithms is typically measured by their time complexity, which indicates how the search time increases with the size of the data. Some searches can be optimized by using specialized data structures like hash tables, which provide constant-time access on average.

+ +

Popular searching algorithms include:

+
    +
  • Linear Search: Checks each element sequentially until the target is found
  • +
  • Binary Search: Efficiently searches sorted arrays by repeatedly dividing the search space in half
  • +
  • Depth-First Search (DFS): Traverses a graph by exploring as far as possible along each branch before backtracking
  • +
  • Breadth-First Search (BFS): Traverses a graph by exploring all neighbor nodes at the present depth before moving to nodes at the next depth level
  • +
+ `, + examples: [ + "Linear Search: Sequentially checks each element in a collection until the target is found (O(n) time complexity)", + "Binary Search: For sorted arrays, compares the target value to the middle element and eliminates half of the remaining elements (O(log n) time complexity)", + "Depth-First Search: Often used for traversing trees and graphs, explores as deeply as possible before backtracking", + "Breadth-First Search: Used for finding the shortest path in unweighted graphs, explores all neighbors at current depth before moving deeper", + ], + relatedTerms: [ + "linear-search", + "binary-search", + "algorithm", + "time-complexity", + "data-structure", + ], + keywords: [ + "retrieval", + "lookup", + "find", + "locate", + "query", + "search techniques", + ], + }, + { + slug: "in-place-algorithm", + term: "In-place Algorithm", + category: "analysis", + shortDefinition: + "Algorithms that transform input using minimal additional memory space.", + fullDefinition: ` +

An in-place algorithm is an algorithm that transforms the input data structure without using extra data structures for storage. It operates directly on the input, modifying it as necessary, while using only a constant amount of extra space for variables.

+ +

The key characteristic of in-place algorithms is their space efficiency. They typically have a space complexity of O(1) or constant space, meaning the amount of additional memory they use doesn't grow with the size of the input.

+ +

In-place algorithms are particularly valuable in environments with limited memory resources, when working with very large datasets, or when memory allocation and deallocation operations are expensive.

+ +

Some algorithms can be implemented in either in-place or out-of-place variants, with different trade-offs in terms of simplicity, speed, and memory usage. In-place versions generally save space but might be more complex or slightly slower in some cases.

+ +

Characteristics of in-place algorithms:

+
    +
  • Use minimal extra memory beyond the input itself
  • +
  • Typically modify the input structure directly
  • +
  • Often use techniques like swapping elements or overwriting values
  • +
  • May use a small constant amount of extra space for temporary variables
  • +
+ `, + examples: [ + "Bubble Sort: Sorts an array by repeatedly swapping adjacent elements if they're in the wrong order", + "Quick Sort: Can be implemented in-place by using the original array to store the partitions", + "Selection Sort: Finds the minimum element and swaps it into position without using extra arrays", + "Array reversal: Swapping pairs of elements from both ends moving toward the center", + ], + relatedTerms: ["space-complexity", "algorithm", "sorting-algorithm"], + keywords: [ + "memory efficient", + "constant space", + "auxiliary space", + "space optimization", + ], + }, + { + slug: "divide-and-conquer", + term: "Divide and Conquer", + category: "techniques", + shortDefinition: + "An algorithmic paradigm that solves problems by breaking them into smaller subproblems, solving each independently, and combining the results.", + fullDefinition: ` +

Divide and Conquer is a problem-solving paradigm that breaks a problem into smaller, similar subproblems, solves each subproblem independently, and then combines these solutions to create a solution to the original problem.

+ +

This algorithmic approach follows three main steps:

+ +
    +
  1. Divide: Break the original problem into smaller, similar subproblems
  2. +
  3. Conquer: Solve the subproblems recursively (or directly if they're simple enough)
  4. +
  5. Combine: Merge the solutions of the subproblems to form the solution to the original problem
  6. +
+ +

Divide and conquer algorithms are often implemented using recursion, though iterative implementations are also possible. They are particularly useful for problems that can be broken down into independent, similar subproblems.

+ +

This approach has several advantages:

+
    +
  • Can lead to efficient algorithms, particularly for large problems
  • +
  • Often results in algorithms with good asymptotic time complexity (commonly O(n log n))
  • +
  • Can be parallelized in many cases, as subproblems can be solved independently
  • +
  • Provides a clear and elegant approach to solving complex problems
  • +
+ `, + examples: [ + "Merge Sort: Divides the array in half, recursively sorts both halves, then merges them", + "Quick Sort: Chooses a pivot, partitions the array around it, then recursively sorts the partitions", + "Binary Search: Divides the search space in half at each step", + "Strassen's Matrix Multiplication: Divides matrices into submatrices and combines results using fewer multiplications than the naive approach", + ], + relatedTerms: ["recursion", "merge-sort", "quick-sort", "binary-search"], + keywords: [ + "recursive algorithms", + "problem decomposition", + "algorithmic paradigm", + "subproblems", + ], + }, + { + slug: "asymptotic-analysis", + term: "Asymptotic Analysis", + category: "analysis", + shortDefinition: + "The study of how algorithms perform as their input sizes approach infinity, ignoring constant factors and lower-order terms.", + fullDefinition: ` +

Asymptotic analysis is a method for describing the efficiency of algorithms by analyzing their performance as the input size grows towards infinity. Rather than focusing on the exact number of operations, it characterizes algorithm performance in terms of how the resource usage (time or space) scales with input size.

+ +

Key principles of asymptotic analysis include:

+ +
    +
  • Focus on growth rate: Examine how an algorithm's efficiency changes as input size increases
  • +
  • Ignore constants: Constant factors don't affect the overall growth rate (e.g., 2n and 100n both grow linearly)
  • +
  • Ignore lower-order terms: As input size increases, higher-order terms dominate (e.g., n² + n becomes approximately n² for large n)
  • +
+ +

Asymptotic analysis typically uses three main notations:

+ +
    +
  • Big O (O): Upper bound - the function grows no faster than
  • +
  • Big Omega (Ω): Lower bound - the function grows at least as fast as
  • +
  • Big Theta (Θ): Tight bound - the function grows at exactly the same rate as
  • +
+ +

This approach allows us to compare algorithms independently of implementation details, hardware, or specific inputs, focusing instead on their fundamental efficiency characteristics.

+ `, + examples: [ + "An algorithm that performs n² + 3n + 1 operations has an asymptotic complexity of O(n²)", + "Binary search has a time complexity of O(log n) because the number of operations is proportional to the logarithm of the input size", + "The asymptotic space complexity of merge sort is O(n) because it requires additional storage proportional to the input size", + ], + relatedTerms: [ + "big-o-notation", + "time-complexity", + "space-complexity", + "algorithm-analysis", + ], + keywords: [ + "growth rate", + "computational complexity", + "algorithm analysis", + "efficiency", + ], + }, + { + slug: "recursion", + term: "Recursion", + category: "techniques", + shortDefinition: + "A programming technique where a function calls itself to solve smaller instances of the same problem.", + fullDefinition: ` +

Recursion is a programming technique in which a function calls itself directly or indirectly to solve a problem. Each recursive call addresses a smaller instance of the same problem, moving toward a base case that can be solved without further recursion.

+ +

A recursive algorithm typically consists of two essential parts:

+ +
    +
  • Base case(s): Simple instances of the problem that can be solved directly without further recursion
  • +
  • Recursive case(s): More complex instances that are solved by breaking them down and involving recursive calls
  • +
+ +

Recursion naturally maps to problems that can be broken down into smaller, similar subproblems, particularly those with a hierarchical or nested structure. It often leads to elegant and concise solutions to complex problems.

+ +

While recursion can be powerful and intuitive, it has some limitations:

+
    +
  • Each recursive call consumes stack space, which can lead to stack overflow errors for deep recursion
  • +
  • Recursive solutions may have more overhead than iterative alternatives due to function call machinery
  • +
  • Simple recursive implementations of some algorithms can lead to redundant calculations
  • +
+ +

Techniques like tail recursion, memoization, and dynamic programming can help address these limitations in many cases.

+ `, + codeExample: `// Recursive factorial function +function factorial(n) { + // Base case + if (n === 0 || n === 1) { + return 1; + } + // Recursive case + return n * factorial(n - 1); +} + +// Recursive fibonacci function +function fibonacci(n) { + // Base cases + if (n <= 0) return 0; + if (n === 1) return 1; + + // Recursive case + return fibonacci(n - 1) + fibonacci(n - 2); +}`, + examples: [ + "Factorial calculation: factorial(n) = n * factorial(n-1), with factorial(0) = 1", + "Fibonacci sequence: fibonacci(n) = fibonacci(n-1) + fibonacci(n-2), with fibonacci(0) = 0 and fibonacci(1) = 1", + "Binary tree traversal: Process the current node, then recursively process left and right subtrees", + "Merge sort: Recursively sort the two halves of an array, then merge them", + ], + relatedTerms: ["divide-and-conquer", "dynamic-programming", "algorithm"], + keywords: [ + "self-reference", + "recursive functions", + "call stack", + "base case", + ], + }, + { + slug: "dynamic-programming", + term: "Dynamic Programming", + category: "techniques", + shortDefinition: + "A method for solving complex problems by breaking them down into simpler subproblems and storing their solutions to avoid redundant computations.", + fullDefinition: ` +

Dynamic Programming (DP) is a technique for solving complex problems by breaking them down into simpler overlapping subproblems and storing the solutions to these subproblems to avoid redundant calculation. It's particularly useful for optimization problems where the goal is to find the best solution among many possible options.

+ +

Two key properties typically characterize problems suited for dynamic programming:

+ +
    +
  • Optimal substructure: An optimal solution to the problem contains optimal solutions to its subproblems
  • +
  • Overlapping subproblems: The same subproblems are encountered multiple times when solving the problem
  • +
+ +

Dynamic programming can be implemented using two main approaches:

+ +
    +
  • Top-down (memoization): Start with the original problem, break it into subproblems, and store their solutions as they're computed
  • +
  • Bottom-up (tabulation): Start by solving the smallest subproblems first, then use their solutions to build up to larger problems
  • +
+ +

The primary advantage of dynamic programming is that it can dramatically improve the efficiency of algorithms for problems with overlapping subproblems, often reducing exponential time complexity to polynomial time.

+ `, + codeExample: `// Fibonacci using dynamic programming (memoization) +function fibonacciDP(n, memo = {}) { + // Check if we've already computed this value + if (n in memo) return memo[n]; + + // Base cases + if (n <= 0) return 0; + if (n === 1) return 1; + + // Store and return the result + memo[n] = fibonacciDP(n - 1, memo) + fibonacciDP(n - 2, memo); + return memo[n]; +} + +// Fibonacci using dynamic programming (tabulation) +function fibonacciTabulation(n) { + if (n <= 0) return 0; + if (n === 1) return 1; + + // Create an array to store values + const dp = new Array(n + 1); + dp[0] = 0; + dp[1] = 1; + + // Fill the array + for (let i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + + return dp[n]; +}`, + examples: [ + "Fibonacci sequence calculation with memoization", + "Knapsack problem: Finding the most valuable combination of items that fit within a weight constraint", + "Longest Common Subsequence: Finding the longest sequence common to two sequences", + "Shortest path algorithms like Floyd-Warshall", + ], + relatedTerms: ["recursion", "memoization", "algorithm", "optimization"], + keywords: [ + "subproblems", + "optimization", + "memoization", + "tabulation", + "optimal substructure", + ], + }, +]; + +// Function to get a glossary term by its slug +export function getGlossaryTermBySlug(slug: string): GlossaryTerm | undefined { + return glossaryTerms.find((term) => term.slug === slug); +} + +// Function to get related terms for a specific term +export function getRelatedTerms(slug: string): GlossaryTerm[] { + const term = getGlossaryTermBySlug(slug); + if (!term || !term.relatedTerms || term.relatedTerms.length === 0) { + return []; + } + + return term.relatedTerms + .map((relatedSlug) => getGlossaryTermBySlug(relatedSlug)) + .filter((term): term is GlossaryTerm => term !== undefined); +} + +// Function to search for glossary terms +export function searchGlossaryTerms(query: string): GlossaryTerm[] { + const lowercaseQuery = query.toLowerCase(); + return glossaryTerms.filter( + (term) => + term.term.toLowerCase().includes(lowercaseQuery) || + term.shortDefinition.toLowerCase().includes(lowercaseQuery) || + term.category.toLowerCase().includes(lowercaseQuery) || + term.keywords?.some((keyword) => + keyword.toLowerCase().includes(lowercaseQuery) + ) + ); +} diff --git a/lib/utils.ts b/lib/utils.ts index fbfd39b..903ef17 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -14,3 +14,23 @@ export function getRandomValueFromArray(array: number[]): number { const randomIndex = Math.floor(Math.random() * array.length); return array[randomIndex]; } + +// Glossary utility functions +export function sortByAlphabet(items: T[]): T[] { + return [...items].sort((a, b) => a.term.localeCompare(b.term)); +} + +export function groupTermsByFirstLetter( + items: T[] +): Record { + const sorted = sortByAlphabet(items); + + return sorted.reduce((groups: Record, item) => { + const firstLetter = item.term.charAt(0).toUpperCase(); + if (!groups[firstLetter]) { + groups[firstLetter] = []; + } + groups[firstLetter].push(item); + return groups; + }, {}); +}