A collection of Java exercises covering data structures, algorithms, recursion, streams, file I/O, math, and date/time utilities.
- Java 11+
- JUnit 4.13.2 (already in lib/)
javac -cp ".:lib/junit-4.13.2.jar:lib/hamcrest-core-1.3.jar" *.javaTEST_CLASSES=($(ls *Test*.java | sed 's/.java//'))
java -cp ".:lib/junit-4.13.2.jar:lib/hamcrest-core-1.3.jar" org.junit.runner.JUnitCore ${TEST_CLASSES[@]}An AccessCountArrayList is a regular Java ArrayList with one extra feature: it counts how many times each element position has been accessed. Every call to get(i) or set(i, value) increments a counter for that index, allowing you to track which elements are touched most often.
The class extends ArrayList and overrides get and set. Before delegating to the parent, it increments an access counter stored in a parallel list.
- A second list stores one integer counter per element, initialized to zero
- Every
get(i)call incrementscounter[i], then returns the element - Every
set(i, value)call incrementscounter[i], then stores the new value getAccessCount(i)returns the total number of accesses for that index
FileProcessor is a reusable template for reading text files line by line. It defines a fixed three-phase skeleton - setup, per-line processing, and teardown - while letting subclasses fill in the logic for each phase. This prevents duplicating the file-reading boilerplate in every program that needs it.
This is an example of the template method design pattern: the overall algorithm is fixed in the base class, but individual steps are overridden per use case.
startFile()is called once before the first line is read- Each line is passed to
processLine(String line)in order from top to bottom endFile()is called once after the last line has been processed
FilterWriter is a character-level output filter. It wraps any Java Writer and lets you decide - one character at a time - whether to pass each character through to the underlying writer or silently discard it. This is useful for transformations like removing double spaces, stripping punctuation, or collapsing repeated characters.
The decision is made by a BiPredicate<Character, Character> that receives the previous character and the current character as inputs. If the predicate returns true, the character is forwarded; if false, it is dropped.
- The writer starts with no previous character (
null) - For each incoming character, the predicate
test(prev, current)is evaluated - If the predicate returns
true, the character is forwarded to the wrapped writer - The previous-character pointer is updated to the current character regardless of outcome
A fraction represents an exact rational number - a numerator divided by a denominator. Unlike floating-point numbers, fractions never lose precision because all arithmetic is exact. Fraction stores both values as BigInteger, so arbitrarily large numerators and denominators are supported without overflow.
In mathematical terms: a fraction p/q where gcd(p, q) = 1 is said to be in lowest terms.
After every arithmetic operation, the result is automatically reduced to lowest terms using the greatest common divisor (GCD) via the Euclidean algorithm.
For example: 6/8 → gcd(6, 8) = 2 → divide both by 2 → 3/4
- Compute
gcd = BigInteger.gcd(numerator, denominator) - Divide both numerator and denominator by the GCD
- If the denominator is negative, flip the signs of both so the denominator is always positive
- The fraction is now fully reduced and in canonical form
Once normalized, fractions support exact addition, subtraction, multiplication, and division:
a/b + c/d = (a·d + c·b) / (b·d), then reducea/b × c/d = (a·c) / (b·d), then reduce
Head is a custom Swing painting component that draws a cartoon face. The face has two states: neutral (resting expression) and happy (smiling). The expression switches automatically in response to mouse movement - no clicking required.
Java's MouseListener interface allows the component to react to mouse events without polling. The component repaints itself each time its state changes.
- Mouse enters the component bounds → state switches to happy →
repaint()is called - Mouse exits the component bounds → state switches to neutral →
repaint()is called paintComponent(Graphics g)reads the current state and draws the appropriate face
HeadMain is the application entry point that opens a Swing window containing four Head components arranged in a 2×2 grid. Each face responds to mouse events independently.
- Create a
JFramewith a title - Set the layout manager to
GridLayout(2, 2) - Instantiate and add four
Headpanels to the frame - Call
pack()andsetVisible(true)to display the window
A Lissajous curve is the path traced by a point whose x and y positions each oscillate sinusoidally but at different frequencies and with a phase offset between them. The shape depends entirely on the ratio of the two frequencies and the phase angle delta.
In mathematical terms:
x(t) = sin(a·t + δ)y(t) = sin(b·t)
When a/b is a simple integer ratio like 1:2 or 3:2, the curve closes into a recognizable figure-eight or pretzel shape. Irrational ratios produce curves that never close.
The curve is sampled at many values of t from 0 to 2π. Each (x, y) pair is scaled to fit the panel and connected to the previous point with a line segment.
- Accept parameters
a,b(frequency integers) anddelta(phase offset in radians) - Iterate
tfrom 0 to 2π in small increments - For each
t, computex = sin(a·t + delta)andy = sin(b·t) - Scale the unit-range coordinates to the panel's pixel dimensions
- Connect consecutive points using
drawLine
LissajousMain is the entry point that constructs the Swing window and adds the Lissajous drawing panel to it.
- Create a
JFrame - Instantiate a
Lissajouspanel with the desireda,b, anddeltaparameters - Add the panel to the frame and call
setVisible(true)
ArrayAlgorithms is a set of array manipulation exercises exploring combinatorics, 2D grid patterns, and order statistics.
The falling power n^(k) is the descending product n × (n−1) × (n−2) × … × (n−k+1). It counts the number of ordered arrangements (permutations) of k items chosen from n.
For example: fallingPower(5, 3) = 5 × 4 × 3 = 60
A zigzag matrix fills a 2D grid with consecutive integers, but alternates the fill direction on each row - left-to-right on even rows, right-to-left on odd rows.
- Even-numbered rows are filled left to right with consecutive integers
- Odd-numbered rows are filled right to left with consecutive integers
- A single counter
startincrements with every cell regardless of direction
An inversion in an array is a pair (i, j) where i < j but arr[i] > arr[j]. The inversion count measures how far an array is from being sorted - a sorted array has zero inversions.
- Compare every pair of positions
(i, j)wherei < j - If
arr[i] > arr[j], the pair is an inversion - increment the count - Return the total count after all pairs are checked
StringAndSequence covers string deduplication, counting safe squares on a chessboard with rooks, and generating the Recaman integer sequence.
removeDuplicates collapses any run of identical consecutive characters into a single character. For example: "aabbbcc" → "abc".
uniqueCharacters returns a string containing each character from the input exactly once, in the order it first appeared. For example: "banana" → "ban".
Given a chessboard of size n×n with rooks already placed, a square is safe if no rook can attack it. A rook attacks every square in its row and its column. The number of safe squares equals (rows with no rook) × (columns with no rook).
- Scan the board and mark every row and every column that contains at least one rook
- Count the number of unmarked rows (
safeRows) and unmarked columns (safeCols) - Multiply:
safeRows × safeColsis the total number of safe squares
The Recaman sequence is defined by a deceptively simple rule: at each step i, try to go backwards by i. If the result is positive and has not appeared in the sequence before, use it; otherwise go forwards by i.
The sequence begins: 0, 1, 3, 6, 2, 7, 13, 20, 12, 21, 11, …
- Set
a[0] = 0 - For each step
i ≥ 1, computeback = a[i−1] − i - If
back > 0andbackhas not appeared in the sequence yet, seta[i] = back - Otherwise set
a[i] = a[i−1] + i(go forwards)
ReversalAlgorithms is a set of reversal exercises: reversing subarrays in place, scrambling strings with a sequence of pancake flips, and reversing the vowels inside a string while leaving consonants untouched.
In a pancake scramble, you repeatedly take the first k characters of the string and reverse them, starting at k = 2 and incrementing k each round. It is related to pancake sorting but used here as a deterministic string transformation.
For example: "abcd" →
- k=2: reverse first 2 →
"bacd" - k=3: reverse first 3 →
"acbd" - k=4: reverse first 4 →
"dbca"
- Start with
k = 2 - Reverse the first
kcharacters of the current string - Increment
kand repeat untilkequals the full length of the string
This method finds every maximal ascending run in the array and reverses it in place. An ascending run is a contiguous sequence where each element is greater than the one before it.
The vowels in the string are collected in order, reversed, and put back into the same vowel positions. All consonants stay exactly where they are. The case of each vowel slot in the original string is preserved - if position i held an uppercase vowel, it still holds an uppercase vowel after the reversal.
For example: "Hello World" → vowels are e, o, o → reversed: o, o, e → result: "Hollo Werld"
ListAlgorithms contains a sliding-window median filter, a combinatorial number theory function, and a custom frequency-based sorter.
At each position i ≥ 2, the median of the three consecutive elements items[i−2], items[i−1], items[i] is computed and emitted. This acts as a smoothing filter that suppresses isolated spikes in a sequence.
For example: [1, 5, 3, 7, 2] → [1, 5, 3, 5, 3]
- The first two elements are passed through unchanged
- For each subsequent element, identify the minimum and maximum of the three-element window
- The median is
sum − min − max(the middle value without sorting)
Returns the smallest positive integer (1, 2, 3, …) that does not appear in the list. For example: [3, 1, 4, 1, 5, 9] → 2.
Sorts a list so that the most frequent elements come first. Elements with equal frequency are placed in ascending numeric order. For example: [4, 4, 2, 2, 2, 1] → [2, 2, 2, 4, 4, 1].
Computes the complete prime factorization of n! by factorizing every integer from 2 to n and merging all prime factors into one sorted list.
BigIntegerProblems explores two number theory puzzles using BigInteger: decomposing any integer as a sum of Fibonacci numbers (Zeckendorf's theorem), and finding the smallest number composed entirely of 7s and 0s that is divisible by n.
Every positive integer can be written uniquely as a sum of non-consecutive Fibonacci numbers. A greedy algorithm finds this decomposition by always subtracting the largest Fibonacci number that fits.
For example: 100 = 89 + 8 + 3
- Extend the Fibonacci sequence until the largest term is ≥
n - Greedily subtract the largest Fibonacci number
≤ nfromnand record it - Repeat until
nreaches zero - Return the collected Fibonacci numbers in descending order
sevenZero(n) finds the smallest positive integer consisting only of the digits 7 and 0 (such as 7, 77, 700, 770, 7700, …) that is exactly divisible by n.
- Generate candidate numbers in order: 7, 77, 700, 770, 7000, 7700, …
- For each candidate, check
candidate mod n == 0usingBigInteger.mod() - Return the first candidate that passes the divisibility check
Backtracking solves two combinatorial problems using recursive backtracking: expressing a number as a sum of distinct perfect cubes, and generating all strings of a given length that avoid a list of forbidden substrings.
Backtracking builds a solution one choice at a time. After each step it checks whether the partial solution can still lead to a valid answer. If not, it backtracks - undoes the last choice and tries the next alternative. This systematically explores all possibilities without re-examining dead ends.
- Start with an empty partial solution
- Try each possible extension of the current partial solution
- If any constraint is violated, immediately undo and try the next option
- If all constraints are satisfied and the solution is complete, record it
- Continue until all branches have been explored
sumOfDistinctCubes(n) finds a set of distinct positive integers {c₁, c₂, …} such that c₁³ + c₂³ + … = n.
For example: 36 = 3³ + 2³ + 1³ → returns [3, 2, 1]
forbiddenSubstrings(alphabet, n, tabu) returns all strings of length n over alphabet that do not contain any string from the tabu list as a contiguous substring. Strings are built character by character and pruned as soon as a forbidden pattern appears.
GameMath analyzes two mathematical structures: detecting which integers can be written as a sum of two distinct squares, and computing winning and losing positions in a subtraction game.
sumOfTwoDistinctSquares(n) returns a boolean array where result[k] is true if k can be written as i² + j² for two distinct positive integers i ≠ j.
For example: 5 = 1² + 2², 13 = 2² + 3².
- Iterate over all pairs
(i, j)where1 ≤ i < j - If
i² + j² < n, markresult[i² + j²] = true - Stop once the smallest possible sum for the current
ialready exceedsn
Two players alternate subtracting any perfect square (1, 4, 9, 16, …) from a shared positive integer. The player who reduces the number to zero wins. subtractSquare(n) labels every position from 0 to n−1 as a winning (W) or losing (L) position.
position[0]is a losing position - the player to move has no valid move- For each
i > 0, try subtracting every perfect squarek²wherek² ≤ i - If any resulting position
i − k²is a losing position for the opponent, theniis a winning position - If all resulting positions are winning for the opponent, then
iis a losing position
DissimilarityMetrics measures how different two binary vectors are from each other. It implements six dissimilarity metrics used in statistics, data mining, and bioinformatics.
Given two boolean arrays v1 and v2 of the same length, each pair of corresponding bits falls into one of four categories:
n11- bothtrue(both vectors agree: present)n10- v1true, v2false(disagree)n01- v1false, v2true(disagree)n00- bothfalse(both vectors agree: absent)
The disagreements (n10 + n01) drive dissimilarity. A dissimilarity of 0 means the vectors are identical.
| Metric | Formula |
|---|---|
| Matching | (n10 + n01) / total |
| Jaccard | (n10 + n01) / (n11 + n10 + n01) |
| Dice | (n10 + n01) / (2·n11 + n10 + n01) |
| Rogers-Tanimoto | 2·(n10+n01) / (n11 + 2·(n10+n01) + n00) |
| Russell-Rao | (n10 + n01 + n00) / total |
| Sokal-Sneath | 2·(n10+n01) / (n11 + 2·(n10+n01)) |
All results are returned as exact Fraction values.
A polynomial is a mathematical expression built from terms, where each term is a coefficient multiplied by a variable raised to a non-negative integer power.
For example: 3x² − 2x + 1
Polynomial stores coefficients as a BigInteger array where index i holds the coefficient of xⁱ. The class is immutable - every arithmetic operation returns a brand new Polynomial instead of modifying the original.
To add two polynomials, add the coefficients at each matching degree. If one polynomial has fewer terms, treat the missing coefficients as zero.
(3x² + 2x) + (x² − 1) = 4x² + 2x − 1
To multiply two polynomials, multiply every term of the first by every term of the second and collect like terms. A term a·xⁱ times b·xʲ contributes a·b to the coefficient of x^(i+j).
(x + 1) × (x − 1) = x² − 1
To evaluate P(x) at a specific value, substitute the value and sum all terms. Horner's method computes this efficiently without repeated exponentiation:
P(x) = (…((aₙ·x + aₙ₋₁)·x + aₙ₋₂)·x + … + a₀)
Java Streams provide a declarative way to process sequences of data. Instead of writing explicit loops, you describe a pipeline of operations - filter, transform, sort, collect - and the Stream API applies them in order. Streams do not store data; they describe what should happen to it.
Every stream has three parts:
- A source (a file, a list, a generator)
- Zero or more intermediate operations that transform the data (lazy - not executed until needed)
- A terminal operation that triggers execution and produces a result
- Source: read all lines from a text file into a stream
- Tokenize: split each line into words using
flatMap(line -> Arrays.stream(line.split(...))) - Normalize: convert each token to lowercase with
map(String::toLowerCase) - Filter: keep only tokens that match a predicate with
filter(...) - Distinct: remove duplicate tokens with
distinct() - Sort: alphabetically order results with
sorted() - Collect: gather into a
Listwithcollect(Collectors.toList())
The Unix tail command prints the last n lines of a file. It is useful when a file is too large to read in full and you only care about the most recent entries - such as the latest messages in a log file.
Tail.java implements this behavior using a rolling buffer that only ever holds the last n lines in memory.
Instead of storing the entire file, the buffer is capped at n entries. When a new line arrives and the buffer is already full, the oldest line is evicted to make room.
- Initialize a buffer with a maximum capacity of
nlines - For each new line read, append it to the end of the buffer
- If the buffer size now exceeds
n, remove the element at the front (the oldest line) - When the file is fully read, the buffer contains exactly the last
nlines in order
TimeProblems is a collection of date and time utilities built on the Java 8 java.time API. It solves three classic calendar problems: timezone conversion, next-weekday projection, and counting Friday the 13th occurrences in a year.
toMontreal converts any ZonedDateTime into Montreal's local time (America/Montreal). Daylight saving time transitions are handled automatically by the java.time library - you just specify the target zone and it does the rest.
Given a starting date and a target day of the week (e.g., next Friday), this computes the next occurrence of that weekday at or after the start date. It uses TemporalAdjusters.nextOrSame(DayOfWeek) from the java.time API.
The method counts how many Friday the 13ths occur in a given year by checking every month: construct the 13th of that month and test whether getDayOfWeek() == FRIDAY.
WordCount reads a text file and reports three statistics: the total number of lines, the total number of words, and the total number of characters. It replicates the behavior of the Unix wc command.
WordCount extends FileProcessor, using its three-phase template to process the file one line at a time without duplicating any file-reading logic.
All three counters start at zero. For each line delivered by FileProcessor:
- Increment the line counter by 1
- Split the line on whitespace and increment the word counter by the number of tokens produced
- Increment the character counter by the length of the line plus 1 for the newline character
- After all lines are processed, retrieve the totals via
getLines(),getWords(), andgetCharacters()
A prime number has only two factors: one and itself. A factor divides the number perfectly leaving no remainder behind.
In mathematical terms: number % factor = 0
The method kthPrime searches for the 1st – 30,000th prime number in less than one
minute from the infinite sequence of prime numbers (Primes.java goes up to 50,000).
- Start at the prime number 2 and eliminate all other multiples of 2, thus eliminating composite numbers
- Move on to 3 and eliminate all other multiples of 3
- Move on to 5 and eliminate all other multiples of 5, etc.
- After the list of primes is created, the kth prime is obtained from the list
The returned list of the method needs to contain all the prime factors in ascending sorted order. For example, if n = 220, [2, 2, 5, 11] is returned.
- If the integer is even, list all occurrences of 2 that divide the integer. The integer n is repeatedly divided by 2 and each successful division is listed.
- If the integer is odd, n is repeatedly divided by 3 and each successful division is listed.
- If the number is not divisible by 3 move on to the next odd prime number until the square root of the integer n is reached.
- When the integer cannot be divided anymore and a prime number is left, list that prime number.
- Problem statement PDFs and some source assignment artifacts are not included.
- Tests in this repository validate the behavior of all major implementations.
Kudos to the authors of the gifs and diagrams!
https://www.dadsworksheets.com/prime-factorization-calculator.html
https://commons.wikimedia.org/wiki/File:Singly-linked-list.svg
https://commons.wikimedia.org/wiki/File:Pipeline.svg
https://commons.wikimedia.org/wiki/File:Euclidean_algorithm_252_105_animation_flipped.gif
https://commons.wikimedia.org/wiki/File:Scratch-lissajous.gif
https://commons.wikimedia.org/wiki/File:Insertion-sort-example-300px.gif
https://commons.wikimedia.org/wiki/File:Recaman%27s_sequence.svg
https://commons.wikimedia.org/wiki/File:PancakeSort_jaredwf.gif
https://commons.wikimedia.org/wiki/File:MovingAverage.GIF
https://commons.wikimedia.org/wiki/File:Zeckendorf_representations.svg
https://commons.wikimedia.org/wiki/File:Eight-queens-animation.gif
https://commons.wikimedia.org/wiki/File:Venn_A_intersect_B.svg
https://commons.wikimedia.org/wiki/File:Polynomialdeg2.svg
https://commons.wikimedia.org/wiki/File:Standard_time_zones_of_the_world.png








