Skip to content

feat: day 9 disk fragmenter 20241209 #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

</details>

Expand Down
32 changes: 32 additions & 0 deletions src/2024/2024-12-09/README.md
Original file line number Diff line number Diff line change
@@ -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<br>
**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.
1 change: 1 addition & 0 deletions src/2024/2024-12-09/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
233313312141413
66 changes: 66 additions & 0 deletions src/2024/2024-12-09/lib/compact.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
85 changes: 85 additions & 0 deletions src/2024/2024-12-09/lib/disk.ts
Original file line number Diff line number Diff line change
@@ -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 ''
}
}
33 changes: 33 additions & 0 deletions src/2024/2024-12-09/lib/types.ts
Original file line number Diff line number Diff line change
@@ -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;
}


143 changes: 143 additions & 0 deletions src/2024/2024-12-09/lib/whole.ts
Original file line number Diff line number Diff line change
@@ -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<string, SpaceBlock>()

/** Structure for tracking the indices and lengths of `FileBlock` file blocks */
fileBlocks = new Map<string, FileBlock>()

/**
* 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
}
}
Loading
Loading