【问题标题】:Grouping data via LinQ in Powershell在 Powershell 中通过 LinQ 对数据进行分组
【发布时间】:2022-01-06 12:43:54
【问题描述】:

我有一个数据数组,我需要将它们按 2 个属性分组,然后计算每个组的第三个属性的总和。我想通过 Linq 尽可能快地做到这一点。

到目前为止,这是我的演示代码:

class costs {
    [string] $first;
    [string] $last;
    [int]    $price;
    costs([string]$first, [string]$last, [int] $price){
        $this.first = $first
        $this.last  = $last
        $this.price = $price
    }
}

[costs[]]$costs = @(
    [costs]::new('peter', 'parker', 1),
    [costs]::new('peter', 'parker', 2),
    [costs]::new('paul',  'summer', 3),
    [costs]::new('paul',  'winter', 4),
    [costs]::new('mary',  'winter', 5)
)

# group by full name:
$groupBy = [Func[Object,string]] {$args[0].first + $args[0].last}
$groupResult = [Linq.Enumerable]::GroupBy($costs, $groupBy)

# sum the costs per group:
$selectFunc   = [Func[Object,int]] {$sum=0; foreach($p in $args[0].price){$sum += $p};$sum}
$selectResult = [Linq.Enumerable]::Select($groupResult, $selectFunc)

$selectResult

selectResult 向我显示每个用户的正确成本总和。 但我正在努力从初始数组中获得两个用户属性的总和。 我也不确定,是否可以将两个 Linq 调用合并为一个以使其更快。 任何输入都非常受欢迎(除了“为什么是 Linq?”)。

更新

根据答案,我更新了如下代码:

class costs {
    [string] $first;
    [string] $last;
    [int]    $price;
    costs([string]$first, [string]$last, [int] $price){
        $this.first = $first
        $this.last  = $last
        $this.price = $price
    }
}

[costs[]]$costs = @(
    [costs]::new('peter', 'parker', 1),
    [costs]::new('peter', 'parker', 2),
    [costs]::new('paul',  'summer', 3),
    [costs]::new('paul',  'winter', 4),
    [costs]::new('mary',  'winter', 5)
)
foreach($doubler in 0..15){$costs += $costs}

cls
write-host "processing $($costs.count) elements."

(measure-command {
    # group by full name:
    $groupBy = [Func[Object,string]] {$args[0].first + $args[0].last}
    $groupResult = [Linq.Enumerable]::GroupBy($costs, $groupBy)

    # sum the costs per group:
    $selectFunc = [Func[Object,Object]]{
        $sum=0
        foreach($p in $args[0].price){
            $sum += $p
        }
        foreach($a in $args[0]) {
            [costs]::new($a.first, $a.last, $sum)
            break
        }
    }
    $selectResult = [Linq.Enumerable]::Select($groupResult, $selectFunc)
    $result = [Linq.Enumerable]::ToArray($selectResult)
}).TotalSeconds

$result

# and for the books the same procedure with a dataTable (slower):

$table = [System.Data.DataTable]::new('table')
[void]$table.Columns.Add('first', [string])
[void]$table.Columns.Add('last',  [string])
[void]$table.Columns.Add('price', [int])
$resultTable = $table.Clone()

# fill table with above test-data:
foreach($c in $costs){
    $null = $table.rows.Add($c.first, $c.last, $c.price)
}

(measure-command {
    $groupBy = [Func[System.Data.DataRow,string]] {$args[0].first + $args[0].last}
    $groupResult = [Linq.Enumerable]::GroupBy([System.Data.DataRow[]]$table.Rows, $groupBy)

    # sum the costs per group:
    $selectFunc = [Func[object,System.Data.DataRow]]{
        $sum=0
        foreach($p in $args[0].price){
            $sum += $p
        }
        foreach($a in $args[0]) {
            $resultTable.rows.Add($a.first, $a.last, $sum)
            break
        }
    }
    $selectResult = [Linq.Enumerable]::Select($groupResult, $selectFunc)
    $null = [Linq.Enumerable]::ToList($selectResult)
}).TotalSeconds
$resultTable

超过 300000 个元素的运行时间约为 2.5 秒。没那么糟。到目前为止,如果不切换到嵌入式 C# 代码,我找不到更快的方法。

【问题讨论】:

    标签: powershell linq group-by


    【解决方案1】:

    $selectFunc 定义更改为返回[psobject][object],然后从现有的分组值创建结果对象:

    $selectFunc   = [Func[Object,psobject]]{
        $sum=0
        foreach($p in $args[0].price){
          $sum += $p
        }
    
        # Output new object with first+last based on input object + sum
        $args[0] |Select first,last,@{Name='sum';Expression={$sum}} -First 1
    }
    

    我想通过 Linq 尽可能快地做到这一点。

    我强烈建议您实际测试这是否比使用更快,比如Group-Object,或者用于计算的简单哈希表 - 很多开销使 PowerShell 变慢(尤其是参数绑定),仍将适用于您的代码,因此差异可能并不显着 - 但您的脚本的可读性可能会受到很大影响。

    我个人的偏好是只使用Group-Object cmdlet:

    $costs |Group-Object first,last |ForEach-Object {
      $sum = ($_.Group |Measure price -Sum).Sum
      $_.Group |Select -Property first,last,@{N='Sum';E={$sum}} -First 1
    }
    

    【讨论】:

    • 非常感谢。当然,我会仔细检查结果是否比常规分组功能更快。我记得我过去在 Powerwhell 中使用 Linq 进行了几次性能测试,结果证明,手动 foreach-logic 最后更快。
    • @Carsten Fixed :) 我还添加了一个简单的 Group-Object 示例,您可以与之进行比较。要测量每种方法的总执行速度,请生成一组更大的测试数据并比较来自Measure-Command 的输出。如需更深入的分析,请查看PSProfiler
    猜你喜欢
    • 2017-05-13
    • 1970-01-01
    • 1970-01-01
    • 2015-06-05
    • 2010-10-01
    • 1970-01-01
    • 2016-06-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多