已解决:有人在 PowerShell 中做 Advent of Code 吗?
Source: Dev.to
执行摘要
TL;DR: 在 PowerShell 中解决 Advent of Code(AoC)面临诸如冗长的输入解析和复杂算法的性能瓶颈等挑战。
解决方案包括:
- 利用符合惯用法的 PowerShell 管道和 .NET 集成。
- 通过 C# 互操作提升关键部分的速度。
- 编排外部的高性能优化工具。
为什么使用惯用的 PowerShell?
- 面向对象的管道 – 链接 cmdlet,传递对象,保持代码可读性。
- 内置 cmdlet – 强大的字符串操作、正则表达式和文件处理。
- 直接 .NET 调用 – 当 cmdlet 不足时,访问任何 .NET 类型。
对于许多 AoC(Advent of Code)谜题,这种方法是 自然、可读、且 足够 的。
当 PowerShell 碰到性能瓶颈
- 大型数据集、深度递归或密集的模拟可能导致 PowerShell 脚本运行时间从秒级变为分钟级。
- 高级数据结构(优先队列、自定义图、不可变集合)在纯 PowerShell 中实现起来效率低下且繁琐。
- 缺少算法原语——PowerShell 没有 Python 或 C# 那样丰富的标准库辅助工具。
解决方案: 使用 C# 互操作(Add-Type)编写热点代码,然后在 PowerShell 中调用编译后的类。
编排外部工具
PowerShell 作为“胶水”,在以下方面表现出色:
- 准备输入。
- 执行外部高度优化的二进制文件(例如
grep、自定义 C/C++ 工具)。 - 将输出处理回 PowerShell 对象。
实用 PowerShell 模式
1. 从分隔输入文件中求和
input.txt
10,20,30
15,25,35
# 读取每一行,按逗号分割,转换为整数并求和
$totalSum = Get-Content -Path 'input.txt' |
ForEach-Object { $_ -split ',' } |
ForEach-Object { [int]$_ } |
Measure-Object -Sum |
Select-Object -ExpandProperty Sum
Write-Host "总和: $totalSum"
2. 使用网格 / 矩阵
grid.txt(示例)
#.##
.#.#
##.#
# 将网格加载为字符数组的数组
$grid = Get-Content -Path 'grid.txt' | ForEach-Object { $_.ToCharArray() }
# 访问元素(第 1 行,第 2 列)
Write-Host "元素位于 (1,2): $($grid[1][2])"
# 遍历网格
for ($row = 0; $row -lt $grid.Length; $row++) {
for ($col = 0; $col -lt $grid[$row].Length; $col++) {
Write-Host "($row,$col) = $($grid[$row][$col])"
}
}
3. 使用 .NET 集合提升性能与灵活性
# 可变的泛型 List[int]
$myList = [System.Collections.Generic.List[int]]::new()
$myList.Add(10)
$myList.Add(20)
$myList.Add(30)
Write-Host "列表计数: $($myList.Count)"
$myList.Remove(20)
Write-Host "移除后的列表: $($myList -join ', ')"
# 字典(PowerShell 中的哈希表)
$myDictionary = @{}
$myDictionary["apple"] = 1
$myDictionary["banana"] = 2
Write-Host "apple 的值: $($myDictionary["apple"])"
C# 热路径代码的互操作
当“惯用的 PowerShell”速度不够快时,直接嵌入 C#:
Add-Type @"
using System;
using System.Collections.Generic;
public static class AoCHelper {
// 示例:快速求整数数组的和
public static long FastSum(int[] numbers) {
long sum = 0;
foreach (int n in numbers) sum += n;
return sum;
}
// 在此添加更多性能关键的方法
}
"@
# 从 PowerShell 使用已编译的类
$numbers = 1..1_000_000
$sum = [AoCHelper]::FastSum($numbers)
Write-Host "Fast sum = $sum"
在 C# 中编写性能关键的算法,使用 Add-Type 编译,然后像普通 PowerShell 方法一样调用它。
综合运用
- 使用 习惯性的 PowerShell 进行解析和编排。
- 识别 性能瓶颈(使用
Measure-Command或分析工具)。 - 将 热点路径的代码段替换为 C# 互操作或外部工具。
- 通过 限制互操作的范围,保持整体脚本的可读性和可维护性。
最终思考
- Advent of Code 是一个极好的练习场,能够探索 PowerShell 的优势与局限。
- 通过结合 管道中心脚本、.NET 强大功能、C# 互操作 与 外部实用工具,你可以高效地解决即使是最棘手的 AoC 谜题,同时保持 PowerShell 作为优秀自动化语言的清晰可读性。
快速斐波那契序列计算(C#)
我们将定义一个 C# 类,其中包含一个静态方法,用于快速计算第 N 个斐波那契数。
$cSharpCode = @'
using System;
public static class FibonacciCalculator
{
public static long GetNthFibonacci(int n)
{
if (n left.Equals(right);
public static bool operator !=(Point left, Point right) => !(left == right);
}
'@
Add-Type -TypeDefinition $cSharpCode -Language CSharp
# Create and use Point structs
$p1 = New-Object Point(10, 20)
$p2 = New-Object Point(10, 20)
$p3 = New-Object Point(5, 5)
Write-Host "Point 1: $p1"
Write-Host "Point 2: $p2"
Write-Host "Point 3: $p3"
if ($p1 -eq $p2) { Write-Host "P1 and P2 are equal." }
if ($p1 -ne $p3) { Write-Host "P1 and P3 are not equal." }
利用外部工具
有时,针对特定子问题,最有效的解决方案并不是用 PowerShell 或 C# 编写,而是调用已有的、高度优化的外部工具。PowerShell 擅长编排这些工具、在它们之间传递数据并整合它们的输出。
示例:使用 WSL grep 进行高级模式匹配
# Assume input.txt contains:
# This is a test line.
# Another line with the word test.
# No match here.
# Find all lines containing "test"
$matchedLines = wsl.exe grep "test" "input.txt"
Write-Host "Lines containing 'test':"
$matchedLines | ForEach-Object { Write-Host $_ }
示例:调用自定义编译可执行文件
# Create a dummy input file for the external tool
Set-Content -Path 'puzzle_map.txt' -Value @'
###########
#S........#
###.......#
#.........#
#.......E.#
###########
'@
# Run the external pathfinder.exe (takes a file path and returns the shortest path length)
$pathResult = & ".\pathfinder.exe" "puzzle_map.txt"
Write-Host "Pathfinder result: $pathResult"
# If the external tool outputs JSON, you can parse it:
# $jsonResult = & ".\myparser.exe" "data.json" | ConvertFrom-Json
# $value = $jsonResult.SomeProperty
示例:将数据管道传递给/从外部工具
PowerShell 的管道可以与外部进程交互,将数据送入它们的标准输入并捕获它们的标准输出。
# Simulate an external tool that counts characters (e.g., `wc -c` on Linux)
# For Windows we’ll use a simple Python script (counter.py):
# import sys
# print(sum(len(line) for line in sys.stdin))
$dataToSend = @(
"Line 1"
"Another Line"
"Last One"
)
# Pipe data to the Python script and capture its output
# Ensure python.exe is in your PATH or specify its full path
$charCount = $dataToSend | python.exe - ".\counter.py"
Write-Host "Total characters (excluding newlines): $charCount"
选择合适的方法
| 功能 / 方法 | 纯 PowerShell(惯用) | C# 互操作 (Add-Type) | 外部工具编排 |
|---|---|---|---|
| 可读性与可维护性 | 高(针对 PowerShell 用户) | 中低(PowerShell 与 C# 混合) | 中等(PowerShell 进行编排;外部逻辑不透明) |
| 性能 | 中低(解释执行,对象开销) | 高(编译的 C# 以本机速度运行) | 非常高(利用高度优化的外部二进制文件) |
选择合适的方法取决于具体的 Advent of Code 题目、您对语言/工具的熟悉程度以及性能需求。使用 PowerShell 作为粘合剂,C# 负责热点路径计算,在外部工具能够提供明显优势时使用它们。
Development Effort
- Low to Medium – familiar language, rapid prototyping.
- Medium to High – requires C# knowledge, debugging can be split.
- Medium – requires understanding external tool APIs, input/output.
Complexity Handled
- 适用于大多数文本处理、数据操作、较简单的算法。
- 非常适合复杂算法、自定义数据结构、性能关键的逻辑。
- 非常适用于已有工具完美匹配的特定子问题。
适用于 AoC
- 大多数谜题的首选,尤其是第 1 部分。
- 适合第 2 部分的谜题,当第 1 部分的解法太慢时。
- 针对特定问题的细分领域(例如,繁重的正则表达式,如果存在相应工具则用于特定图算法)。
调试体验
- 优秀 在 PowerShell ISE/VS Code 中。
- 一般 – 脚本的 PowerShell 调试器,C# 逻辑在 PowerShell 中常常不透明。
- 一般 – 编排的 PowerShell 调试器,外部工具调试分离。
👉 阅读原文请访问 TechResolve.blog