经过了漫长的时间(准确地说是五个小时),Day 20 Part 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中文网其他相关文章!