{"id":23171708,"url":"https://github.com/smarthypercube/aoc2023-haskell","last_synced_at":"2025-04-04T23:42:54.811Z","repository":{"id":210655662,"uuid":"726432762","full_name":"SmartHypercube/aoc2023-haskell","owner":"SmartHypercube","description":"Advent of Code 2023 Haskell 解题代码","archived":false,"fork":false,"pushed_at":"2023-12-22T04:45:33.000Z","size":155,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-10T08:16:26.017Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SmartHypercube.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-12-02T11:37:57.000Z","updated_at":"2023-12-02T14:03:18.000Z","dependencies_parsed_at":"2023-12-22T05:29:19.324Z","dependency_job_id":null,"html_url":"https://github.com/SmartHypercube/aoc2023-haskell","commit_stats":null,"previous_names":["smarthypercube/aoc2023-haskell"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartHypercube%2Faoc2023-haskell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartHypercube%2Faoc2023-haskell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartHypercube%2Faoc2023-haskell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartHypercube%2Faoc2023-haskell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SmartHypercube","download_url":"https://codeload.github.com/SmartHypercube/aoc2023-haskell/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247266479,"owners_count":20910832,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-18T04:19:18.431Z","updated_at":"2025-04-04T23:42:54.790Z","avatar_url":"https://github.com/SmartHypercube.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Advent of Code 2023 Haskell 解题代码\n\n**内含解题代码**，我只会在每道题的排行榜满了后再上传代码。\n\n## 运行方式\n\n在安装了 [Haskell Stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/) 的环境下，使用 `stack run day1` 运行 day1 的解题代码，以此类推。\n\n## 代码结构\n\n`data` 目录下是输入数据，`src` 目录下是解题代码。\n\n## 目录\n\n### Day1\n```\ntwo1nine\neightwothree\nabcone2threexyz\nxtwone3four\n4nineeightseven2\nzoneight234\n7pqrstsixteen\n```\n[输入数据](data/day1.txt) [解题代码](src/day1.hs)\n\n这道题的需求太罕见了，导致不得不使用我一般极少会用到的 `breakOn` 函数来做子串搜索。绝大多数类似需求在 Haskell 中会通过简单 `split` 一下解决，或者写正经的 parser。这道题巧妙地要求我反向搜索，考虑到有重叠的可能性（例如 `nineight` 正向搜索会先看到 `nine`，反向搜索会先看到 `eight`），我放弃了写 parser 的想法。\n\n### Day2\n```\nGame 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green\nGame 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue\nGame 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red\nGame 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red\nGame 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green\n```\n[输入数据](data/day2.txt) [解题代码](src/day2.hs)\n\n写 parser 啦！\n\n### Day3\n```\n467..114..\n...*......\n..35..633.\n......#...\n617*......\n.....+.58.\n..592.....\n......755.\n...$.*....\n.664.598..\n```\n[输入数据](data/day3.txt) [解题代码](src/day3.hs)\n\n写 List monad 啦！\n\n### Day4\n```\nCard 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53\nCard 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19\nCard 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1\nCard 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83\nCard 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36\nCard 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11\n```\n[输入数据](data/day4.txt) [解题代码](src/day4.hs)\n\n第二问最好是要有一个可以随机读写的内存，才比较方便实现。不然的替代方案就是做个状态机，用 `foldl`/`foldl'` 不断更新一个表示内存的状态。我用了 `STArray`、`HashMap`、`Integer -\u003e Integer` 三种表示内存的方案各写了一遍，逻辑是完全相同的，可以相互对照。\n\n### Day5\n```\nseeds: 79 14 55 13\n\nseed-to-soil map:\n50 98 2\n52 50 48\n\nsoil-to-fertilizer map:\n0 15 37\n37 52 2\n39 0 15\n\nfertilizer-to-water map:\n49 53 8\n0 11 42\n42 0 7\n57 7 4\n\nwater-to-light map:\n88 18 7\n18 25 70\n\nlight-to-temperature map:\n45 77 23\n81 45 19\n68 64 13\n\ntemperature-to-humidity map:\n0 69 1\n1 0 69\n\nhumidity-to-location map:\n60 56 37\n56 93 4\n```\n[输入数据](data/day5.txt) [解题代码 1](src/day5.hs) [解题代码 2](src/day5b.hs)\n\n要是有一些现成的和区间打交道的类型和函数就好了。\n\n### Day6\n```\nTime:      7  15   30\nDistance:  9  40  200\n```\n[输入数据](data/day6.txt) [解题代码](src/day6.hs)\n\n过于简单。\n\n### Day7\n```\n32T3K 765\nT55J5 684\nKK677 28\nKTJJT 220\nQQQJA 483\n```\n[输入数据](data/day7.txt) [解题代码 1](src/day7.hs) [解题代码 2](src/day7b.hs)\n\n用 Haskell 写这道题挺爽的，我知道第二问的 `handType` 可以写得更好一点，不过当时赶了一下时间。\n\n### Day8\n```\nLR\n\n11A = (11B, XXX)\n11B = (XXX, 11Z)\n11Z = (11B, XXX)\n22A = (22B, XXX)\n22B = (22C, 22C)\n22C = (22Z, 22Z)\n22Z = (22B, 22B)\nXXX = (XXX, XXX)\n```\n[输入数据](data/day8.txt) [解题代码](src/day8.hs)\n\n赶时间，写得比较 dirty。第二问先是写成了把 `startPositions` 中的每一项分别 `map` 成后续序列，然后整个 `List.transpose` 一下，看第几项正好全以 Z 结尾，结果发现跑不完，改成了 `lcm` 的方案。\n\n### Day9\n```\n0 3 6 9 12 15\n1 3 6 10 15 21\n10 13 16 21 30 45\n```\n[输入数据](data/day9.txt) [解题代码](src/day9.hs)\n\n过于简单。\n\n### Day10\n```\n...........\n.S-------7.\n.|F-----7|.\n.||.....||.\n.||.....||.\n.|L-7.F-J|.\n.|..|.|..|.\n.L--J.L--J.\n...........\n```\n[输入数据](data/day10.txt) [解题代码](src/day10.hs)\n\n这道题正好可以用图形学中常用的求面积的算法来做，走一圈就能把周长和面积都求出来，两问需要的答案都能只用周长和面积算出来。不过用递归写循环确实体验不是很好，以及我常常希望如果 `Direction`、`opposite`、`step` 等类型和函数不用自己写就好了。求面积的这个算法是每次向右走时，加一次纵坐标，每次向左走时减一次纵坐标，最终的结果取绝对值就是面积，符号表示旋转的方向。注意到这个算法的另一种表述是，找出所有横着走的部分，每一段如果是向右走的，就加上这条横线的长度乘以纵坐标，如果是向左走的，就减去。\n\n### Day11\n```\n...#......\n.......#..\n#.........\n..........\n......#...\n.#........\n.........#\n..........\n.......#..\n#...#.....\n```\n[输入数据](data/day11.txt) [解题代码](src/day11.hs)\n\n把水平和垂直方向的距离分开算，假设有 6 个星系，中间就有 5 段距离，假设分别是 a、b、c、d、e，那么总距离就是 `1*5*a + 2*4*b + 3*3*c + 4*2*d + 5*1*e`。考虑到膨胀的影响，算每段距离的时候要变换一下，例如对于第一问，变换规则是 `0-\u003e0, 1-\u003e1, 2-\u003e3, 3-\u003e5, 4-\u003e7, 5-\u003e9, ...`。\n\n### Day12\n```\n???.### 1,1,3\n.??..??...?##. 1,1,3\n?#?#?#?#?#?#?#? 1,3,1,6\n????.#...#... 4,1,1\n????.######..#####. 1,6,5\n?###???????? 3,2,1\n```\n[输入数据](data/day12.txt) [解题代码 1](src/day12.hs) [解题代码 2](src/day12b.hs)\n\n不用 memo 写动态规划实在不是很爽。要显式定义状态和状态转移过程也不是很爽，我更喜欢把涉及状态机的需求写成 parser 或 async 代码，当 parser 解析了一部分输入但还没结束时，或者 async 代码暂停在一个 yield 处时，就自动保存了状态。然而这样定义出来的状态和状态转移过程是次优的，分叉后等价的状态无法再合并，也就是说只能实现剪枝带来的性能提升。另外，这道题更好的写法是，不要把 runs 和 springs 存进 cache，只存下标进去，这样省空间，hash 也更快，但意味着就不能用列表了，应该用 Vector，而且递归代码也会变得更丑。\n\n### Day13\n```\n#.##..##.\n..#.##.#.\n##......#\n##......#\n..#.##.#.\n..##..##.\n#.#.##.#.\n\n#...##..#\n#....#..#\n..##..###\n#####.##.\n#####.##.\n..##..###\n#....#..#\n```\n[输入数据](data/day13.txt) [解题代码](src/day13.hs)\n\n有点简单。我用了更高效一点的写法，就是对每个可能有镜子的位置，数是否正好 1 处不相符。但其实分别把每个位置翻转一下再找镜子位置，运行时间也会很短的。\n\n### Day14\n```\nO....#....\nO.OO#....#\n.....##...\nOO.#O....O\n.O.....O#.\nO.#..O.#.#\n..O..#O..O\n.......O..\n#....###..\n#OO..#....\n```\n[输入数据](data/day14.txt) [解题代码](src/day14.hs)\n\n不停地改变方向倾斜，没有随机访问元素的能力的话还真是不太好办，把列表转来转去感觉很容易出错，而且也比较低效，不过反正倾斜运算的性能不是这里的瓶颈就是了。回头想来，如果一开始知道要做任意方向的倾斜，而且在意性能的话，应该搞一些互为索引的数据结构的，至少要有 Map 用于快速查出向某个方向移动时下一个会撞上的东西在哪。\n\n感觉第二问在剧情设定的意义下比较奇怪，担心北侧负载的话当然应该关心的是某次向北倾斜后的负载，而不是向东倾斜后的。因为这里理解错了，用 `load` 而非 `loadWithoutTilt` 一直不对（后者是想明白后新写的函数）。\n\n### Day15\n```\nrn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7\n```\n[输入数据](data/day15.txt) [解题代码](src/day15.hs)\n\n题目没必要这么拼命地迎合 Python 的特性吧！\n\n### Day16\n```\n.|...\\....\n|.-.\\.....\n.....|-...\n........|.\n..........\n.........\\\n..../.\\\\..\n.-.-/..|..\n.|....-|.\\\n..//.|....\n```\n[输入数据](data/day16.txt) [解题代码](src/day16.hs)\n\n除了不得不用 State 以及定义方向和坐标相关基础设施有点麻烦以外，感觉没啥值得说的。\n\n### Day17\n```\n2413432311323\n3215453535623\n3255245654254\n3446585845452\n4546657867536\n1438598798454\n4457876987766\n3637877979653\n4654967986887\n4564679986453\n1224686865563\n2546548887735\n4322674655533\n```\n[输入数据](data/day17.txt) [解题代码](src/day17.hs)\n\n这道题最开始没意识到应该是 Dijkstra 算法，做了一些错误的尝试。在按 Dijkstra 算法实现后，仍然有若干细节比较棘手，并且有的地方不同的选择可以产生较大的性能差异，导致搞了很久。最终版本的代码在我的电脑上需要 39 秒。\n\nP.S. 看了一下别人的代码，发现一个[巨大 bug](https://github.com/SmartHypercube/aoc2023-haskell/commit/61f94c7e8e1e43a5f3f497e77a93258e8cf27530)，笑死。现在在我的电脑上不到 1 秒了。\n\n### Day18\n```\nR 6 (#70c710)\nD 5 (#0dc571)\nL 2 (#5713f0)\nD 2 (#d2c081)\nR 2 (#59c680)\nD 2 (#411b91)\nL 5 (#8ceee2)\nU 2 (#caa173)\nL 1 (#1b58a2)\nU 2 (#caa171)\nR 2 (#7807d2)\nU 3 (#a77fa3)\nL 2 (#015232)\nU 2 (#7a21e3)\n```\n[输入数据](data/day18.txt) [解题代码](src/day18.hs)\n\n额，这不就是 day10，一点难度都没加。\n\n### Day19\n```\npx{a\u003c2006:qkq,m\u003e2090:A,rfg}\npv{a\u003e1716:R,A}\nlnx{m\u003e1548:A,A}\nrfg{s\u003c537:gd,x\u003e2440:R,A}\nqs{s\u003e3448:A,lnx}\nqkq{x\u003c1416:A,crn}\ncrn{x\u003e2662:A,R}\nin{s\u003c1351:px,qqz}\nqqz{s\u003e2770:qs,m\u003c1801:hdj,R}\ngd{a\u003e3333:R,R}\nhdj{m\u003e838:A,pv}\n\n{x=787,m=2655,a=1222,s=2876}\n{x=1679,m=44,a=2067,s=496}\n{x=2036,m=264,a=79,s=2244}\n{x=2461,m=1339,a=466,s=291}\n{x=2127,m=1623,a=2188,s=1013}\n```\n[输入数据](data/day19.txt) [解题代码 1](src/day19.hs) [解题代码 2](src/day19b.hs)\n\nList monad 加尾递归。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmarthypercube%2Faoc2023-haskell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmarthypercube%2Faoc2023-haskell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmarthypercube%2Faoc2023-haskell/lists"}