Advent of Code 2025 solutions
1import { expect, test, describe } from "bun:test";
2import {
3 combinations,
4 multicombinations,
5 unique,
6 first,
7 last,
8 middle,
9 swap,
10 remove,
11 sum,
12 getIdxAt,
13 compareArr,
14} from "./array";
15
16describe("combinations", () => {
17 test("returns empty array for empty input", () => {
18 expect(combinations([], 1)).toEqual([]);
19 expect(combinations([], 2)).toEqual([]);
20 });
21
22 test("returns single element combinations", () => {
23 expect(combinations([1, 2, 3], 1)).toEqual([[1], [2], [3]]);
24 expect(combinations(["a", "b"], 1)).toEqual([["a"], ["b"]]);
25 });
26
27 test("returns 2-element combinations", () => {
28 expect(combinations([1, 2, 3], 2)).toEqual([
29 [1, 2],
30 [1, 3],
31 [2, 3],
32 ]);
33 expect(combinations(["a", "b", "c"], 2)).toEqual([
34 ["a", "b"],
35 ["a", "c"],
36 ["b", "c"],
37 ]);
38 });
39
40 test("returns 3-element combinations", () => {
41 expect(combinations([1, 2, 3, 4], 3)).toEqual([
42 [1, 2, 3],
43 [1, 2, 4],
44 [1, 3, 4],
45 [2, 3, 4],
46 ]);
47 });
48
49 test("returns empty array when n > array length", () => {
50 expect(combinations([1, 2], 3)).toEqual([]);
51 expect(combinations([1], 2)).toEqual([]);
52 });
53
54 test("returns single combination when n equals array length", () => {
55 expect(combinations([1, 2, 3], 3)).toEqual([[1, 2, 3]]);
56 });
57
58 test("works with complex types", () => {
59 const objects = [{ id: 1 }, { id: 2 }, { id: 3 }];
60 const result = combinations(objects, 2);
61 expect(result).toHaveLength(3);
62 expect(result[0]).toEqual([{ id: 1 }, { id: 2 }]);
63 expect(result[1]).toEqual([{ id: 1 }, { id: 3 }]);
64 expect(result[2]).toEqual([{ id: 2 }, { id: 3 }]);
65 });
66});
67
68describe("multicombinations", () => {
69 test("returns empty array for empty input", () => {
70 expect(multicombinations([], 1)).toEqual([]);
71 expect(multicombinations([], 2)).toEqual([]);
72 });
73
74 test("returns single element multicombinations", () => {
75 expect(multicombinations([1, 2, 3], 1)).toEqual([[1], [2], [3]]);
76 expect(multicombinations(["a", "b"], 1)).toEqual([["a"], ["b"]]);
77 });
78
79 test("returns 2-element multicombinations (allows reuse)", () => {
80 expect(multicombinations([1, 2, 3], 2)).toEqual([
81 [1, 1],
82 [1, 2],
83 [1, 3],
84 [2, 2],
85 [2, 3],
86 [3, 3],
87 ]);
88 });
89
90 test("returns 3-element multicombinations", () => {
91 const result = multicombinations([1, 2], 3);
92 expect(result).toEqual([
93 [1, 1, 1],
94 [1, 1, 2],
95 [1, 2, 2],
96 [2, 2, 2],
97 ]);
98 });
99
100 test("handles larger n values correctly", () => {
101 const result = multicombinations([1, 2], 4);
102 expect(result).toHaveLength(5); // (n+k-1)!/(n!(k-1)!) = (4+2-1)!/(4!(2-1)!) = 5!/(4!1!) = 5
103 expect(result).toEqual([
104 [1, 1, 1, 1],
105 [1, 1, 1, 2],
106 [1, 1, 2, 2],
107 [1, 2, 2, 2],
108 [2, 2, 2, 2],
109 ]);
110 });
111});
112
113describe("unique", () => {
114 test("removes duplicate numbers", () => {
115 expect(unique([1, 2, 2, 3, 1, 4])).toEqual([1, 2, 3, 4]);
116 });
117
118 test("removes duplicate strings", () => {
119 expect(unique(["a", "b", "a", "c", "b"])).toEqual(["a", "b", "c"]);
120 });
121
122 test("handles empty array", () => {
123 expect(unique([])).toEqual([]);
124 });
125
126 test("handles array with no duplicates", () => {
127 expect(unique([1, 2, 3])).toEqual([1, 2, 3]);
128 });
129
130 test("preserves order of first occurrence", () => {
131 expect(unique([3, 1, 2, 1, 3])).toEqual([3, 1, 2]);
132 });
133});
134
135describe("first", () => {
136 test("returns first element", () => {
137 expect(first([1, 2, 3])).toBe(1);
138 expect(first(["a", "b", "c"])).toBe("a");
139 });
140
141 test("returns undefined for empty array", () => {
142 expect(first([])).toBeUndefined();
143 });
144
145 test("works with single element array", () => {
146 expect(first([42])).toBe(42);
147 });
148});
149
150describe("last", () => {
151 test("returns last element", () => {
152 expect(last([1, 2, 3])).toBe(3);
153 expect(last(["a", "b", "c"])).toBe("c");
154 });
155
156 test("returns undefined for empty array", () => {
157 expect(last([])).toBeUndefined();
158 });
159
160 test("works with single element array", () => {
161 expect(last([42])).toBe(42);
162 });
163});
164
165describe("middle", () => {
166 test("returns middle element for odd length", () => {
167 expect(middle([1, 2, 3])).toBe(2);
168 expect(middle([1, 2, 3, 4, 5])).toBe(3);
169 });
170
171 test("returns upper middle element for even length", () => {
172 expect(middle([1, 2, 3, 4])).toBe(3);
173 expect(middle([1, 2, 3, 4, 5, 6])).toBe(4);
174 });
175
176 test("returns undefined for empty array", () => {
177 expect(middle([])).toBeUndefined();
178 });
179
180 test("works with single element array", () => {
181 expect(middle([42])).toBe(42);
182 });
183});
184
185describe("swap", () => {
186 test("swaps elements at given indices", () => {
187 expect(swap(0, 2, [1, 2, 3])).toEqual([3, 2, 1]);
188 expect(swap(1, 3, ["a", "b", "c", "d"])).toEqual(["a", "d", "c", "b"]);
189 });
190
191 test("returns new array (immutable)", () => {
192 const original = [1, 2, 3];
193 const swapped = swap(0, 2, original);
194 expect(swapped).toEqual([3, 2, 1]);
195 expect(original).toEqual([1, 2, 3]); // Original unchanged
196 });
197
198 test("handles swapping same index", () => {
199 expect(swap(1, 1, [1, 2, 3])).toEqual([1, 2, 3]);
200 });
201
202 test("works with negative indices", () => {
203 // Note: swap function doesn't handle negative indices, it uses them as-is
204 expect(swap(-1, 0, [1, 2, 3])).toEqual([undefined, 2, 3] as any);
205 });
206});
207
208describe("remove", () => {
209 test("removes element at given index", () => {
210 expect(remove([1, 2, 3, 4], 2)).toEqual([1, 2, 4]);
211 expect(remove(["a", "b", "c"], 0)).toEqual(["b", "c"]);
212 });
213
214 test("returns new array (immutable)", () => {
215 const original = [1, 2, 3];
216 const removed = remove(original, 1);
217 expect(removed).toEqual([1, 3]);
218 expect(original).toEqual([1, 2, 3]); // Original unchanged
219 });
220
221 test("handles removing from empty array", () => {
222 expect(remove([], 0)).toEqual([]);
223 });
224
225 test("handles out of bounds index", () => {
226 expect(remove([1, 2, 3], 10)).toEqual([1, 2, 3]);
227 expect(remove([1, 2, 3], -1)).toEqual([1, 2, 3]);
228 });
229});
230
231describe("sum", () => {
232 test("sums array of positive numbers", () => {
233 expect(sum([1, 2, 3, 4])).toBe(10);
234 expect(sum([5, 10, 15])).toBe(30);
235 });
236
237 test("sums array with negative numbers", () => {
238 expect(sum([1, -2, 3, -4])).toBe(-2);
239 expect(sum([-1, -2, -3])).toBe(-6);
240 });
241
242 test("handles empty array", () => {
243 expect(sum([])).toBe(0);
244 });
245
246 test("handles single element array", () => {
247 expect(sum([42])).toBe(42);
248 });
249
250 test("handles zeros", () => {
251 expect(sum([0, 0, 0])).toBe(0);
252 expect(sum([1, 0, 2, 0, 3])).toBe(6);
253 });
254});
255
256describe("getIdxAt", () => {
257 test("handles positive indices within bounds", () => {
258 expect(getIdxAt(0, 5)).toBe(0);
259 expect(getIdxAt(2, 5)).toBe(2);
260 expect(getIdxAt(4, 5)).toBe(4);
261 });
262
263 test("handles positive indices out of bounds (wraps around)", () => {
264 expect(getIdxAt(5, 5)).toBe(0);
265 expect(getIdxAt(6, 5)).toBe(1);
266 expect(getIdxAt(10, 5)).toBe(0);
267 });
268
269 test("handles negative indices", () => {
270 expect(getIdxAt(-1, 5)).toBe(4);
271 expect(getIdxAt(-2, 5)).toBe(3);
272 expect(getIdxAt(-5, 5)).toBe(5); // -5 % 5 = 0, so 5 + 0 = 5
273 });
274
275 test("handles negative indices out of bounds (wraps around)", () => {
276 expect(getIdxAt(-6, 5)).toBe(4);
277 expect(getIdxAt(-7, 5)).toBe(3);
278 });
279
280 test("handles edge cases", () => {
281 expect(getIdxAt(0, 1)).toBe(0);
282 expect(getIdxAt(-1, 1)).toBe(1); // -1 % 1 = 0, so 1 + 0 = 1
283 });
284});
285
286describe("compareArr", () => {
287 test("returns true for identical arrays", () => {
288 expect(compareArr([1, 2, 3], [1, 2, 3])).toBe(true);
289 expect(compareArr(["a", "b", "c"], ["a", "b", "c"])).toBe(true);
290 });
291
292 test("returns false for different arrays", () => {
293 expect(compareArr([1, 2, 3], [1, 2, 4])).toBe(false);
294 expect(compareArr(["a", "b"], ["a", "c"])).toBe(false);
295 });
296
297 test("returns false for different lengths", () => {
298 expect(compareArr([1, 2, 3], [1, 2])).toBe(false);
299 // Note: compareArr uses every() which only checks up to the shorter array length
300 expect(compareArr([1, 2], [1, 2, 3])).toBe(true); // Only compares first 2 elements
301 });
302
303 test("returns true for empty arrays", () => {
304 expect(compareArr([], [])).toBe(true);
305 });
306
307 test("handles mixed types", () => {
308 expect(compareArr([1, "2", true], [1, "2", true])).toBe(true);
309 expect(compareArr([1, "2", true], [1, 2, true])).toBe(false);
310 });
311
312 test("handles nested arrays", () => {
313 // Note: compareArr uses === which doesn't work for nested array comparison
314 expect(
315 compareArr(
316 [
317 [1, 2],
318 [3, 4],
319 ],
320 [
321 [1, 2],
322 [3, 4],
323 ],
324 ),
325 ).toBe(false); // Different object references
326 expect(
327 compareArr(
328 [
329 [1, 2],
330 [3, 4],
331 ],
332 [
333 [1, 2],
334 [3, 5],
335 ],
336 ),
337 ).toBe(false);
338 });
339});
340