diff --git a/README.md b/README.md index 99b8085..60213d8 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,12 @@ ## 2019 Worth taking notes on: -* Day 2a and b -* Day 6 a and b +* [Day 2b - System of Equations](src/2019/2b.md) * Day 12 a and b * Day 14 for sure! -* Day 15 - the trick with this is to not go backwards until you have to! \ No newline at end of file +* Day 15 - the trick with this is to not go backwards until you have to! +* [Day 18a - Robots Unlocking Keys and Doors](src/2019/18.md) + * Dijkstra's algorithm +* [Day 18b - Multiple Robots Unlocking Keys and Doors](src/2019/18b.md) + * Combinations with Dijkstra. + diff --git a/src/2019/18.md b/src/2019/18.md index 2b587a5..5502280 100644 --- a/src/2019/18.md +++ b/src/2019/18.md @@ -1,4 +1,4 @@ -# Day 18: Many-Worlds Interpretation +# Day 18: Many-Worlds Interpretation - [Code](18.ts) Only one **entrance** (marked `@`) is present among the **open passages** (marked `.`) and **stone walls** (`#`), but you also detect an assortment of **keys** (shown as lowercase letters) and **doors** (shown as uppercase letters). Keys of a given letter open the door of the same letter: `a` opens `A`, `b` opens `B`, and so on. You aren't sure which key you need to disable the tractor beam, so you'll need to collect all of them. For example, suppose you have the following map: @@ -102,4 +102,263 @@ In your [input](18.txt), how many steps is the shortest path that collects all o ## Solution I learned the hard way that when trying to find the shortest distance between two points in a graph or a grid, **do not use DFS!** You have to use a special type of BFS. In BFS, we use a Queue, right? Well, in this one, we use a *Priority Queue* (or a *Heap*), which makes it a **Dijkstra's Algorithm**. -The solution is broken up into two parts \ No newline at end of file +The solution is broken up into two parts +1. For each `key1` to `key2` pair, get + * **Minimum** Steps between `key1` and `key2`. + * Doors between `key1` and `key2`. + * Other keys inbetween `key1` and `key2`. +2. Starting from the `@` key, traverse any reachable keys from it, keeping track of the min steps taken. Stop when all keys have been traversed. This can be solved using DFS, BFS, or Dijkstra's. I went with Dijstra's. + +Here's a pseudocode for step 2: +``` +distanceToCollectKeys(currentKey, keys): + + if keys is empty: + return 0 + + result := infinity + foreach key in reachable(keys): + d := distance(currentKey, key) + distanceToCollectKeys(key, keys - key) + result := min(result, d) + + return result; +``` + +### Step 1: Key to Key Map +It's so important to do this via Dijkstra's instead of DFS. If you don't believe me, check out the the distance differences from '@' to all keys in the graph. + +DFS +``` +r: 16 l: 28 b: 42 p: 58 t: 78 q: 84 f: 100 x: 114 h: 132 g: 140 k: 158 m: 170 j: 182 +u: 196 e: 208 d: 224 s: 244 c: 260 y: 270 i: 286 w: 296 v: 314 n: 328 o: 340 a: 354 z: 366 +``` + +Dijkstra's Algorithm +``` +r: 16 l: 28 b: 42 p: 54 t: 72 q: 80 f: 96 x: 110 h: 126 g: 138 k: 154 m: 170 j: 182 +u: 196 e: 208 d: 222 s: 240 c: 254 y: 264 i: 280 w: 294 v: 308 n: 324 o: 336 a: 350 z: 362 +``` + +```typescript +// will return all short AND LONG paths fromKey to toKey + +const getNearestKeysMap = (tunnel: ITunnel) => { + const { keys, entrance } = tunnel; + return new Map(Array.from(keys.entries()) + .map(([k, p]) => [k, getDistancesToAllKeys(p, tunnel)]) + ).set('@', getDistancesToAllKeys(entrance, tunnel)); +}; + +const getDistancesToAllKeys = (from: IPoint, tunnel: IBaseTunnel) => { + const queue = new PriorityQueue(p => -1 * p.distance); + const visited = new GenericSet(toString); + const result: IKeyToKeyInfo[] = []; + const isValid = makeIsValid(tunnel)(visited); + const addPointInfo = makeAddPointInfo(tunnel); + + visited.add(from); + queue.enqueue({ + point: from, + distance: 0, + keysObtained: new GenericSet(s => s), + doorsInWay: new GenericSet(s => s) + }); + + while (!queue.isEmpty()) { + const {point, distance, keysObtained, doorsInWay} = queue.dequeue()!; + const neighbors = getNeighbors(point) + .filter(isValid) + .map(addPointInfo); + + for (let i = 0; i < neighbors.length; i++) { + const neighbor = neighbors[i]; + const newKeysObtained = i === neighbors.length - 1 ? keysObtained : new GenericSet(keysObtained); + const newDoorsInWay = i === neighbors.length - 1 ? doorsInWay : new GenericSet(doorsInWay); + if (neighbor.door != null) + newDoorsInWay.add(neighbor.door); + if (neighbor.key != null) + newKeysObtained.add(neighbor.key); + + visited.add(neighbor.point); + queue.enqueue({ + point: neighbor.point, + distance: distance + 1, + keysObtained: newKeysObtained, + doorsInWay: newDoorsInWay + }); + + if (neighbor.key != null) { + const keysInWay = new GenericSet(keysObtained); + keysInWay.delete(neighbor.key); // don't include toKey in keysInWay + result.push({ + toKey: neighbor.key, + steps: distance + 1, + doorsInWay: new GenericSet(doorsInWay), + keysInWay + }); + } + } + } + return result; +}; +``` + +Utility functions for the method above: +```typescript +interface IPriorityQueueState { + point: IPoint; + distance: number; + keysObtained: GenericSet; + doorsInWay: GenericSet; +} + +interface IKeyToKeyInfo { + toKey: string; + steps: number; + doorsInWay: GenericSet; + keysInWay: GenericSet; +} + +const getNeighbors = ({row, col}: IPoint, exclude?: IPoint | null): IPoint[] => + exclude == null ? + [{row: row - 1, col}, {row: row + 1, col}, {row, col: col + 1}, {row, col: col - 1}] : + getNeighbors({row, col}, null).filter(p => p.row !== exclude.row || p.col !== exclude.col); + +const makeIsValid = ({grid, keys, doors}: IBaseTunnel) => (visited: GenericSet) => (p: IPoint) => { + const cell = grid[p.row][p.col]; + if (cell == null || + visited.has(p) || + cell !== '.' && + cell !== '@' && + !equals(keys.get(cell), p) && // stepping on a key is valid + !equals(doors.get(cell), p) // stepping on a door is valid. We don't care about doors right now + ) + return false; + return true; +}; + +const makeAddPointInfo = ({grid, keys, doors}: IBaseTunnel) => (p: IPoint) => { + const cell = grid[p.row][p.col]; + return { + point: p, + key: equals(p, keys.get(cell)) ? cell : null, + door: equals(p, doors.get(cell)) ? cell : null + }; +}; +``` + +There are cases where the above code won't do so well. E.g., imagine the grid + +``` +[2, 20 steps] +########## +#.a###.Ab# +#.B..@.### +#...###### +########## +``` +It'd say `@ -> a` equals `4`, and `@ -> b` equals `4`. However, each key requires the other key, resulting in failure. + +To avoid this, the `getDistancesToAllKeys` would have to return the following: +* `@ -> a` equals `4` +* `@ -> a` equals `6` +* `@ -> a` equals `8` +* `@ -> b` equals `4` + +We can do this by removing the `visited` Set from the equation, allowing us to find all routes. However, since it passes the test case without this issue, we won't add this constraint. + +### Step 2: Traversing the Transformed Map +Now that we have transformed the plane from a grid to a graph of keys to keys, we can solve this using any graph traversal we want. I implemented all three solutions. Here's what I learned +* Dijkstra was the fastest, then DFS, then BFS. +* BFS and Dijkstra's are almost identical. The key differences are: + * BFS uses a normal queue, Dijkstra's uses a PriorityQueue, dequeuing the element with the fewest steps first. + * Because of this, Dijkstra can terminate as soon as it finds a solution, BFS has to traverse the entire map. + * DFS also has the traverse the entire graph. + +Here is the Dijkstra and BFS solution (see comments to see how to convert it to BFS): + +```typescript +const solve = (keytoKeyMap: Map) => { + type QueueState = {totalSteps: number; keysObtained: string; atKey: string}; + // const queue = new Queue(); FOR BFS + const queue = new PriorityQueue(p => -1 * p.totalSteps); + const visited = new Map(); + const getReachableKeys = makeGetReachableKeys(keytoKeyMap); + let minSteps = Infinity; + + queue.enqueue({totalSteps: 0, keysObtained: '', atKey: '@'}); + while (!queue.isEmpty()) { + const { totalSteps, keysObtained, atKey } = queue.dequeue()!; + if (keysObtained.length === keytoKeyMap.size - 1) { + minSteps = Math.min(totalSteps, minSteps); + return minSteps; // for BFS, remove this line. + } + + const reachableKeys = getReachableKeys(atKey, new GenericSet(k => k, [...keysObtained])); + for (const key of reachableKeys) { + const newKeysObtained = keysObtained + key.toKey; + const newCacheKey = getCacheKey(key.toKey, newKeysObtained); + const newTotalSteps = totalSteps + key.steps; + // the visited.get(newCacheKey)! > newTotalSteps is crucial, because you may find a later route that has + // fewer steps + if (!visited.has(newCacheKey) || visited.get(newCacheKey)! > newTotalSteps) { + queue.enqueue({ + totalSteps: newTotalSteps, + atKey: key.toKey, + keysObtained: newKeysObtained + }); + visited.set(newCacheKey, newTotalSteps); + } + } + } + return minSteps; +}; +``` + +And lastly, the DFS solution is as follows: + +```typescript +const solve = (keyToKeyMap: Map) => { + const getReachableKeys = makeGetReachableKeys(keyToKeyMap); + const cache: {[key: string]: number} = {}; + + const helper = (fromKey: string, keysObtained: string): number => { + if (keysObtained.length === keyToKeyMap.size - 1) { + return 0; + } // minus 1 because @ is in keysToKeysSteps + const cacheKey = getCacheKey(fromKey, keysObtained); + if (cache[cacheKey] != null) + return cache[cacheKey]; + // has to be uppercased so it can be compared against doors + const reachableKeys = getReachableKeys(fromKey, new GenericSet(k => k, [...keysObtained])); + let totalSteps = Infinity; + for (const reachableKey of reachableKeys) { + const steps = + reachableKey.steps + + Math.min(totalSteps, helper(reachableKey.toKey, keysObtained + reachableKey.toKey)); + totalSteps = Math.min(totalSteps, steps); + } + cache[cacheKey] = totalSteps; + return totalSteps; + }; + + return helper('@', ''); +}; +``` + +Helper functions used by both: + +```typescript +export const makeGetReachableKeys = + (keyToKeyMap: Map) => + (fromKey: string, keysObtained: GenericSet) => + keyToKeyMap.get(fromKey) + ?.filter(k => !keysObtained.has(k.toKey)) + ?.filter(k => k.doorsInWay.subsetOf(keysObtained, k => k.toString().toLowerCase())) + ?.filter(k => k.keysInWay.subsetOf(keysObtained)) ?? []; + // if you skip over keysInWay, you'e looking at a suboptiaml route because you'll examine routes + // like a -> c, when there is key b between a -> c + +export const getCacheKey = (fromKey: string, keysObtained: string) => + `${fromKey},${[...keysObtained].sort().toString()}`; +``` \ No newline at end of file diff --git a/src/2019/18.ts b/src/2019/18.ts index 66e33ac..6da2be4 100644 --- a/src/2019/18.ts +++ b/src/2019/18.ts @@ -16,6 +16,20 @@ interface ITunnel extends IBaseTunnel { entrance: IPoint; } +interface IPriorityQueueState { + point: IPoint; + distance: number; + keysObtained: GenericSet; + doorsInWay: GenericSet; +} + +export interface IKeyToKeyInfo { + toKey: string; + steps: number; + doorsInWay: GenericSet; + keysInWay: GenericSet; +} + export const toTunnel = ({name, grid}: {name: string; grid: string[][]}): ITunnel => { const keys = new Map(); const doors = new Map(); @@ -39,7 +53,7 @@ export const toTunnel = ({name, grid}: {name: string; grid: string[][]}): ITunne }; }; -export const getSimulations = () => getRunsFromIniFile(input) +const getSimulations = () => getRunsFromIniFile(input) .map(s => ({ name: s.name, grid: s.content.split(/\r?\n/).filter(s => s.trim() !== '').map(r => r.split('')) @@ -55,139 +69,6 @@ const getNeighbors = ({row, col}: IPoint, exclude?: IPoint | null): IPoint[] => [{row: row - 1, col}, {row: row + 1, col}, {row, col: col + 1}, {row, col: col - 1}] : getNeighbors({row, col}, null).filter(p => p.row !== exclude.row || p.col !== exclude.col); -export const collectKeys = ({grid, keys, entrance}: ITunnel) => { - const get = ({row, col}: IPoint) => grid[row][col]; - const isValid = (p: IPoint, visited: Set, keysObtained: Set) => { - const cell = get(p); - const pHash = toString(p); - if (cell == null || - cell !== '.' && - cell !== '@' && - toString(keys.get(cell)) !== pHash && // stepping on a key is valid - !keysObtained.has(cell.toLowerCase()) && // stepping on A if 'a' is obtained is valid - !visited.has(pHash) - ) - return false; - return true; - }; - - const helper = (start: IPoint, from: IPoint | null, visited: Set, keysObtained: Set): number => { - if (!isValid(start, visited, keysObtained)) - return Infinity; - - const cell = get(start); - let onKey = false; - - visited.add(toString(start)); - if (keys.has(cell) && !keysObtained.has(cell)) { - keysObtained.add(cell); - console.log('OBTAINED KEY: ', cell, '\n\tRemaining: ', Array.from(keys.keys()).filter(k => !keysObtained.has(k))); - onKey = true; - if (keysObtained.size === keys.size) - return 0; - } - - // if currently stepping on key, you're allowed to go back - const neighbors = getNeighbors(start, onKey ? null : from); - const steps: number[] = []; - for (const neighbor of neighbors) - steps.push(helper(neighbor, start, onKey ? new Set() : visited, new Set(keysObtained))); - - // console.log('backtracking at ', toKey(start)); - return 1 + Math.min(...steps); - }; - - return helper(entrance, null, new Set(), new Set()); -}; - -export const getNearestKeysMap = ({grid, keys, doors, entrance}: ITunnel) => { - const result = new Map}[]>(); - const cache: {[toKey: string]: {[fromPoint: string]: IPoint[]}} = {}; // cache[toKey][fromPoint] = steps; - for (const k of keys.keys()) { - cache[k] = {}; - result.set(k, []); - } - result.set('@', []); // need to add this for entrance - - const get = ({row, col}: IPoint) => grid[row][col]; - const isValid = (p: IPoint, visited: GenericSet) => { - const cell = get(p); - if (cell == null || - visited.has(p) || - cell !== '.' && - cell !== '@' && - !equals(keys.get(cell), p) && // stepping on a key is valid - !equals(doors.get(cell), p) // stepping on a door is valid. We don't care about doors right now - ) - return false; - return true; - }; - const helper = (start: IPoint, visited: GenericSet, toKey: string): IPoint[] | null => { - if (!isValid(start, visited)) - return null; - // console.log('at', start, 'looking for', toKey); - const cell = get(start); - const pointStr = toString(start); - if (cache[toKey][pointStr] != null) { - // console.log('HIT CACHE! from:', start, 'to:', toKey, 'steps:', cache[toKey][pointStr]); - return cache[toKey][pointStr]; - } - if (cell === toKey) - return []; - - visited.add(start); - const neighbors = getNeighbors(start).filter(n => !visited.has(n)); - const allSteps: IPoint[][] = []; - for (const neighbor of neighbors) { - const steps = helper(neighbor, visited, toKey); - if (steps != null) - allSteps.push(steps); - } - - if (allSteps.length === 0) - return null; - - const minSteps = allSteps.sort((a, b) => a.length - b.length)[0]; - minSteps.unshift(start); - // cache[toKey][pointStr] = minSteps; - // console.log('put', cache[toKey][pointStr], 'into cache for toKey =', toKey, 'point =', pointStr); - return minSteps; - }; - - for (const k of keys.keys()) { - const steps = helper(entrance, new GenericSet(toString), k); - if (steps != null) - result.get('@')!.push({ - toKey: k, - steps: new GenericSet(toString, steps) - }); - } - - for (const fromKey of keys.keys()) - for (const toKey of keys.keys()) { - if (fromKey === toKey) - continue; - const steps = helper(keys.get(fromKey)!, new GenericSet(toString), toKey); - if (steps == null) - continue; - // if fromKey to toKey is 5 steps, then toKey to fromKey is 5 steps. - cache[toKey][toString(keys.get(fromKey))] = [...steps]; - cache[fromKey][toString(keys.get(toKey))] = [...steps].reverse(); - result.get(fromKey)!.push({ - toKey, - steps: new GenericSet(toString, [...steps]) - }); - } - return result; -}; - -interface IPriorityQueueState { - point: IPoint; - distance: number; - keysObtained: GenericSet; - doorsInWay: GenericSet; -} - export const makeIsValid = ({grid, keys, doors}: IBaseTunnel) => (visited: GenericSet) => (p: IPoint) => { const cell = grid[p.row][p.col]; if (cell == null || @@ -210,23 +91,27 @@ export const makeAddPointInfo = ({grid, keys, doors}: IBaseTunnel) => (p: IPoint }; }; -// will return all short and long paths fromKey to toKey +// will return all short AND LONG paths fromKey to toKey export const getDistancesToAllKeys = (from: IPoint, tunnel: IBaseTunnel) => { const queue = new PriorityQueue(p => -1 * p.distance); const visited = new GenericSet(toString); - const result: {toKey: string; steps: number; doorsInWay: GenericSet; keysInWay: GenericSet}[] = []; + const result: IKeyToKeyInfo[] = []; const isValid = makeIsValid(tunnel)(visited); const addPointInfo = makeAddPointInfo(tunnel); - queue.enqueue({point: from, distance: 0, keysObtained: new GenericSet(s => s), doorsInWay: new GenericSet(s => s)}); visited.add(from); + queue.enqueue({ + point: from, + distance: 0, + keysObtained: new GenericSet(s => s), + doorsInWay: new GenericSet(s => s) + }); while (!queue.isEmpty()) { const {point, distance, keysObtained, doorsInWay} = queue.dequeue()!; const neighbors = getNeighbors(point) .filter(isValid) .map(addPointInfo); - for (let i = 0; i < neighbors.length; i++) { const neighbor = neighbors[i]; const newKeysObtained = i === neighbors.length - 1 ? keysObtained : new GenericSet(keysObtained); @@ -236,16 +121,17 @@ export const getDistancesToAllKeys = (from: IPoint, tunnel: IBaseTunnel) => { if (neighbor.key != null) newKeysObtained.add(neighbor.key); + visited.add(neighbor.point); queue.enqueue({ point: neighbor.point, distance: distance + 1, keysObtained: newKeysObtained, doorsInWay: newDoorsInWay }); - visited.add(neighbor.point); + if (neighbor.key != null) { const keysInWay = new GenericSet(keysObtained); - keysInWay.delete(neighbor.key); + keysInWay.delete(neighbor.key); // don't include toKey in keysInWay result.push({ toKey: neighbor.key, steps: distance + 1, @@ -258,20 +144,13 @@ export const getDistancesToAllKeys = (from: IPoint, tunnel: IBaseTunnel) => { return result; }; -export const getNearestKeysMap2 = (tunnel: ITunnel) => { +export const getNearestKeysMap = (tunnel: ITunnel) => { const { keys, entrance } = tunnel; return new Map(Array.from(keys.entries()) .map(([k, p]) => [k, getDistancesToAllKeys(p, tunnel)]) ).set('@', getDistancesToAllKeys(entrance, tunnel)); }; -export interface IKeyToKeyInfo { - toKey: string; - steps: number; - doorsInWay: GenericSet; - keysInWay: GenericSet; -} - export const makeGetReachableKeys = (keyToKeyMap: Map) => (fromKey: string, keysObtained: GenericSet) => @@ -279,24 +158,23 @@ export const makeGetReachableKeys = ?.filter(k => !keysObtained.has(k.toKey)) ?.filter(k => k.doorsInWay.subsetOf(keysObtained, k => k.toString().toLowerCase())) ?.filter(k => k.keysInWay.subsetOf(keysObtained)) ?? []; - // if you skip over keysInWay, you'e looking at a suboptiaml route + // if you skip over keysInWay, you'e looking at a suboptiaml route because you'll examine routes + // like a -> c, when there is key b between a -> c + +export const getCacheKey = (fromKey: string, keysObtained: string) => + `${fromKey},${[...keysObtained].sort().toString()}`; export const solve = (keyToKeyMap: Map) => { const getReachableKeys = makeGetReachableKeys(keyToKeyMap); - const getCacheKey = (fromKey: string, keysObtained: string) => `${fromKey},${[...keysObtained].sort().toString()}`; const cache: {[key: string]: number} = {}; - let count = 0; const helper = (fromKey: string, keysObtained: string): number => { if (keysObtained.length === keyToKeyMap.size - 1) { - // console.log('count', count); return 0; } // minus 1 because @ is in keysToKeysSteps const cacheKey = getCacheKey(fromKey, keysObtained); if (cache[cacheKey] != null) return cache[cacheKey]; - count++; - console.log('atKey:', fromKey, 'keysObtained', keysObtained); // has to be uppercased so it can be compared against doors const reachableKeys = getReachableKeys(fromKey, new GenericSet(k => k, [...keysObtained])); let totalSteps = Infinity; @@ -310,16 +188,12 @@ export const solve = (keyToKeyMap: Map) => { return totalSteps; }; - const x = helper('@', ''); - console.log('count', count); - return x; + return helper('@', ''); }; -export const getCacheKey = (fromKey: string, keysObtained: string) => - `${fromKey},${[...keysObtained].sort().toString()}`; - export const solve2 = (keytoKeyMap: Map) => { type QueueState = {totalSteps: number; keysObtained: string; atKey: string}; + // const queue = new Queue(); FOR BFS const queue = new PriorityQueue(p => -1 * p.totalSteps); const visited = new Map(); const getReachableKeys = makeGetReachableKeys(keytoKeyMap); @@ -330,6 +204,7 @@ export const solve2 = (keytoKeyMap: Map) => { const { totalSteps, keysObtained, atKey } = queue.dequeue()!; if (keysObtained.length === keytoKeyMap.size - 1) { minSteps = Math.min(totalSteps, minSteps); + return minSteps; // for BFS, remove this line. } const reachableKeys = getReachableKeys(atKey, new GenericSet(k => k, [...keysObtained])); @@ -337,6 +212,8 @@ export const solve2 = (keytoKeyMap: Map) => { const newKeysObtained = keysObtained + key.toKey; const newCacheKey = getCacheKey(key.toKey, newKeysObtained); const newTotalSteps = totalSteps + key.steps; + // the visited.get(newCacheKey)! > newTotalSteps is crucial, because you may find a later route that has + // fewer steps if (!visited.has(newCacheKey) || visited.get(newCacheKey)! > newTotalSteps) { queue.enqueue({ totalSteps: newTotalSteps, @@ -352,27 +229,13 @@ export const solve2 = (keytoKeyMap: Map) => { export const run = () => { console.log('STARTING'); - const sims = getSimulations(); + const sims = getSimulations().slice(1, 2); for (const s of sims) { - const d = getNearestKeysMap2(s); - + const d = getNearestKeysMap(s); console.log(timer.start(`18 - ${s.name}, ${s.keys.size} keys, ${s.doors.size} doors`)); - // console.log(s.grid.map(r => r.join(' ')).join('\n')); - // const keysToKeys = getNearestKeysMap(s); - // const simpleKeysToKeys = new Map( - // Array.from(keysToKeys.entries()) - // .map(([k, s]) => [ - // k, - // s.map(v => ({toKey: v.toKey, steps: v.steps.size})) - // ])); - // console.log(simpleKeysToKeys.get('@')?.sort((a, b) => a.steps - b.steps).map(a => `${a.toKey}: ${a.steps}`).join(' ')); - // console.log(solve(simpleKeysToKeys, getDoorsNeededUnlockedMap(keysToKeys, s.doors))); - // Array.from(d.keys()).forEach(k => - // console.log(`key=${k}\t`, d.get(k)?.map(a => `${a.toKey}: ${a.steps} (${Array.from(a.keysInWay.values()).concat(Array.from(a.doorsInWay.values()))})`).join(' ')) - // ); - // console.log(d.get('@')?.map(a => `${a.toKey}: ${a.steps} (${Array.from(a.keysInWay.values())})`).join(' ')); - console.log('answer =', solve2(d)); - // console.log(solve2(d)); + console.log(s.grid.map(r => r.join(' ')).join('\n')); + console.log('answer (DIJ) =', solve2(d)); + console.log('answer (DFS) =', solve(d)); console.log(timer.stop()); } }; diff --git a/src/2019/18b.md b/src/2019/18b.md new file mode 100644 index 0000000..dc645ab --- /dev/null +++ b/src/2019/18b.md @@ -0,0 +1,320 @@ +# Day 18: Many-Worlds Interpretation Part 2 - [Code](18b.ts) + +You arrive at the vault only to discover that there is not one vault, but four - each with its own entrance. + +On your map, find the area in the middle that looks like this: +``` +... +.@. +... +``` + +Update your map to instead use the correct data: +``` +@#@ +### +@#@ +``` +This change will split your map into four separate sections, each with its own entrance: +``` +####### ####### +#a.#Cd# #a.#Cd# +##...## ##@#@## +##.@.## --> ####### +##...## ##@#@## +#cB#Ab# #cB#Ab# +####### ####### +``` + +Because some of the keys are for doors in other vaults, it would take much too long to collect all of the keys by yourself. Instead, you deploy four remote-controlled robots. Each starts at one of the entrances (`@`). + +Your goal is still to `collect all of the keys in the fewest steps`, but now, each robot has its own position and can move independently. You can only remotely control a single robot at a time. Collecting a key instantly unlocks any corresponding doors, regardless of the vault in which the key or door is found. + +For example, in the map above, the top-left robot first collects key `a`, unlocking door A in the bottom-right vault: +``` +####### +#@.#Cd# +##.#@## +####### +##@#@## +#cB#.b# +####### +``` +Then, the bottom-right robot collects key `b`, unlocking door `B` in the bottom-left vault: +``` +####### +#@.#Cd# +##.#@## +####### +##@#.## +#c.#.@# +####### +``` +Then, the bottom-left robot collects key `c`: +``` +####### +#@.#.d# +##.#@## +####### +##.#.## +#@.#.@# +####### +``` +Finally, the top-right robot collects key `d`: +``` +####### +#@.#.@# +##.#.## +####### +##.#.## +#@.#.@# +####### +``` +In this example, it only took **8 steps** to collect all of the keys. + +Sometimes, multiple robots might have keys available, or a robot might have to wait for multiple keys to be collected: +``` +############### +#d.ABC.#.....a# +######@#@###### +############### +######@#@###### +#b.....#.....c# +############### +``` +First, the top-right, bottom-left, and bottom-right robots take turns collecting keys `a`, `b`, and `c`, a total of `6 + 6 + 6 = 18 steps`. Then, the top-left robot can access key `d`, spending another `6 steps`; collecting all of the keys here takes a minimum of `24 steps`. + +Here's a more complex example: +``` +############# +#DcBa.#.GhKl# +#.###@#@#I### +#e#d#####j#k# +###C#@#@###J# +#fEbA.#.FgHi# +############# +``` +* Top-left robot collects key a. +* Bottom-left robot collects key b. +* Top-left robot collects key c. +* Bottom-left robot collects key d. +* Top-left robot collects key e. +* Bottom-left robot collects key f. +* Bottom-right robot collects key g. +* Top-right robot collects key h. +* Bottom-right robot collects key i. +* Top-right robot collects key j. +* Bottom-right robot collects key k. +* Top-right robot collects key l. + +In the above example, the fewest steps to collect all of the keys is **32**. + +Here's an example with more choices: +``` +############# +#g#f.D#..h#l# +#F###e#E###.# +#dCba@#@BcIJ# +############# +#nK.L@#@G...# +#M###N#H###.# +#o#m..#i#jk.# +############# +``` +One solution with the fewest steps is: + +* Top-left robot collects key e. +* Top-right robot collects key h. +* Bottom-right robot collects key i. +* Top-left robot collects key a. +* Top-left robot collects key b. +* Top-right robot collects key c. +* Top-left robot collects key d. +* Top-left robot collects key f. +* Top-left robot collects key g. +* Bottom-right robot collects key k. +* Bottom-right robot collects key j. +* Top-right robot collects key l. +* Bottom-left robot collects key n. +* Bottom-left robot collects key m. +* Bottom-left robot collects key o. + +This example requires at least **72** steps to collect all keys. + +After updating your map and using the remote-controlled robots, **what is the fewest steps necessary to collect all of the keys**? + +## Solution +The hardest part about this problem is wrapping your head around it. The maps are changed in a way so that no robot other than `robot1` can reach `a`, and no robot other than `robot2` can access `b`. *No key can be accessed by two or more robots*. The quadrants are blocked off in a way that robots can't go into other quadrants. + +### Lower-bound / Good Guess + +The first thing we do is get a lower-bound on the number of steps needed. I.e., we assume each robot has all doors that have their key belonging to other quadrants unlocked. E.g., if `robot1` has door `A` but key `a` belongs in `robot2`'s quadrant, we assume `A` is unlocked because `robot2` will get key `a` at some point eventually because it has to. **My answer from this lower-bound problem was the correct answer to my input!** As well as Greg's! + +```typescript +export const lowerBound = (keytoKeyMap: Map, entrances: Map) => { + type QueueState = {totalSteps: number; keysObtained: string; atKey: string}; + const queue = new PriorityQueue(p => -1 * p.totalSteps); + const visited = new Map(); + const getReachableKeys = makeGetReachableKeys(keytoKeyMap); + const keyToQuadrantMap = getKeyToEntranceMap(keytoKeyMap, entrances); // The entrance a key belongs to + // all the keys that will be obtained in a quadrant. + const allKeysInQuadrant = new Map(Array.from(entrances.keys()).map(k => [ + k, // key of the Map + keytoKeyMap.get(k)!.map(k1 => k1.toKey).join('') + ])); + const keysPerQuadrant = new Map(Array.from(entrances.keys()).map(k => [k, keytoKeyMap.get(k)!.length])); + const minSteps = new Map(Array.from(entrances.keys()).map(k => [k, Infinity])); + + for (const e of entrances.keys()) + queue.enqueue({totalSteps: 0, keysObtained: '', atKey: e}); + + while (!queue.isEmpty()) { + const { totalSteps, keysObtained, atKey } = queue.dequeue()!; + const quadrant = keyToQuadrantMap.get(atKey)!; + + if (keysObtained.length === keysPerQuadrant.get(quadrant)) { + const currMinSteps = minSteps.get(quadrant)!; + minSteps.set(quadrant, Math.min(currMinSteps, totalSteps)); + } + + const keysObtainedOtherQuadrants = getAllValuesExcludingKey(quadrant, allKeysInQuadrant); + const keysObtainedSet = new GenericSet(k => k, [...keysObtained, ...keysObtainedOtherQuadrants]); + const reachableKeys = getReachableKeys(atKey, keysObtainedSet); + + for (const key of reachableKeys) { + const newKeysObtained = keysObtained + key.toKey; + const newCacheKey = getCacheKey(key.toKey, newKeysObtained); + const newTotalSteps = totalSteps + key.steps; + if (!visited.has(newCacheKey) || visited.get(newCacheKey)! > newTotalSteps) { + queue.enqueue({ + totalSteps: totalSteps + key.steps, + atKey: key.toKey, + keysObtained: newKeysObtained + }); + visited.set(newCacheKey, newTotalSteps); + } + } + } + return Array.from(minSteps.values()).reduce((a, c) => a + c, 0); +}; +``` + +The lower-bound above isn't always correct. E.g., for the following maze: + +``` +[4, 36steps] +########### +#.DeE#aB..# +#.#######.# +#...@#@...# +########### +#cA.@#@...# +####.####.# +#b...#dC..# +########### +``` +It returns **34 steps**, because the bottom-left quadrant assumes it'll have door `A` unlocked sometime in the future, so it goes `@ -> c -> b`, when really, it needs to go `@ -> b -> c` because then will it be able to unlock door `B` for top-right quadant, and so on. The right answer is **36 steps**. + +### Always Correct Solution +It helps to visualize the problem with just two robots first. So imagine if there were only two robots: `@` and `@1`. The reachable key for `@` are `[]` and reachable keys for `@1` are `[a, b]` with steps `[10, 3]`, respectively. Thus, we have the following two possible options: +* `(@, a)` - `robot0` stays where it's at because its the only option it can take, and `robot1` goes to key `a`, unlocking door `A` when it gets popped from queue. This will cost us **10 steps**. +* `(@, b)` - `robot0` stays where it's at, and `robot1` goes to key `b`, unlocking door `B` when it gets popped from queue. This will cost us **3 steps**. + +Now imagine the `queue` pops the robot positions `(@, b)`, implying `robot0` is at `@` and `robot1` is at `b`, and door `B` is unlocked, with a total cost of 3 steps. + +We again get the reachable keys for both robots, and now the reachable keys are: +* `robot0` = `[u, v]` with steps `[4, 40]` +* `robot1` = `[c]` with steps `[10]`. + +Now the possible routes we have are: +* ~~`(@, b)`~~ - This option implies both robots stay where they're at, which would lead to an infinite loop, so this is **not** an option. +* `(@, c)` - `robot0` stays, `robot1` advances. +* `(u, b)` - `robot0` advances, `robot1` stays. +* `(u, c)` - both robots advance. +* `(v, b)` - `robot0` advances, `robot1` stays. +* `(v, c)` - both robots advance. + +All of those options get put into the queue! E.g., the option `(u, c)` will get put into the queue with a total step of `3 + 4 + 10 = 17`, and `keysObtained = b,u,c`. + +We keep dequeueing and enqueueing the queue until we get all keys obtained. + +```typescript +/* +Input: [ + [a, b], + [c, d] + [e] +] +Output: [ + [a, c, e], + [a, d, e], + [b, c, e], + [b, d, e] +] +*/ +export const combinations = (keysReachableKeys: T[][]) => { + const solutions: T[][] = []; + + const helper = (atKeyIndex: number, curr: T[]) => { + if (curr.length === keysReachableKeys.length) { + solutions.push([...curr]); + return; + } + for (let i = atKeyIndex; i < keysReachableKeys.length; i++) + for (let j = 0; j < keysReachableKeys[i].length; j++) { + curr.push(keysReachableKeys[i][j]); + helper(i + 1, curr); + curr.pop(); + } + }; + helper(0, []); + return solutions; +}; + +export const solve = (keytoKeyMap: Map, entrances: Map) => { + type QueueState = {totalSteps: number; atKeys: string[]; keysObtained: string}; + const queue = new PriorityQueue(p => -1 * p.totalSteps); + const visited = new Map(); + const getReachableKeys = makeGetReachableKeys(keytoKeyMap); + + queue.enqueue({ + totalSteps: 0, + atKeys: Array.from(entrances.keys()), + keysObtained: '' + }); + while (!queue.isEmpty()) { + const { totalSteps, atKeys, keysObtained } = queue.dequeue()!; + if (keysObtained.length === keytoKeyMap.size - entrances.size) + return totalSteps; + + const keysObtainedSet = new GenericSet(k => k, [...keysObtained]); + const allRouteOptionsForAllRobots = + combinations( + // we're adding the current key to reachable keys to include the option of "staying put" + // obviously, staying put has 0 steps involved + atKeys.map(k => [{toKey: k, steps: 0}].concat(getReachableKeys(k, keysObtainedSet))) + ).slice(1); // remove the first one because it'll be identical to the current one + + for (const routeOptionForAllRobots of allRouteOptionsForAllRobots) { + // 0th robot moves to key robotsMoveToKey[0] + const robotsMoveToKey = routeOptionForAllRobots.map(r => r.toKey); + const newKeysObtained = Array.from(new GenericSet(k => k, + robotsMoveToKey + .filter(k => !entrances.has(k)) + .concat([...keysObtained])) + .values()).join(''); + const newCacheKey = getCacheKey(robotsMoveToKey.join(''), newKeysObtained); + const newTotalSteps = routeOptionForAllRobots.reduce((a, c) => a + c.steps, totalSteps); + if (!visited.has(newCacheKey) || visited.get(newCacheKey)! > newTotalSteps) { + queue.enqueue({ + totalSteps: newTotalSteps, + atKeys: robotsMoveToKey, + keysObtained: newKeysObtained + }); + visited.set(newCacheKey, newTotalSteps); + } + } + } + return Infinity; +}; +``` \ No newline at end of file diff --git a/src/2019/18b.ts b/src/2019/18b.ts index bf5dd7f..12470b0 100644 --- a/src/2019/18b.ts +++ b/src/2019/18b.ts @@ -68,18 +68,16 @@ const getAllValuesExcludingKey = (keyToExclude: string, allKeys: Map, entrances: Map) => { - let failureWeight = 10000; - type QueueState = {totalSteps: number; keysObtained: string; atKey: string; failedAttempts?: number}; - // the prioritizer function is called only when an empty is inserted, so we increment failureWeight every time - // a failedAttempt is added, to make sure its added at the bottom of the queue. - const queue = new PriorityQueue(p => p.failedAttempts != null ? -1 * (failureWeight++) : -p.totalSteps); +export const lowerBound = (keytoKeyMap: Map, entrances: Map) => { + type QueueState = {totalSteps: number; keysObtained: string; atKey: string}; + const queue = new PriorityQueue(p => -1 * p.totalSteps); const visited = new Map(); const getReachableKeys = makeGetReachableKeys(keytoKeyMap); - const keyToQuadrantMap = getKeyToEntranceMap(keytoKeyMap, entrances); // key to entrance it belongs to - const quadrantLastKeysObtained = new Map(Array.from(entrances.keys()).map(k => [ + const keyToQuadrantMap = getKeyToEntranceMap(keytoKeyMap, entrances); // The entrance a key belongs to + // all the keys that will be obtained in a quadrant. + const allKeysInQuadrant = new Map(Array.from(entrances.keys()).map(k => [ k, // key of the Map - keytoKeyMap.get(k)!.map(k1 => k1.toKey).join('') // all keys reachable by entrance, regardless of doors in way + keytoKeyMap.get(k)!.map(k1 => k1.toKey).join('') ])); const keysPerQuadrant = new Map(Array.from(entrances.keys()).map(k => [k, keytoKeyMap.get(k)!.length])); const minSteps = new Map(Array.from(entrances.keys()).map(k => [k, Infinity])); @@ -88,7 +86,7 @@ export const solve = (keytoKeyMap: Map, entrances: Map< queue.enqueue({totalSteps: 0, keysObtained: '', atKey: e}); while (!queue.isEmpty()) { - const { totalSteps, keysObtained, atKey, failedAttempts = 0 } = queue.dequeue()!; + const { totalSteps, keysObtained, atKey } = queue.dequeue()!; const quadrant = keyToQuadrantMap.get(atKey)!; if (keysObtained.length === keysPerQuadrant.get(quadrant)) { @@ -96,21 +94,10 @@ export const solve = (keytoKeyMap: Map, entrances: Map< minSteps.set(quadrant, Math.min(currMinSteps, totalSteps)); } - const keysObtainedOtherQuadrants = getAllValuesExcludingKey(quadrant, quadrantLastKeysObtained); + const keysObtainedOtherQuadrants = getAllValuesExcludingKey(quadrant, allKeysInQuadrant); const keysObtainedSet = new GenericSet(k => k, [...keysObtained, ...keysObtainedOtherQuadrants]); const reachableKeys = getReachableKeys(atKey, keysObtainedSet); - if (reachableKeys.length === 0 && failedAttempts < 10) { - const hasKeysToGetInFuture = keytoKeyMap.get(atKey)!.filter(k => !keysObtainedSet.has(k.toKey)).length > 0; - if (hasKeysToGetInFuture) { - console.info(`\tadding ${atKey} (${entrances.get(atKey)?.col},${entrances.get(atKey)?.row}) to failed`); - queue.enqueue({ totalSteps, keysObtained, atKey, failedAttempts: failedAttempts + 1 }); - } - } - if (reachableKeys.length === 0 && failedAttempts >= 10) { - console.info(`${atKey} has failed ${failedAttempts} times. It is now discarded`); - } - for (const key of reachableKeys) { const newKeysObtained = keysObtained + key.toKey; const newCacheKey = getCacheKey(key.toKey, newKeysObtained); @@ -125,7 +112,6 @@ export const solve = (keytoKeyMap: Map, entrances: Map< } } } - console.log(minSteps); return Array.from(minSteps.values()).reduce((a, c) => a + c, 0); }; @@ -161,26 +147,23 @@ export const combinations = (keysReachableKeys: T[][]) => { return solutions; }; -export const solve2 = (keytoKeyMap: Map, entrances: Map) => { - type QueueState = {totalSteps: number; atKeys: string[]; allKeysObtained: string}; +export const solve = (keytoKeyMap: Map, entrances: Map) => { + type QueueState = {totalSteps: number; atKeys: string[]; keysObtained: string}; const queue = new PriorityQueue(p => -1 * p.totalSteps); const visited = new Map(); const getReachableKeys = makeGetReachableKeys(keytoKeyMap); - let minSteps = Infinity; queue.enqueue({ totalSteps: 0, atKeys: Array.from(entrances.keys()), - allKeysObtained: '' + keysObtained: '' }); while (!queue.isEmpty()) { - const { totalSteps, atKeys, allKeysObtained } = queue.dequeue()!; - console.info('atKey:', atKeys, 'keysObtained', allKeysObtained, 'totalSteps:', totalSteps); - if (allKeysObtained.length === keytoKeyMap.size - entrances.size) { - minSteps = Math.min(totalSteps, minSteps); - continue; - } - const keysObtainedSet = new GenericSet(k => k, [...allKeysObtained]); + const { totalSteps, atKeys, keysObtained } = queue.dequeue()!; + if (keysObtained.length === keytoKeyMap.size - entrances.size) + return totalSteps; + + const keysObtainedSet = new GenericSet(k => k, [...keysObtained]); const allRouteOptionsForAllRobots = combinations( // we're adding the current key to reachable keys to include the option of "staying put" @@ -194,7 +177,7 @@ export const solve2 = (keytoKeyMap: Map, entrances: Map const newKeysObtained = Array.from(new GenericSet(k => k, robotsMoveToKey .filter(k => !entrances.has(k)) - .concat([...allKeysObtained])) + .concat([...keysObtained])) .values()).join(''); const newCacheKey = getCacheKey(robotsMoveToKey.join(''), newKeysObtained); const newTotalSteps = routeOptionForAllRobots.reduce((a, c) => a + c.steps, totalSteps); @@ -202,13 +185,13 @@ export const solve2 = (keytoKeyMap: Map, entrances: Map queue.enqueue({ totalSteps: newTotalSteps, atKeys: robotsMoveToKey, - allKeysObtained: newKeysObtained + keysObtained: newKeysObtained }); visited.set(newCacheKey, newTotalSteps); } } } - return minSteps; + return Infinity; }; export const run = () => { @@ -219,11 +202,7 @@ export const run = () => { const d = getNearestKeysMap(s); // console.log(s.grid.map(r => r.join(' ')).join('\n')); console.log(timer.start(`18b - ${s.name}, ${s.keys.size} keys, ${s.doors.size} doors`)); - Array.from(d.keys()).forEach(k => - console.info(`key=${k}\t`, d.get(k)?.map(a => `${a.toKey}: ${a.steps} (${Array.from(a.keysInWay.values()).concat(Array.from(a.doorsInWay.values()))})`).join(' ')) - ); - console.log('entrances', s.entrances); - console.log(chalk.redBright('answer ='), solve2(d, s.entrances)); + console.log(chalk.redBright('answer ='), lowerBound(d, s.entrances)); console.log(timer.stop()); } resetConsoleInfo(); diff --git a/src/2019/18b.txt b/src/2019/18b.txt index 4fa3596..376d254 100644 --- a/src/2019/18b.txt +++ b/src/2019/18b.txt @@ -47,7 +47,90 @@ #b...#dC..# ########### -[input] +[input-greg] +################################################################################# +#...#.......#.....#.......#...#.........#.......#.......#..q............#...#...# +#.#.#.#.###.###.#.#.#####.#.#.#.#######.#####.#.###.###Y###.#######.#####I#.#.#.# +#.#.#.#...#.....#.#.....#.#.#...#...#...#.....#.....#.#.....#.......#...#.#...#.# +#.#.#####.#######.#####.#.#.#####.#.#.#.#.#####.#####.###.#######.###N#.#.#####.# +#.#....f#.....#.#.#.#...#.#...#...#...#.#...#...#...#...#.#.....#.#...#.#...#...# +#.#####.###.#.#.#.#.#.###.#####.###########.#####.#.#.###.#.###.###.###.###.#.### +#.....#...#.#.#.#...#...#.....#.#.......#...#...#.#.#.....#.#.#.......#.....#.F.# +#####.###.###.#.###.###.#####.#.#.#####.#.###.#.#.#.#######.#.#############.###.# +#...#.#.#.#...#.....#.....#...#.#...#...#...#.#.#.#.......#.#.....#.......#.#a..# +#.#.#.#.#.#.###.#####.#####.###.#.###.#.#.#.#.#.#.#######.#.#.#.###.#####.###.### +#.#...#.#...#.#...#...#.....#...#.#...#.#.#...#.#.....#...#.#.#...#.....#.....#.# +#.#####.#####.#.###.###.#######.#.#.###.#######.#.#####.#.#.#####.#####.#######.# +#...#.........#.#...#.#.......#...#.#.#.#.....#...#...#.#.#...........#.#.......# +###.#########.#.#.###.#######.#.###.#.#.#.###.#####.#.#.###########.###.#.#####.# +#...#.....#...#.#.......#...#.#.#...#.#.#.#.....#...#...#...#.......#...#...#...# +#.#.#.###.#.###.#######.#.###.#.#.###.#.#.#####.#.#####.#.#.#.#######.#####.##### +#.#.#...#.#.....#.#.......#...#.#.#.#...#...#.....#.....#.#.......#...#...#.....# +#.#####.#.###.###.#.#######.#####.#.#.###.#.#########.###.#########.###.#.#####.# +#.......#...#.....#.#.......#.....#...#.#.#.....#...#.#.#...#.....#.#...#.......# +#.#######.#.#######.#.#########.###.###.#.#####.#.#.#.#.###.#.###.#.#.#.#######.# +#...#...#.#.......#.#.#...L...#.#.#...#.#...#.#...#.#.....#.#.#.#.#.#.#...#...#.# +#.###.#.#####.###.#.#######.#.#.#.###.#.###.#.#####.#####.#.###.#.#.#####.#.#.### +#.#...#.....#...#.#.......#.#...#.......#.#.......#...#...#.#...#...#.....#.#...# +#.#.#######.###.#.#######.#.###########.#.#######.###.#.###.#.#.#####.#.###.###.# +#.#.#.#.....#...#...#.....#.........#...#.#...#.....#.#...#.#.#.....#.#.#...#.#.# +###.#.#.#########.###.###########.#.###.#.#.#.#.#####.###.#.#####.#.#.###.###.#.# +#...#...J.#.......#...#.........#.#...#.#...#...#.....#.#.#.#.....#...#...#s..#.# +#.#######.#.#####S#.#######.###.#.###.###.#######.#####.#.#.#.#####.###.#####.#.# +#.#.....#...#...#.#.#.....#.#.#.#...#...#...#.#...#.......#.#...#...#...#.....#.# +#.#T###.#####.#.###.###.#.#.#.#.#.#####.###.#.#.#########.#.###.#####.###.#####.# +#.#.#...#.....#.#...#...#...#...#.#...#.#.#.#.#.#.......#.#...#.......#.....#..w# +#.#.#.###.#####.#.###.#######.#####.#.#.#.#.#.#.#.#####.#####.#.###########.#.#.# +#...#.#.B.#...#.#.#.#...#.#...#.....#.#.#.#.#.#.#.#r..#.....#.#.#...#.......#.#.# +#.###.#.###.#.#.#.#.#.#.#.#.###.#####.#.#.#.#.#.#.#.#######.#.#.#.#.#.###.###.#.# +#...#.#...#.#.#...#...#.#.#.#.......#...#.#...#...#.......#.#.#.#.#.#.#...#...#.# +###.#.###.###.#########.#.#.#.#####.#####.###.#####.#####.#.#.#.#.#.#.#####.###.# +#...#...#...#.....W...#.#.#.#c#...#.....#...#.....#.#.....#...#...#...#...#...#.# +#.###.#####.###.#####.#.#.#.###.#.#####.#.#.#####.###.###.#############.#.###.#.# +#o..#...........#.......#.......#......@#@#...........#.................#.....#.# +################################################################################# +#...#.....#...#.........#.............#@#@........#.....#............j....#...#.# +#.#.#P###.###.#.###.###.#.###.#######.#.#.#####.###.#.#.###.#.###.#######.#.#.#.# +#.#...#.#...#.#.#.#...#.#...#.#.....#.#.#.#...#.....#.#...#.#.#...#..t..#...#...# +#G#####.###.#.#.#.###.#.#.###.#.#.#.#.#.#.###.#######.###C###.#.###.###.###.##### +#.#...#...#.#.#.....#.#.#.#...#.#.#.#.#.#...#.....#...#.#...#.#.#...#...#...#...# +#.#.###.#.#.#.#####.#.#.###.#####.###.#.###.#.#####.###.###.#.###.###.#######.#.# +#.#.....#.#.#...#...#.#...#.....#...#.#.#.....#...#.#.....#.#.......#.#.......#.# +#.#######.#.#.###.###.###.#####.###.#.#.#.#####.#.#.#.#####.#.#####.#.#.#######.# +#.......#...#...#.#.#.#.#.....#...#...#.#...#...#.#.#.....#.#.#...#.#...#.......# +#.#####.#.#####.#.#.#.#.#####.###.#.###.#.###.###.#.#####.#.###.#.#######.#####.# +#.#...#.#.....#...#.#.#.....#.....#.#...#.#...#.#.#.....#...#...#.........#.....# +#.#.#.#.#####.#.###.#.#####.#######.#.#.#.#.###.#.#.###.#.###.#############.##### +#...#.#.#.....#.....#.....#...#.....#.#.#.#.#.....#.#.#.#.#...#...#.O.#...#b..#.# +#####.#.###.###.#########.###.#.#####.#.#.#.#.#####.#.#.#.#.###.#.###.#.#.###.#.# +#...#.#...#...#.#.........#...#...#...#.#.#.#...#.....#.#...#...#..d#.#.#...#.#.# +#.###.###.###.#.#.#########.#.###.#.###.###.###.#######.###.#.#####.#.#.#.###.#.# +#.#.X.#.#.#...#.#...#.#.....#...#.#...#.#...#.....#.....#...#.#...#.#...#...#...# +#.#.###.#.#.###.###.#.#.###.###.#.#.#.#.#.#.#####.#.#####.###.###.#.#######.###.# +#.......#.#.#.#...#.#...#.#.#...#.#.#.#.#.#.#...#.#.#...#...#...#k#.......#...#.# +#.#####.#.#.#.#.###.###.#.#.#####.#.#.###.###.#.#.#.#.#.#######.#.#######.#.#.#.# +#.#...#.#.#.#...#...#.#...#.#...#.#.#...#.#...#.#...#.#.....U.#.#.....#...#.#...# +#.#.#.###.#.#.###.#.#.###.#.#.#.#.#####.#.#.###.###.#.#########.#.#####.######### +#.#.#.....#.#.#...#.#.#...#...#...#...#.#.....#...#.#...#......h#.......#.......# +#.#.#######.###.###.#.#######.#####.#.#.#########.#.###.###.#####.#######.#####.# +#.#.#...#.....#.#...#u......#...#...#.R.#.......#.#.#...#...#...#........x#...#.# +#.#.#.#.#####.#.#.#########.###.#.#.###.#.#####.#.###.###.###.#Z###########.#.#.# +#.#...#...#.#...#...#...#.E.#.#.#.#.#...#...#...#...#.#.......#.#.....#....g#.#.# +#K#######.#.#####.###.#.#.###.#.#.#.#.#####.#.#####.#.#####.###.#.###.###.#####.# +#.#z....#.#.......#..v#.#...#.#.#.#.#...#...#.....#.#.#..e#...#.#...#...#.......# +#.#.###.#.#########.###.###.#.#.#.#.###.#.#######.#.#.#.#.###.#.#.#####.#.####### +#.#.#...#.......#...#.#.....#.#.#.#.#...#...#.......#...#...#.#...#...M.#.......# +#.###H#.#######.#.###.#######.#.###.#.###.#.###############V#######.###########.# +#...#.#.#.....#.#.....#.........#...#l#.#.#...#.........Q.#.........#.......#...# +###.#.###.#.###.#####.#.#####.###.###.#.#####.#.#######.#############.#####.#.### +#.#.#.....#.#.....#.#.#.#...#...#.#.....#.....#.#...#...#.......#.........#.#..p# +#.#.#######.#.###.#.#.###.#.###.#.#####.#.###.#.###.#.###.###.#.#.#######.#.###.# +#.#...D.....#.#.....#m....#.#...#...#.#.#y#...#.#...#n..#...#i#.#.#.......#.#...# +#.#########.#.#############.#.#####.#.#.#.#####.#.#.###.###.#.###.#.#########.### +#...........#...............#.......#...#.........#...#.....#.....#.......A.....# +################################################################################# + +[input-aziz] ################################################################################# #.......#...#...#...........#.....#...#.#.....#m......#.......#.....#........u..# #####.#.#.#.#.#.###.#######.###.#.#.#.#.#.###.###.#####.#.###.###.#.###########.#