明けましておめでとうございます。

昨年の年末、とある理由で2023 のAdvent of Code をちょっとずつ解いていたので、自分の解答や、ちょっとしたメモを残していこうと思います。

なお、全部pythonで書いています。

全部解き終わったタイミングで、まとめてgithubにあげようかなと考えていますが、いつになるかはわかりません。

それでは、早速Day 1 の解答を書いていきます。

Part a

  • 問題の概要

    数字と文字が混ざった文字列が与えられる。 この文字列のそれぞれの行に含まれる最初の数字と最後の数字を取り出し、それらを結合した数値を求める。

    例:

    1abc2        → 12
    pqr3stu8vwx  → 38
    a1b2c3d4e5f  → 15
    treb7uchet   → 77
    

    課題は、与えられた入力ファイルの全ての行について、この方法で校正値を求めて合計することである。

    この例では、4つの行の校正値(12 + 38 + 15 + 77)を合計すると142になる。

  • My solution

    from fastcore.utils import L, first, last, groupby
    from aocd import get_data, submit
    import fastcore.all as fc, re, math, itertools, functools, numpy as np, typing ,dataclasses, matplotlib.pyplot as plt, collections, regex
    from regex import findall
    
    def get_calibration_value(x:str):
        digits = re.findall(r"\d", x)
        return int(digits[0]+digits[-1])
    
    def solve1a(inp:str):
        lines = L(inp.splitlines())
        return lines.map(get_calibration_value).sum()
    

Part b

  • 問題の概要

    パート2では、数字が文字で書かれているケースも考慮する。

    例:

    two1nine → 29
    eightwothree → 83
    abcone2threexyz → 13
    

    つまり、数字の文字(“1”,“2"など)だけでなく、英単語(“one”,“two"など)も数字として扱い、各行の最初と最後の数字を見つける必要がある。

  • My solution

    d = {str(i):i for i in range(10)}
    number_words = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
    d |= {o:i+1 for i,o in enumerate(number_words)}
    
    pat = "|".join(d.keys())
    
    def get_calib_with_letters(x):
        m = findall(pat, x, overlapped=True)
        return d.get(m[0])*10 + d.get(m[-1])
    
    def solve1b(inp):
        lines = L(inp.splitlines())
        return lines.map(get_calib_with_letters).sum()
    

解いた感想・メモ

標準ライブラリ re の挙動を理解できていなかったので、part bで少し手こずった。

具体的には、

x = "7pqrstsixteen"
print(re.findall(pat, x))
['7', 'six']

のような場合、 findall で十分上手くいくが、

x = "oneight"
print(re.findall(pat, x))
['one']

このとき、 ['one', 'eight'] がmatchしていて欲しいところ、 ['one'] しか取得できていない!!

これは、“one"の"e"が"eight"の"e"と重なっているためである。

これを回避するためには、標準ライブラリ re をwrapしているthird-partyライブラリregex を使うと良い。

このライブラリの findall は、 overlapped という引数があり、これを True にすることで、重なりを許容してマッチングを行うことができる。

具体的には以下:

x = "oneight"
print(regex.findall(pat, x, overlapped=True))
['one', 'eight']

他にも regex には色々便利な機能がありそうなので、使いこなせるようになりたい。

以上、Advent of Code 2023 Day 1の解説でした。