This blog post is twelfth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described on Day 12.

All solutions are in this GitHub repository.

## Solving Part One

Given a set of connections between underground caves, we must find all possible paths between the `start`

and `end`

caves and output the number of found paths. We can visit large caves (named using uppercase letters) multiple times, whereas we can only visit small caves (names using lowercase letters) once.

The strategy for solving part one was a recursive algorithm that returns the number of paths from a given cave to the `end`

cave. At each recursion step, we visit each linked cave of the current cave and recursively return the number of paths from the linked cave to the `end`

cave.

In terms of data structure, I opted for representing the cave graph using an Adjacency List. In other words, a key-value map, where the keys are the cave names and each value is an array corresponding to linked caves. For example, the input from the problem description would produce the following key-value map:

```
{
start: [ 'A', 'b' ],
A: [ 'start', 'c', 'b', 'end' ],
b: [ 'start', 'A', 'd', 'end' ],
c: [ 'A' ],
d: [ 'b' ],
end: [ 'A', 'b' ]
}
```

This data structure works well for iterating through adjacent caves.

I broke down the solution as follows:

- build the adjacency list from the input file
- get number of paths from the start cave recursively
- prevent visiting small caves twice

### Build the adjacency list from the input file

The first step to solving part one is reading each line representing a connection between two caves and adding this information to the adjacency list.

```
const links = {}
const lines = data.split('\n')
lines.forEach((line) => {
const [caveA, caveB] = line.split('-')
addLink(links, caveA, caveB)
addLink(links, caveB, caveA)
})
```

The `addLink`

function adds a connected cave (e.g. `caveB`

) to the corresponding list (e.g. `caveA`

list) or creates a new list if the cave does not exist.

```
const addLink = (links, caveA, caveB) => {
if (links[caveA]) {
links[caveA].push(caveB)
} else {
links[caveA] = [caveB]
}
}
```

### Get number of paths from the start cave recursively

The first call of the recursive `getPaths`

function begins from the `start`

cave. It passes the following parameters:

- the adjacent list
`links`

used for finding connected caves - an array of visited caves containing the
`start`

cave - the cave from which to count paths to the
`end`

cave

```
getPaths(links, ["start"], "start")
```

The outline for getting the number of paths recursively is the following:

```
const getPaths = (links, visitedSmallCaves, cave) => {
if (cave === "end") {
return 1
}
let generatedPaths = 0
links[cave].forEach(linkedCave => {
// TODO: if small cave not visited
generatedPaths += getPaths(links, visitedSmallCaves, linkedCave)
})
return generatedPaths
}
```

The recursion stop condition is when we reach the `end`

cave. Thus we can return `1`

representing a path found. Alternatively, we might return `0`

if we reach a dead-end (e.g. a small cave connected to already visited small caves).

Each recursion step, we iterate through the connected caves and sum the possible paths to the `end`

cave.

Keep in mind that the outlined algorithm doesn't yet consider visited small caves. Thus, if you run it as is, it will go back and forth between the same two caves until the program throws a stack overflow exception. We'll take a look at visited caves next.

### Prevent visiting small caves twice

To check whether a cave is small we need to verify whether it has lowercase characters

```
const isSmallCave = (cave) => cave.toLowerCase() === cave
```

Then we can update the `visitedSmallCaves`

like so:

```
const newVisitedArray = isSmallCave(linkedCave)
? [...visitedSmallCaves, linkedCave]
: visitedSmallCaves
```

We are then able to check whether we have visited a small cave using the `include`

function like so:

```
visitedSmallCaves.includes(linkedCave)
```

Thus, the updated `getPath`

function contains the following:

```
links[cave].forEach(linkedCave => {
if (!visitedSmallCaves.includes(linkedCave)) {
const newVisitedArray = isSmallCave(linkedCave) ? [...visitedSmallCaves, linkedCave] : visitedSmallCaves
generatedPaths += getPaths(links, newVisitedArray, linkedCave)
}
})
```

The final solution for part one is in my GitHub repository.

## Solving Part Two

Part two changes the visiting rules by allowing one to visit a single small cave twice, except the `start`

and `end`

caves, which one can only visit once.

The strategy for solving part two was adding an item called `twice`

to the `visitedSmallCaves`

array after visiting a small cave twice.

I updated the recursive `getPath`

function in order to separate the visiting logic to two separate functions: `hasVisitedCave`

and `getVisitedCaves`

.

```
links[cave].forEach(linkedCave => {
if (!hasVisitedCave(visitedSmallCaves, linkedCave)) {
generatedPaths += getPaths(links, getVisitedCaves(visitedSmallCaves, linkedCave), linkedCave)
}
})
```

The `hasVisitedCave`

function includes the new rules and allows visiting a single small cave that are not names `start`

twice.

```
const hasVisitedCave = (visited, cave) => {
if (cave === "start") {
return true
}
return visited.includes(cave) && visited.includes("twice")
}
```

The `getVisitedCaves`

function skips adding large caves, adds the `twice`

element to the array once a small cave has been visited twice and adds small caves that have not been visited before.

```
const getVisitedCaves = (visited, cave) => {
const isSmall = isSmallCave(cave)
if (!isSmall) {
return visited
}
if (visited.includes(cave)) {
return [...visited, "twice"]
}
return [...visited, cave]
}
```

The final solution for part two is in my GitHub repository.

## Conclusion

Day 12 was about finding all paths between two caves recursively. A graph implemented using an Adjacency List helped store connected caves.