Private/Get-LineSimilarity.ps1

#Requires -Version 5.1

function Get-LineSimilarity {
    <#
    .SYNOPSIS
        2 行の類似度を 0.0〜1.0 で返す。
    .DESCRIPTION
        削除行と追加行を、1 行の変更(Changed)として対応付けるかの判定に使います。
        類似度は「共通の先頭・末尾の文字数 ÷ 長い方の行の文字数」で計算します。
    .PARAMETER Left
        比較する一方の行。空文字も受け取ります。
    .PARAMETER Right
        比較するもう一方の行。空文字も受け取ります。両方が空なら 1.0 を返します。
    .OUTPUTS
        [double] 0.0(共通する先頭・末尾なし)〜 1.0(完全一致)
    #>

    [CmdletBinding()]
    [OutputType([double])]
    param(
        [Parameter(Mandatory)] [AllowEmptyString()] [string]$Left,
        [Parameter(Mandatory)] [AllowEmptyString()] [string]$Right
    )

    $longer = [Math]::Max($Left.Length, $Right.Length)
    if ($longer -eq 0) { return 1.0 }

    # LCS(最長共通部分列)を使わないのは、対応付けの判定のためだけに計算すると、判定の処理時間が差分計算の本体を上回るため。
    # 対応付けたい変更(桁数・日付・コード値の変更)は先頭と末尾が共通で、中央だけが異なるので、この近似で十分。
    # 列自体が別の列に置換された場合は共通部分が短く、類似度は低くなる。
    $limit = [Math]::Min($Left.Length, $Right.Length)
    $head = 0
    while ($head -lt $limit -and $Left[$head] -ceq $Right[$head]) { $head++ }
    $tail = 0
    while ($tail -lt ($limit - $head) -and
        $Left[($Left.Length - 1 - $tail)] -ceq $Right[($Right.Length - 1 - $tail)]) { $tail++ }

    return [double]($head + $tail) / $longer
}