它是完美的练习素材,你可以写成几种方式,它的核心是:
- 读取 CSV 行
- 您可以以文本行的形式执行此操作,但 PowerShell 中的“正确”方式是
Import-Csv)。
-
测试文本/数字部分
- 首先处理文本条件(在您的代码中),任何超过该条件的内容将是数字。
- 首先尝试处理数字大小写,超过该范围的任何内容将是文本。
- 假设都是数字,如果中断,就是文本。这是可行的,但它使用异常来控制流,这很糟糕。这很糟糕,因为异常是针对异常情况的,并且您期望文本作为程序操作的正常部分,这有点糟糕,因为异常有很多开销。但是,您是程序员,您可以根据需要选择使用它们 - 例如它特别易读/清晰。
-
导出 CSV 行
- 同样,您可以将其作为文本行进行,但
Export-Csv 可以与 Import-Csv 配对,因此这是 PowerShell 中的“正确”方式。
在 PowerShell 术语中,您安排它是有意义的:
Import-Csv | ForEach-Object { process one CSV row at a time } | Export-Csv
(as opposed to:
$foo = import-csv
$bar = @()
foreach ($line in $foo) {
#...
$bar += $line
}
which is workable but ugly and wasteful of memory and CPU and won't scale nicely)
好的,我们已经处理了读/处理/写部分的结构。现在您将数值分配给存储桶。
0-10 11-20 21-30 31-40
\__/ \___/ \___/ \___/
或任何大小范围/存储桶条件。
这种模式尖叫if/else 或switch。
剩下的部分是,您选择 1. 2. 3. 方法中的哪一种,在哪里以及如何将文本与数字分开,以及如何将数字分配到存储桶中。
这种选择大部分是关于可读性和您的偏好。
具有开始和结束的桶意味着像start -le $num -and $num -lt end 这样的双重测试,然后两个边缘情况只有一个测试。但是你的三个桶意味着一个需要双重测试,两个需要单一测试。
if ($foo -gt 100)
elseif (51 -lt $foo -and $foo -le 100)
elseif ($foo -lt 50)
看看 if/elseif 和单/双测试的混杂。但是因为你的桶很好地相互碰撞,你可以利用/误用跌落测试来:
if ($foo -gt 100) { big }
if ($foo -le 100) { medium }
if ($foo -le 50 ) { small }
好的,有些会被分配为“中”,然后是“小”,但这种布局更易于阅读,看看它在做什么,不是吗?
除非你停止它,否则失败会隐式发生在switch 中,因此会有带有或不带break 的切换案例来阻止失败。
如果您选择首先匹配 文本,您可能会使用正则表达式来识别不是数字的东西(向后),所以我明白了:
Import-Csv .\t.csv | ForEach-Object {
if ($_.Col03 -notmatch '^\d+$')
{
$_.Col01 = 'text'
}
else
{
if ([int]$_.Col03 -gt 100) { $_.Col01 = 'large' }
if ([int]$_.Col03 -le 100) { $_.Col01 = 'medium' }
if ([int]$_.Col03 -le 50) { $_.Col01 = 'small' }
}
$_
} # | Export-Csv out.csv -NoTypeInformation
这是可行的,但是。如果您选择首先识别数字,您可以使用相同的正则表达式模式,或者告诉 .Net 框架将文本作为数字TryParse-ing。我明白了:
Import-Csv .\t.csv | ForEach-Object {
[int]$n = 0
if ([int]::TryParse($_.Col03, [ref]$n))
{
if ($n -gt 100) { $_.Col01 = 'large' }
if ($n -le 100) { $_.Col01 = 'medium' }
if ($n -le 50) { $_.Col01 = 'small' }
}
else
{
$_.Col01 = 'text'
}
$_
} # | Export-Csv out.csv -NoTypeInformation
这不是很漂亮。进行正则表达式/切换失败组合,我得到:
Import-Csv .\t.csv | ForEach-Object {
switch -regex ($_)
{
{$_.Col03 -notmatch '^\d+$'} { $_.Col01 = 'text' ; break }
{[int]$_.Col03 -gt 100} { $_.Col01 = 'large' }
{[int]$_.Col03 -le 100} { $_.Col01 = 'medium' }
{[int]$_.Col03 -le 50 } { $_.Col01 = 'small' }
}
$_
} # | Export-Csv out.csv -NoTypeInformation
这很漂亮,但部分; break / fallthrough 只是在等待有人在阅读时出错。并且使用异常处理作为控制流,我得到了这个,(由于catch块内$_的范围问题,我已经将其更改为首先将整个文件读入内存):
$Rows = foreach ($Row in Import-Csv .\t.csv)
{
try {
if ([int]$Row.Col03 -gt 100) { $Row.Col01 = 'large' }
if ([int]$Row.Col03 -le 100) { $Row.Col01 = 'medium' }
if ([int]$Row.Col03 -le 50) { $Row.Col01 = 'small' }
} catch {
$row.Col01 = 'text'
}
$row
}
$Rows # | Export-Csv out.csv -NoTypeInformation
但他们都在做基本相同的事情,我想不出更好的方法来做分桶,所以我所有的答案都是几乎相同的形状,即使它们确实运行完全不同的代码。
Frode F 使用从 if 分配的 PowerShell 再次以不同的方式工作,但在这种方法中我不能使用 fallthrough 存储桶检查 - 因此他使用 if/elseif/if 而不是 if/if/如果。这有点好,可以格式化为:
$row.Col01 = if ( $n -le 50) { 'small' }
elseif ( 51 -lt $n -and $n -le 100) { 'medium' }
elseif (100 -lt $n ) { 'large' }
然后更清楚的是它们是开始/结束范围,实际上是的,我最喜欢 Frode 的方法,只是没有格式化他的格式化方式,希望我在写这篇文章之前能阅读他的答案。