Private/ConvertTo-LineId.ps1

#Requires -Version 5.1

function ConvertTo-LineId {
    <#
    .SYNOPSIS
        変更前と変更後の行を、整数 ID に変換する。
    .DESCRIPTION
        同じ内容の行には、変更前と変更後をまたいで同じ ID を割り当てます。
        行の比較は大文字と小文字を区別します。
    .PARAMETER Left
        変更前の行の配列。空の配列と空行を含められます。
    .PARAMETER Right
        変更後の行の配列。空の配列と空行を含められます。
    .OUTPUTS
        [hashtable] @{ Left = [int[]]; Right = [int[]] }
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        # NOTE: AllowEmptyString が必須。Mandatory な [string[]] は、空文字を含む配列を
        # 「引数が空の文字列である」として拒否する。比較する行には空行が含まれる。
        [Parameter(Mandatory)] [AllowEmptyCollection()] [AllowEmptyString()] [string[]]$Left,
        [Parameter(Mandatory)] [AllowEmptyCollection()] [AllowEmptyString()] [string[]]$Right
    )

    # Myers 法は行の一致判定を何度も繰り返す。文字列のまま比較すると、長い行では比較処理が大半の時間を占めるため、先に整数に変換する。
    # 序数比較にするのは、既定の比較子がカルチャに依存し、環境によっては異なる行を同一と判定するため。
    # 大文字と小文字も区別する。データでは大文字と小文字の違いも変更に当たるため。
    $map = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,int]' -ArgumentList ([System.StringComparer]::Ordinal)
    $leftIds = New-Object -TypeName 'int[]' -ArgumentList $Left.Length
    $rightIds = New-Object -TypeName 'int[]' -ArgumentList $Right.Length

    for ($leftIndex = 0; $leftIndex -lt $Left.Length; $leftIndex++) {
        if (-not $map.ContainsKey($Left[$leftIndex])) { $map[$Left[$leftIndex]] = $map.Count }
        $leftIds[$leftIndex] = $map[$Left[$leftIndex]]
    }
    for ($rightIndex = 0; $rightIndex -lt $Right.Length; $rightIndex++) {
        if (-not $map.ContainsKey($Right[$rightIndex])) { $map[$Right[$rightIndex]] = $map.Count }
        $rightIds[$rightIndex] = $map[$Right[$rightIndex]]
    }
    return @{ Left = $leftIds; Right = $rightIds }
}