diff --git a/README.md b/README.md
index 18ce939..1b4f4da 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,7 @@ The codes are structured in a way that discusses and walks through the solution
- Day 6: Guard Gallivant [[link]](/src/2024/2024-12-06/README.md)
- Day 7: Bridge Repair [[link]](/src/2024/2024-12-07/README.md)
- Day 8: Resonant Collinearity [[link]](/src/2024/2024-12-08/README.md)
+- Day 9: Disk Fragmenter [[link]](/src/2024/2024-12-09/README.md)
diff --git a/src/2024/2024-12-09/README.md b/src/2024/2024-12-09/README.md
new file mode 100644
index 0000000..52082a0
--- /dev/null
+++ b/src/2024/2024-12-09/README.md
@@ -0,0 +1,32 @@
+## Day 9: Disk Fragmenter
+
+Visit the Advent of Code website for more information on this puzzle at:
+
+**Source:** https://adventofcode.com/2024/day/8
+**Status:** Complete ⭐⭐
+
+## Code
+
+### `disk.ts`
+
+**`Disk`** class - Object that provides common disk-like utility processing methods and stores processed data with the following methods:
+
+- **`createCharacterMap()`** - Converts files and spaces disk text representation into a character map, storing it in the `this.map[]` string array
+- **`calculateDiskChecksum()`** - Calculates the checksum of fragmented or defragmented disk files and spaces text map
+- **`getGrid()`** - Joins the `this.map[]` disk and spaces character array into a string format if it is less than `this.maxArrayToPrintLength`
+- **`getCompactGrid()`** - Joins the `this.compactMap[]` "compact" disk and spaces character array into a string format if it is less than `this.maxArrayToPrintLength`
+
+
+### `compact.ts`
+
+**`CompactDisk`** class (extends the **`Disk`** class) - Object that compacts disk character symbols representing file and space blocks from the right to the left-most free disk spaces with the following methods:
+
+- **`defragmentation()`** - Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map, Storing the result in the `this.compactMap[]` and returning the result.
+
+### `whole.ts`
+
+**WholeDisk** class - Object that compacts disk character symbols representing files and spaces by moving whole (groups) of file blocks into spaces that can accommodate them with the following methods:
+
+- **`findDiskBlocks()`** - Finds and initializes the array indices of file blocks and spaces, noting their lengths for tracking
+- **`findFreeSpaceBlock()`** - Finds the starting index of a group of free space blocks that can contain the whole length of a `FileBlock` in the `this.map[]` array from the `this.spaceBlocks` block map
+- **`defragmentation()`** - Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole file blocks, Storing the result in the this.compactMap[] and returning the result.
diff --git a/src/2024/2024-12-09/input.txt b/src/2024/2024-12-09/input.txt
new file mode 100644
index 0000000..ae6fb74
--- /dev/null
+++ b/src/2024/2024-12-09/input.txt
@@ -0,0 +1 @@
+233313312141413
\ No newline at end of file
diff --git a/src/2024/2024-12-09/lib/compact.ts b/src/2024/2024-12-09/lib/compact.ts
new file mode 100644
index 0000000..3e1f6ce
--- /dev/null
+++ b/src/2024/2024-12-09/lib/compact.ts
@@ -0,0 +1,66 @@
+import { Disk } from './disk.js'
+import type { CompactParams } from './types.js'
+
+/**
+ * @class CompactDisk
+ * @extends Disk
+ * @inheritdoc
+ * @description Object that compacts disk character symbols representing file and space blocks from the right to the left-most free disk spaces
+ */
+export class CompactDisk extends Disk {
+ /**
+ * Creates an instance of the `CompactDisk` class
+ * @constructor
+ * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks
+ * @param {boolean} printLog Flag to display the step-by-step file movement on screen. Defaults to `false`
+ */
+ constructor(diskMapText: string, printLog: boolean = false) {
+ super(diskMapText)
+ this.defragmentation({ printLog })
+ }
+
+ /**
+ * Moves file blocks one character at a time from the right to the left-most free disk spaces of
+ * a disk character map, storing the result in the `this.compactMap[]` and returning the result.
+ * @param {CompactParams} params - Parameters for input map string array.
+ * @returns {string[]} Array of defragmented files and spaces blocks
+ */
+ defragmentation (params: CompactParams): string[] {
+ const map: string[] = [...(params?.charMap || this.map)]
+
+ let charIndex = map.length - 1
+ let logs = ''
+
+ // Total number of file (non-space) blocks
+ const filesCount = map.reduce(
+ (sum, item) => Number(item) >= 0
+ ? sum += 1
+ : sum,
+ 0
+ )
+
+ while (charIndex >= filesCount) {
+ const dotIndex = map.indexOf('.')
+
+ if (params?.printLog) {
+ logs += this.getGrid(map)
+ }
+
+ // Swap file and space locations one unit at a time
+ if (dotIndex !== -1) {
+ map[dotIndex] = map[charIndex] ?? '.'
+ map[charIndex] = '.'
+ }
+
+ charIndex -= 1
+ }
+
+ if (params?.printLog) {
+ logs += this.getGrid(map)
+ console.log(logs)
+ }
+
+ this.compactMap = map
+ return map
+ }
+}
diff --git a/src/2024/2024-12-09/lib/disk.ts b/src/2024/2024-12-09/lib/disk.ts
new file mode 100644
index 0000000..a7336bf
--- /dev/null
+++ b/src/2024/2024-12-09/lib/disk.ts
@@ -0,0 +1,85 @@
+/**
+ * @class Disk
+ * @description Object that provides common disk-like utility processing methods and stores processed data.
+ */
+export class Disk {
+ /** 1-dimensional character array conversion of a disk map string */
+ protected map: string[] = []
+
+ /** Compacted disk character array */
+ protected compactMap: string[] = []
+
+ /** Maximum number of array elements to allow printing as string in console.log() */
+ maxArrayToPrintLength: number = 10000
+
+ /**
+ * Creates an instance of the `Disk` class
+ * @constructor
+ * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks
+ */
+ constructor(diskMapText: string) {
+ this.createCharacterMap(diskMapText)
+ }
+
+ /**
+ * Converts files and spaces disk text representation into a character map, storing it in the `this.map[]` string array
+ * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks
+ * @returns {string[]} Character array conversion of a disk map string
+ */
+ createCharacterMap (diskMapText: string): string[] {
+ for (let i = 0; i < diskMapText.length; i += 1) {
+ if (i % 2 === 1) {
+ this.map.push(
+ ...Array(Number(diskMapText[i])).fill('.')
+ )
+ } else {
+ const itemID = i / 2
+
+ this.map.push(
+ ...Array(Number(diskMapText[i])).fill(itemID.toString())
+ )
+ }
+ }
+
+ return this.map
+ }
+
+ /**
+ * Calculates the checksum of fragmented or defragmented disk files and spaces text map
+ * @param {string[]} [compactFileText] (Optional) Compacted disk map resulting from the `this.defragmentation()` function. Processes the local `this.compactMap[]` data if parameter is not provided.
+ * @returns {number} Check sum - sum of file block IDs multiplied with their positions
+ */
+ calculateDiskChecksum (compactFileText?: string[]): number {
+ return (compactFileText || this.compactMap)
+ .reduce((sum, item, index) => {
+ if (item === '.') return sum
+ return sum + Number(item) * index
+ }, 0)
+ }
+
+ /**
+ * Joins the `this.map[]` disk and spaces character array into a string format if it is less than `this.maxArrayToPrintLength`
+ * @param {string} [map] (Optional) Character string array similar to `this.map[]`. Uses the `this.map[]` array by default.
+ * @returns {string} linear string/text version of the `this.map[]` character array
+ */
+ getGrid (map?: string[]): string {
+ if ((map?.length ?? this.map.length) <= this.maxArrayToPrintLength) {
+ return (map ?? this.map).join('') + '\n'
+ }
+
+ return ''
+ }
+
+ /**
+ * Joins the `this.compactMap[]` "compact" disk and spaces character array into a string format if it is less than `this.maxArrayToPrintLength`
+ * @param {string} [map] (Optional) Character string array similar to `this.map[]`. Uses the `this.compactMap[]` array by default.
+ * @returns {string} linear string/text version of the `this.compactMap[]` character array
+ */
+ getCompactGrid (map?: string[]): string {
+ if ((map?.length ?? this.compactMap.length) <= this.maxArrayToPrintLength) {
+ return (map ?? this.compactMap).join('') + '\n'
+ }
+
+ return ''
+ }
+}
diff --git a/src/2024/2024-12-09/lib/types.ts b/src/2024/2024-12-09/lib/types.ts
new file mode 100644
index 0000000..d393e11
--- /dev/null
+++ b/src/2024/2024-12-09/lib/types.ts
@@ -0,0 +1,33 @@
+/**
+ * Parameters for input map string array
+ * @interface CompactParams
+ * @param {string} [charMap] - (Optional) Character mapping conversion of a disk map. Processes the local `this.map[]` if parameter is not provided.
+ * @param {boolean} [printLog] - (Optional) Flag to display the step-by-step file block movement on screen.
+ */
+export interface CompactParams {
+ charMap?: string[];
+ printLog? : boolean;
+}
+
+/**
+ * Properties of a system file block
+ * @interface FileBlock
+ * @param {number} length - Length of a file block - number of units that it occupies in the `this.map[]` array
+ * @param {number} index - Starting array index of a file block in the `this.map[]` array
+ */
+export interface FileBlock {
+ length: number;
+ index: number;
+}
+
+/**
+ * Properties of a space (free space) block
+ * @interface SpaceBlock
+ * @param {number} occupied - Number of space block units occupied by a `FileBlock`. A `SpaceBlock` is full if its `occupied`
+ * and `length` properties are equal
+ */
+export interface SpaceBlock extends FileBlock {
+ occupied: number;
+}
+
+
diff --git a/src/2024/2024-12-09/lib/whole.ts b/src/2024/2024-12-09/lib/whole.ts
new file mode 100644
index 0000000..95bc617
--- /dev/null
+++ b/src/2024/2024-12-09/lib/whole.ts
@@ -0,0 +1,143 @@
+import { Disk } from './disk.js'
+import type { CompactParams, FileBlock, SpaceBlock } from './types.js'
+
+/**
+ * @class WholeDisk
+ * @extends Disk
+ * @inheritdoc
+ * @description Object that compacts disk character symbols representing files and spaces by moving whole (groups) of file blocks into spaces that can accommodate them.
+ */
+export class WholeDisk extends Disk {
+ /** Structure for tracking the indices, length and availability of `SpaceBlock` disk space blocks */
+ spaceBlocks = new Map()
+
+ /** Structure for tracking the indices and lengths of `FileBlock` file blocks */
+ fileBlocks = new Map()
+
+ /**
+ * Creates an instance of the `WholeDisk` class
+ * @constructor
+ * @param {string} diskMapText Series of numbers representing an alternating file and disk space files
+ */
+ constructor(diskMapText: string, printLog: boolean = false) {
+ super(diskMapText)
+ this.defragmentation({ printLog })
+ }
+
+ /**
+ * Finds and initializes the array indices of file blocks and spaces, noting their lengths for tracking.
+ * @returns {void}
+ */
+ findDiskBlocks (): void {
+ let spaceCount = 0
+
+ this.map.forEach((digit, index) => {
+ if (digit !== '.') {
+ if (!this.fileBlocks.has(digit)) {
+ this.fileBlocks.set(digit, { length: 1, index })
+ } else {
+ this.fileBlocks.get(digit)!.length += 1
+ }
+
+ if (spaceCount > 0) {
+ this.spaceBlocks.set(String(index - spaceCount), {
+ length: spaceCount,
+ index: index - spaceCount,
+ occupied: 0
+ })
+
+ spaceCount = 0
+ }
+ } else {
+ spaceCount += 1
+ }
+ })
+ }
+
+ /**
+ * Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole file blocks, Storing the result in the `this.compactMap[]` and returning the result.
+ * @param {number} fileBlockLength Length of a `FileBlock` - number of spaces it occupies in the `this.map[]` array
+ * @param {number} fileStartIndex Starting index of a `FileBlock` in the `this.map[]` array
+ * @returns {number} Start array index in the `this.map[]` of a valid free space that can contain the whole length of a file block
+ */
+ findFreeSpaceBlock (fileBlockLength: number, fileStartIndex: number): number {
+ let indexOfSpace = -1
+
+ for (const item of this.spaceBlocks.values()) {
+ if (
+ // Space units already occupied with file blocks plus expected file block length is less than or equal
+ // the spaces (length) it can accommodate
+ item.occupied + fileBlockLength <= item.length &&
+ // The max units (spaces) should be large enough to contain the file length
+ item.length >= fileBlockLength &&
+ // Space block's index (position in the this.map[]) array is lower than the file's starting index to avoid repitition
+ item.index < fileStartIndex
+ ) {
+ indexOfSpace = item.index
+ break
+ }
+ }
+
+ return indexOfSpace
+ }
+
+ /**
+ * Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole files blocks,
+ * Storing the result in the `this.compactMap[]` and returning the result.
+ * @typedef {CompactParams} params - Parameters for input map string array.
+ * @returns {string[]} Array of defragmented files and spaces blocks
+ */
+ defragmentation (params: CompactParams): string[] {
+ const map: string[] = structuredClone(params.charMap ?? this.map)
+ let logs = ''
+
+ // Find the locations and stats of files and spaces
+ this.findDiskBlocks()
+
+ // Ascending order file IDs
+ const digitKeys = Array.from(this.fileBlocks.keys())
+
+ for (let i = digitKeys.length - 1; i >= 0; i -= 1) {
+ const key = digitKeys[i]
+
+ // Full file block
+ const file = this.fileBlocks.get(String(key))
+
+ if (file === undefined) continue
+
+ // Find the available free disk space location that can fit the full file block
+ const fileBlockLength = file.length
+ const spaceIndex = this.findFreeSpaceBlock(fileBlockLength, file.index)
+
+ const space = this.spaceBlocks.get(String(spaceIndex))
+ if (space === undefined) continue
+
+ const start = space.index + space.occupied
+ const end = start + fileBlockLength
+
+ // Move files to the free space
+ for (let j = start; j < end; j += 1) {
+ map[j] = String(key)
+ space.occupied += 1
+ }
+
+ // Remove file trace from the old location
+ for (
+ let k = file.index;
+ k < file.index + fileBlockLength;
+ k += 1
+ ) {
+ map[k] = '.'
+ }
+
+ logs += this.getCompactGrid(map)
+ }
+
+ if (params?.printLog) {
+ console.log(logs)
+ }
+
+ this.compactMap = map
+ return map
+ }
+}
diff --git a/src/2024/2024-12-09/main.ts b/src/2024/2024-12-09/main.ts
new file mode 100644
index 0000000..7a048d3
--- /dev/null
+++ b/src/2024/2024-12-09/main.ts
@@ -0,0 +1,33 @@
+import path from 'path'
+
+import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js'
+import { currentDirectory } from '@/utils/file.js'
+
+import { CompactDisk } from './lib/compact.js'
+import { WholeDisk } from './lib/whole.js'
+
+const input = readAOCInputFile({
+ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'),
+ type: AOC_OUTPUT_TYPE.STRING
+}) as string
+
+/**
+ * Part 1/2 of the 2024-12-09 quiz
+ * Counts the check sum of a compacted disk represented by strings of text
+ */
+export const quiz20241209_01 = () => {
+ const disk = new CompactDisk(input, true)
+ const sum = disk.calculateDiskChecksum()
+
+ console.log('Compacted disk checksum:', sum)
+}
+
+export const quiz20241209_02 = () => {
+ const disk = new WholeDisk(input, true)
+ const sum = disk.calculateDiskChecksum()
+
+ console.log('Disk - whole file blocks checksum:', sum)
+}
+
+quiz20241209_01()
+quiz20241209_02()
diff --git a/src/2024/2024-12-09/sample.test.ts b/src/2024/2024-12-09/sample.test.ts
new file mode 100644
index 0000000..ca1d259
--- /dev/null
+++ b/src/2024/2024-12-09/sample.test.ts
@@ -0,0 +1,23 @@
+import path from 'path'
+import { test, expect } from 'vitest'
+
+import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js'
+import { currentDirectory } from '@/utils/file.js'
+
+import { CompactDisk } from './lib/compact.js'
+import { WholeDisk } from './lib/whole.js'
+
+const input = readAOCInputFile({
+ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'),
+ type: AOC_OUTPUT_TYPE.STRING
+}) as string
+
+test('Defragmented disk checksum', () => {
+ const disk = new CompactDisk(input)
+ expect(disk.calculateDiskChecksum()).toBe(967)
+})
+
+test('Defragmented disk - (move whole file blocks) checksum', () => {
+ const disk = new WholeDisk(input)
+ expect(disk.calculateDiskChecksum()).toBe(1440)
+})