【问题标题】:Find all unique occurrences of first 3 char, count number of occurrences and write output to a file查找前 3 个字符的所有唯一出现,计算出现次数并将输出写入文件
【发布时间】:2022-12-14 07:11:48
【问题描述】:

我有一个超过 2.5 亿行的文本文件。每行都有一个 3 位数的区号,后跟一个逗号和一个 7 位数的号码。

示例输入文件:
201,2220000
201,5551212
310,5552481
376,1239876
443,0002222
572,8880099
...

我想生成一个输出文件,其中列出每个唯一的区号和该区号的出现次数(仅查看每行的前 3 个字符)。

示例输出(区号、计数):
201, 44556
202、34529
...

我在 Windows 10 环境中工作。

经过大量研究,我能够在 PowerShell 中使用 的 Switch 函数来实现非常接近的效果。这个解决方案的问题是我需要知道我正在寻找哪些区号(而且我不知道这个文件中列出的所有区号)。

我想修改解决方案,使其找到所有唯一的区号,然后运行代码。

这是我尝试过的:


  1. 说,我要搜索以下四个区号:201,202,203,205
  2. 我的文本文件是 datafile.txt
    $count1 = 0
    $count2 = 0
    $count3 = 0
    $count4 = 0
    switch -File C:\datafile.txt -Exact -Regex { '201\S{8}' { ++$count1 } }
    Write-Output "Area Code 201: $($count1)" | Format-Table | Out-File "C:\summary.txt" -append
    switch -File C:\datafile.txt -Exact -Regex { '202\S{8}' { ++$count2 } }
    Write-Output "Area Code 202: $($count2)" | Format-Table | Out-File "C:\summary.txt" -append
    switch -File C:\datafile.txt -Exact -Regex { '203\S{8}' { ++$count3 } }
    Write-Output "Area Code 203: $($count3)" | Format-Table | Out-File "C:\summary.txt" -append
    switch -File C:\datafile.txt -Exact -Regex { '205\S{8}' { ++$count4 } }
    Write-Output "Area Code 204: $($count4)" | Format-Table | Out-File "C:\summary.txt" -append
    

    此代码生成文件 summary.txt 并将计数附加到区号。但是,我认为这是低效的,因为:

    1. 我需要知道这个数据文件中的所有区号。
    2. 我必须为每个额外的区号添加 3 行代码。

      感谢任何改进此代码或使用替代解决方案的帮助(我在 Stackoverflow 上发现了一个使用 grep https://stackoverflow.com/questions/61229157/using-regex-in-grep-for-windows-command-line 的线程,但它具有相同的限制 - 您需要知道要搜索的字符串。

【问题讨论】:

  • 您能否从这 250,000,000 行中提供大约 10 行的样本?
  • 太好了,将其编辑到您的问题中
  • 刚刚将其添加到问题中。
  • Import-Csv .\Input.txt -Header area |Group-Object -Property area -NoElement |Export-Csv .\Output.csv

标签: regex regex windows powershell grep switch-statement


【解决方案1】:

假设我理解正确,这里不需要 ,只需要 .SubString(0, 3) 来获取每行的前 3 个字符和 hashtable 来确保唯一代码和效率。

实际上,switch -File 非常适合这项任务,应该用来读取您的文件。否则,为简单起见并保持高效,您可以使用File.ReadLines

$map = @{ }
switch -File path	osourceile.txt {
    Default {
        $map[$_.Substring(0, 3)] += 1
    }
}

$map.GetEnumerator() | ForEach-Object {
    [pscustomobject]@{
        Code  = $_.Key
        Count = $_.Value
    }
} | Export-Csv path	o
esultOfUniqueCodes.csv -NoTypeInformation

【讨论】:

  • Export-Csv 而不是 Set-Content 完美地格式化了输出!太感谢了!
  • @Matteru 乐于提供帮助 :) 我很好奇处理 2.5 亿行代码需要多长时间?
  • 将近 10 分钟(但在运行时并没有真正占用太多系统资源)。该文件略多于 2.47 亿行。
  • @Matteru 抱歉回复晚了。是的,你肯定可以,我假设你使用的是Get-ChildItem,然后在循环中使用我的答案中发布的这个逻辑?如果是这样,在代码的最后一部分,您可以在[pscustomobject] 内添加一个新属性,例如FilePath = $_.FullName。如果这没有意义,请提出一个新问题,显示您当前必须循环文件并在需要时参考此问题
  • 谢谢指点。我能够使用Get-ChildItem并使用foreach($file in Get-ChildItem循环它因为我只想将文件名添加到输出中,所以我在[pscustomobject]中使用File = $file.name并将-Append添加到Export-csv
【解决方案2】:

流媒体方法只是我的两分钱——尽量避免所有相对较慢的东西,比如ForEach-ObjectpscustomobjectExport-Csv

# Create a scriptblock to be able to pipe output of foreach loop
& { 
    foreach( $line in [IO.File]::ReadLines( 'input.txt' ) ) { 
        $line.Substring( 0, 3 )
    }
} | Group-Object -NoElement | & {
    begin {
        'Code,Count'
    }
    process {
        '{0},{1}' -f $_.Name, $_.Count
    }
} | Set-Content output.csv

评论:

  • foreach( $line in [IO.File]::ReadLines( 'input.txt' ) ) 懒惰地处理输入文件,所以它是不是整体读入内存。这是有效的,因为 ReadLines 返回一个 foreach 理解的迭代器(不是集合)。正如其他人提到的那样,ReadLines 被认为是逐行处理文本文件的最快方法之一,同时仍然提供易用性(与使用 .NET 流相比)。
  • Group-Object -NoElement 仅计算唯一输入元素的出现次数,可能使用内部哈希表,因此它应该与手动创建的哈希表一样快(尽管未测量 - 这真的很有趣)。
  • Group-Object 到脚本块的管道比使用脚本块的ForEach-Object 快得多,请参阅GitHub issue。尽管在您的情况下并不重要,但瓶颈将是读取和处理输入文件。
  • 由于已知输入数据的种类,我们可以避免Export-Csv的复杂性(如转义规则),并使用简单的字符串操作和Set-Content直接创建CSV。同样,在这里不会真正有所作为,但对于其他输出量更大的情况,了解它可能仍然是件好事。

【讨论】:

  • 我刚刚使用包含 400 万个电话号码的样本文件进行了测试。 1) 使用 ReadLines 和 Group-Object 的解决方案耗时 22.04 秒 2) 使用 Switch 和 ForEach-Object 的解决方案耗时 7.5 秒。
  • @Matteru 感谢测试!我刚刚做了一些其他测试,似乎最大的收获来自于将 Group-Object 替换为 hashtable。比较 switchReadLines 似乎 ReadLines 在 PS 5.1 上稍快,而在 PS 7.3 上,switch 更快。
【解决方案3】:

尝试以下:

$input = @"
area,number
201,44556
202,34529
201,44556
202,34529
201,44556
202,34529
201,44556
202,34529
"@

$table = $input | ConvertFrom-Csv
$table | Format-Table

$groups = $table | Group-Object {$_.area}

$outputTable = [System.Collections.ArrayList]::new()
foreach($group in $groups)
{
$group | Format-Table

   $newRow = New-Object -TypeName psobject
   $newRow | Add-Member -NotePropertyName area -NotePropertyValue $group.Name

   $newRow | Add-Member -NotePropertyName count -NotePropertyValue $group.Count

   $outputTable.Add($newRow)  | Out-Null
}
$outputTable | Format-Table

【讨论】:

  • 你能澄清代码的初始部分吗?它是否要求列出所有需要读取的数据?
  • 刚刚创建的测试数据。所以我从一个字符串而不是一个文件中读取。您可以改用 Import-CSV 从文件中读取。
  • 正如它所关心的2.5亿行,我会按预期使用 PowerShell 管道 (One-at-a-time processing)(通过不是分配结果 $table = 阻塞管道并将所有内容加载到内存中)。只需 Import-Csv .Input..txt -Header area |Group-Object -Property area -NoElement 即可。
  • 顺便说一句,不要使用 $input 变量(没有解释),因为它是一个保留的自动变量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-12
  • 2013-12-23
  • 1970-01-01
  • 2015-09-14
  • 2017-06-18
相关资源
最近更新 更多