在PowerShell里使用LINQ

一直对LINQ感兴趣,正好PowerShell确实可以用,也写了不少相关的东西,那就另开这么一个文章,以后查也方便。

之前写过LINQ相关的代码会搬过来一些,稍微注意。

要是遇到需要重要的地方,哪怕跟LINQ并无关系,也会说一两句的。

环境

若未指明:

操作系统Win10 2004,PowerShell 7.1.0(现在的时点——2020/10/14——为rc.1),$PSCulture$PSUICulture均为ja-JP

(2020/10/23)刚才更新了一下,成了rc.2。

参考

别人踩出来的路

文档

正文

Aggregate

这个在JS那边对应的可以是Array.prototype.reduce()

[Linq.Enumerable]::Aggregate( # Aggregate<TSource>(
  [int[]] (1..100),           # IEnumerable<TSource>,
  [Func[int, int, int]] {     # Func<TSource,TSource,TSource>
    Param($s, $i)
    $s + $i
  }
)                             # )
# -> 5050

老小高斯了。

但写完了总觉得有什么地方不对:这么常见的操作,得有个Sum吧。

Sum

[Linq.Enumerable]::Sum([int[]] (1..100))
# -> 5050

……你说我刚才用Aggregate图什么www

再就是,虽然不提也罢,就是其中的参数必须明示类型,不然我看它自己找不到用哪个overload方法。

Select

嗯,JS那边说就是map。IE那边从版本9就开始支持这个了,自然IE 11也是OK的——躲不开的IE哟。

ConvertTo-Json (
[Linq.Enumerable]::Select( # Select<TSource,TResult>(
  [int[]] (1..10),         #   IEnumerable<TSource>,
  [Func[int, int]] {       #   Func<TSource,TResult>
    Param($i)
    $i * $i
  }
)                          # )
# -> [1,4,9,16,25,36,49,64,81,100]
) -Compress

这个用JS写就是

JSON.stringify([1,2,3,4,5,6,7,8,9,10].map(i => i * i))
// -> [1,4,9,16,25,36,49,64,81,100]

很常用的东西,不需要说太多也行。

SelectMany

换句话说,LINQ的SelectMany其实和我们常说的flatMap(JS那边会用到)基本用法上是一个东西。

IE直到11都没有这个,注意一下。

那么干脆就用JS那边的用例试试看。参见:Array.prototype.flatMap()

$arr = [int[]] @(1, 2, 3, 4)
<# -- 1 -- #>
$result =
[Linq.Enumerable]::SelectMany(                      # SelectMany<TSource,TResult>(
  [int[]] $arr,                                     #   IEnumerable<TSource>,
  [Func[int, int[]]] { Param($i); @($i, ($i * 2)) } #   Func<TSource,IEnumerable<TResult>>
)                                                   # )
ConvertTo-Json -InputObject $result -Compress
# -> [1,2,2,4,3,6,4,8]
<# -- 2 -- #>
$result =
[Linq.Enumerable]::SelectMany(
  [int[]] $arr,
  [Func[int, int[]]] { Param($i); @($i * 2) }
)
ConvertTo-Json -InputObject $result -Compress
# -> [2,4,6,8]
<# -- 3 -- #>
$result =
[Linq.Enumerable]::SelectMany(
  [int[]] $arr,
  [Func[int, int[][]]] { Param($i); @(,(,($i * 2))) } # 「@(,(,($i * 2)))」这样的写法在这里其实并无必要,毕竟Func的类型已经声明得非常充分了,哪怕写成「@($i * 2)」也可出正常结果。即便如此,还是要注意嵌套数组的表示方法。在此仅仅是抛砖引玉
)
ConvertTo-Json -InputObject $result -Compress
# -> [[2],[4],[6],[8]]
<# -- 之后的例子还是省略了吧 -- #>

至于嵌套数组:

# 我们可以:
ConvertTo-Json -InputObject @(,(,(1 * 2))) -Compress
# -> [[2]]
# 内部不需要计算值的话:
ConvertTo-Json -InputObject @(,(,2)) -Compress
# -> [[2]]
# 但是:
ConvertTo-Json -InputObject @(,(,1 * 2)) -Compress
# -> [[1,1]]
# 再稍微简单一点:
ConvertTo-Json -InputObject @(,1 * 2) -Compress
# -> [1,1]
# 嗯。总之当心

除了之前在例3里提到的(虽然现在无所谓)嵌套数组表示以外,还请注意ConvertTo-Json的使用方法。

咱们接着例3的结果继续:

# PowerShell 5.1
$result | ConvertTo-Json -Compress
# -> [{"value":[2],"Count":1},{"value":[4],"Count":1},{"value":[6],"Count":1},{"value":[8],"Count":1}]
# PowerShell 7.0.3 & PowerShell 7.1.0-rc.1
$result | ConvertTo-Json -Compress
# -> [[2],[4],[6],[8]]

原因应该是管道针对数组的特别处理。啊,实际上并不是很清楚为什么会出现Count,以后想起来了再去查。

这里有版本差异,而现在时点(2020/10/13),主流版本是PowerShell 5.1,为了避免不必要的麻烦,建议直接使用诸如ConvertTo-Json -InputObject $result -Compress的办法,因为它的行为相对一致,在本例结果均为[[2],[4],[6],[8]],对用户来说又很理想。

说来,JS那边有flat,简单说就是抹平一层嵌套。LINQ没有这东西,但实际上我们可以用x => x的办法,但要注意类型:

ConvertTo-Json (
[Linq.Enumerable]::SelectMany(
  [int[][]] @((,2),(,4),(,6),(,8)), # [[2],[4],[6],[8]]
  [Func[[int[]], [int[]]]] {        # 看着挺晕的,但遇到这种含有数组的类型声明不要用的情况,也只好对这数组再套一层方括号了
    Param($x)
    $x
  }
)) -Compress
# -> [2,4,6,8]

至于JS那边,可以

JSON.stringify([[2],[4],[6],[8]].flat())
// -> [2,4,6,8]
InSb

InSb

只是跟工作和生活相关的记录