在經歷了漫長的時光(準確地說是五個小時)之後,第 20 天第 2 部分終於決定合作。我仍然因為等待而感到有點茫然,但職責召喚了!今天我們要討論《代碼降臨》的第 7 天,從上週第 6 天結束的地方開始。我們今天的任務是修復一座橋,以便我們可以穿過它並繼續尋找首席歷史學家。
Microsoft Copilot 產生的可愛插圖
今天的挑戰向我們提出了一種不同類型的問題:我們得到了以特定格式排列的數字列表(幸運的是,今天不是 2D 謎題......)。每條線都被設計成一個方程,但沒有運算子。我們的任務是測試各種運算子以確定所得方程式是否成立。
我們使用兩個解析函數來處理輸入。解析函數先用冒號 (:) 字元分割每一行:
def parse(input: str) -> tuple[tuple[int, tuple[int, ...]], ...]: return tuple( parse_line(*line.strip().split(":")) for line in input.strip().splitlines() )
parse_line 將預期字串和操作數字串轉換為整數,傳回一個包含預期整數和整數運算元組的元組
def parse_line(expected: str, operands: str) -> tuple[int, tuple[int, ...]]: return int(expected), tuple(int(item) for item in operands.strip().split(" "))
我更喜歡函數式程式設計風格,儘管 Python 具有命令式性質,但像 toolz/cytoolz 這樣的函式庫非常有用。今天,我們使用 toolz.functoolz 中的 thread_first。 thread_first 的操作方式如下:它採用一個初始值,然後應用一系列函數參數對,並透過每個步驟將結果執行緒化。
>>> from operator import add, mul >>> assert thread_first(1, (add, 2), (mul, 2)) == mul(add(1, 2), 2)
在此範例中,thread_first 從 1 開始,然後對 2 應用 add(結果為 3),最後對 2 套用 mul(結果為 6)。這相當於 mul(add(1, 2), 2).
我們現在定義計算函數來應用操作。它需要一個函數(funcs)元組和一個操作數(operands)元組作為輸入:
def calculate( funcs: tuple[Callable[[int, int], int], ...], operands: tuple[int, ...] ) -> int: assert len(operands) - len(funcs) == 1 return thread_first(operands[0], *(zip(funcs, operands[1:])))
斷言確保比函數多一個運算元。 operands[1:] 提供函數的運算元。 zip 和 * 為 thread_first 建立函數運算元對,執行鍊式計算。
範例:
>>> from operator import add, mul >>> calculate((add, mul), (2,3,4)) 20
現在我們可以使用 check_can_calibrate 函數來驗證輸入的每一行。此函數將預期結果、運算元和可能函數的元組作為輸入,如果任何函數組合產生預期結果,則傳回 True,否則傳回 False:
def check_can_calibrate( expected: int, operands: tuple[int, ...], funcs: tuple[Callable[[int, int], int], ...], ) -> bool: return next( filter( None, ( calculate(funcs, operands) == expected for funcs in product(funcs, repeat=len(operands) - 1) ), ), False, )
itertools.product 產生所有函數組合。生成器表達式檢查是否有任何組合與預期結果相符。 filter(None, ...) 和 next(..., False) 有效地找到第一個 True 結果,如果沒有找到則傳回 False。
對於第 1 部分,我們只給了乘法和加法運算子。這個難題要求期望值的總和,其中可以使用這些運算符形成有效的方程式。我們實作評估函數來計算這個總和:
def parse(input: str) -> tuple[tuple[int, tuple[int, ...]], ...]: return tuple( parse_line(*line.strip().split(":")) for line in input.strip().splitlines() )
它迭代解析的輸入並對 check_can_calibrate 傳回 True 的預期值求和。
最後,我們根據目前已經建造的內容組裝第 1 部分
def parse_line(expected: str, operands: str) -> tuple[int, tuple[int, ...]]: return int(expected), tuple(int(item) for item in operands.strip().split(" "))
函數使用 parse 解析輸入,然後使用解析後的資料和函數元組(operator.mul、operator.add)呼叫評估,分別表示乘法和加法。
在第 2 部分中,我們遇到一個將兩個數字連接在一起的串聯運算符。在 Python 中,這相當於使用 f 字串:
>>> from operator import add, mul >>> assert thread_first(1, (add, 2), (mul, 2)) == mul(add(1, 2), 2)
這透過將第二個數字的數字附加到第一個數字的末尾來有效地建立一個新數字。
或者,我們可以使用數學公式來執行串聯。正整數 x 的位數可以使用以下公式計算:
這是有效的,因為 log₁₀(x) 給出了必須提高 10 才能得到 x 的冪。下取整函數將其向下舍入為最接近的整數,並加 1 給出位數。我們以123為例:
將其實作為 int_concat 函數:
def calculate( funcs: tuple[Callable[[int, int], int], ...], operands: tuple[int, ...] ) -> int: assert len(operands) - len(funcs) == 1 return thread_first(operands[0], *(zip(funcs, operands[1:])))
執行整數串聯在數學上避免了字串轉換的開銷。字串串聯涉及記憶體分配和操作,其效率低於直接整數運算,特別是對於大數字或多次串聯。因此,這種數學方法通常更快、更節省記憶體。
最後,我們實作第 2 部分。與第 1 部分相比,唯一的差異是新增了 int_concat 運算子:
>>> from operator import add, mul >>> calculate((add, mul), (2,3,4)) 20
噠噠!我們已經破解了第 7 天。這是一個相對簡單的挑戰,尤其是與後來的幾天相比(第 20 天的優化仍然讓我頭痛?)。雖然它可能不是表現最好的,但我優先考慮可讀性。
這就是今天的全部內容。節日快樂,新年快樂? !祈禱明年有更好的工作情況(仍然#OpenToWork,請聯繫我尋求合作!),下週我將再次寫信。
以上是如何修復橋樑,代碼降臨 ay 7的詳細內容。更多資訊請關注PHP中文網其他相關文章!