按字母顺序排序,小写在大写之前?

Tom*_*ver 4 sorting powershell output

我从 Project Gutenberg 的“The Complete Works of William Shakespeare by William Shakespeare”开始,这是一个 UTF-8 文本文件,可从http://www.gutenberg.org/ebooks/100 获得。在 PowerShell 中,我跑了

Get-Content -Tail 50 $filename | Sort-Object -CaseSensitive
Run Code Online (Sandbox Code Playgroud)

这 - 我相信 - 将文件的最后 50 行(即由换行符分隔的字符串)通过管道传输到Sort-Object,该文件被配置为按字母顺序排序,字符串以小写字母开头,然后字符串以大写字母开头。

为什么下图中的输出(尤其是 P 的)没有根据-CaseSensitive开关排序?什么是解决方案?

链接到排序输出图片

mkl*_*nt0 6

注意:此答案侧重于对整个字符串进行排序的一般情况(按所有字符,而不仅仅是按一个字符)。

您正在寻找序数排序,其中字符按其Unicode 代码点(“ASCII 值”)进行数字排序,因此所有大写字母作为一个组,在所有小写字母之前排序

从 Windows PowerShell v5.1 / PowerShell Core v7.0 开始,Sort-Object 总是 使用词法排序[1](默认情况下使用不变区域性,但这可以通过-Culture参数更改),其中区分大小写的排序仅意味着小写形式给定字母的直接出现在大写形式之前,而不是所有字母的统称;例如,bsorts before B,但它们都在aand 之后A(而且,逻辑与序数情况相反,大写字母在前):

PS> 'B', 'b', 'A', 'a' | Sort-Object -CaseSensitive
a
A
b
B
Run Code Online (Sandbox Code Playgroud)

但是,有一种解决方法,它 (a) 在小写字母之前对大写字母进行排序,并且 (b) 以牺牲性能为代价:

  • 为了通过直接序数排序获得更好的性能,您需要直接使用 .NET 框架 - 见下文,它还提供了一种先对小写字母进行排序的解决方案。
  • 此 GitHub 问题Sort-Object正在讨论增强以支持序数排序。
# PSv4+ syntax
# Note: Uppercase letters come first.
PS> 'B', 'b', 'A', 'a' |
      Sort-Object { -join ([int[]] $_.ToCharArray()).ForEach('ToString', 'x4') } 
A
B
a
b
Run Code Online (Sandbox Code Playgroud)

该解决方案将每个输入字符串映射到由 4 位十六进制组成的字符串。字符代码点的表示,例如'aB'变成'00610042',表示代码点0x610x42;比较这些表示就相当于按字符的代码点对字符串进行排序。


使用 .NET 进行直接、性能更好的序数排序:

# Get the last 50 lines as a list.
[Collections.Generic.List[string]] $lines = Get-Content -Tail 50 $filename

# Sort the list in place, using ordinal sorting
$lines.Sort([StringComparer]::Ordinal)

# Output the result.
# Note that uppercase letters come first.
$lines
Run Code Online (Sandbox Code Playgroud)

[StringComparer]::Ordinal返回一个实现[System.Collections.IComparer]接口的对象。

管道中使用此解决方案是可能的,但需要通过管道将行数组作为单个对象发送,-ReadCount参数提供:

Get-Content -Tail 50 $filename -ReadCount 0 | ForEach-Object { 
  ($lines = [Collections.Generic.List[string]] $_).Sort([StringComparer]::Ordinal)
  $lines # output the sorted lines 
}
Run Code Online (Sandbox Code Playgroud)

注意:如上所述,这首先对大写字母进行排序


要全部使用小写字母排序第一,你需要实现自定义的通过方式排序[System.Comparison[string]]的委托,这在PowerShell中可以实现为一个脚本块({ ... }它接受两个输入字符串并返回其分类排名() -1(或任何负值)为少-than0对于equal1(或任何正值)对于大于):

$lines.Sort({ param([string]$x, [string]$y)
  # Determine the shorter of the two lengths.
  $count = if ($x.Length -lt $y.Length) { $x.Length } else { $y.Length }
  # Loop over all characters in corresponding positions.
  for ($i = 0; $i -lt $count; ++$i) {
    if ([char]::IsLower($x[$i]) -ne [char]::IsLower($y[$i])) {
      # Sort all lowercase chars. before uppercase ones.
      return (1, -1)[[char]::IsLower($x[$i])]
    } elseif ($x[$i] -ne $y[$i]) { # compare code points (numerically)
      return $x[$i] - $y[$i]
    }
    # So far the two strings compared equal, continue.
  }
  # The strings compared equal in all corresponding character positions,
  # so the difference in length, if any, is the decider (longer strings sort
  # after shorter ones).
  return $x.Length - $y.Length
})
Run Code Online (Sandbox Code Playgroud)

注意:对于英文文本,以上应该可以正常工作,但是为了支持可能包含代理代码单元对和不同规范化形式(组合与分解重音字符)的所有 Unicode 文本,还需要做更多的工作。


[1]的Windows,所谓字排序默认情况下进行的:“某些非字母数字字符可能会分配给他们特殊的权重。例如,连字符(。 -)可能会分配给它一个非常小的权重,使得coopco-op出现在排序列表中彼此相邻。"; 在类 Unix平台上,字符串排序是默认设置,其中没有特殊权重适用于非字母数字字符。- 查看文档