タイダログ

もっと怠けますか? (y/n)

To "Another PowerShell Math Challenge"

"Another Powershell Math Challenge" と "Solving Another PowerShell Math Challenge" に寄せて

PowerShell で算数の問題の解き方を考えている方々を見つけました。面白そうだったので私も挑戦してみます。

問題

私が見た記事はこちらです。Problem #2 に挑戦します。

jdhitsolutions.com

元ネタはこちらだそうです。上記の Problem #2 は、元ネタだと Advanced Level となっています。

ironscripter.us

以下のような数値の配列を受け取り、

$a = 2,5,6

数値の全ての組み合わせの和を求めるという問題です。

2
5
6
2+5 = 7
2+5+6 = 13
2+6 = 8
5+6 = 11

Another PowerShell Math Challenge – Iron Scripter より

私の考え方

数の全ての組み合わせの和を求めるという問題なので、それそれの数について、使うか使わないかの二択を考えたらいいですね。

使う→o、使わない→x とすると以下の通りになります。

Pattern 2 5 6
0 x x x
1 o x x
2 x o x
3 x x o
4 o o x
5 o o o
6 o x o
7 x o o

この感じ、なんだか見覚えが……そうですね、2進数ですね!

使う→1、使わない→0 です。

Pattern 2 5 6
0 0 0 0
1 0 0 1
2 0 1 0
3 0 1 1
4 1 0 0
5 1 0 1
6 1 1 0
7 1 1 1

たとえば以下のパターンだと、2 と 6 を使い、5 は使いません。和は 8 です。

Pattern 2 5 6
5 1 0 1

ということで、2進数を使って全ての組み合わせを作り、和を求めます。

作業環境

コード

function

元ネタのページに提出しようと思いましたが締め切られていました。

one-liner

ワンライナーでもどうぞ。

$nums = 2, 5, 6

1..([Math]::Pow(2, $numbers.Length) - 1) | % {([long][Convert]::ToString($_, 2)).ToString($('0' * $numbers.Length))} | % {$binaryString = $_; $sum = 0; 0..($_.Length - 1) | % {if ($binaryString[$_] -eq '1') {$sum += $numbers[$_]}}; $sum} | sort | unique

説明

function 版で説明します。

int の配列を受け取ります。

param (
    [int[]]
    $Numbers
)

組み合わせの数を数えます。数が 3 つなら 23- 1 = 7 通りです。どの数も使わないパターン(つまり 0)は不要なので除きます。
(2022/05/18 変数名を訂正)

$patternCount = [Math]::Pow(2, $Numbers.Length) - 1

2進数を3桁で表すための「カスタム数値形式文字列」を作り、

カスタム数値形式文字列 | Microsoft Docs

[string]$binaryFormatString = '0' * $Numbers.Length

1から7の整数 ($i) を3桁の2進数で表し、
(2021/09/26 変数名を訂正)

for ($i = 1; $i -le $patternCount; $i++) {
    [string]$patternBinary = ([long][Convert]::ToString($i, 2)).ToString($binaryFormatString)

2進数の左から $j 桁目が 1 だったら、配列の $j 番目の要素を $sum に加算します。2進数が 101 の場合、配列の 0 番目の要素と 2 番目の要素を $sum に加算します。
(2022/05/18 一部表記を変更)

    for ($j = 0; $j -lt $patternBinary.Length; $j++) {
        if ($patternBinary[$j] -eq '1') {
            $sum += $Numbers[$j]
        }
    }

その組み合わせの和を $result 配列に追加します。

    $result += $sum

全組み合わせでこれを行ったら、各組合せの和を昇順にして、重複を除いて出力します。

return $result | Sort-Object | Get-Unique

発展問題

Twitter でこんな問題を見つけました。

今回の function を応用してこちらも解いてみます。

function Get-AllPossibleSum2 {
    param (
        # Specifies numbers to get all the possible sums
        [Parameter(Mandatory=$true,
                   HelpMessage="Numbers to get all the possible sums."
                   )]
        [int[]]
        $Numbers
    )


    Write-Verbose "`$Numbers.Length: $($Numbers.Length)"
    
    [int]$patternCount = [Math]::Pow(2, $numbers.Length) - 1
    Write-Verbose "`$patternCount: $patternCount"
    
    [string]$binaryFormatString = '0' * $Numbers.Length
    Write-Verbose "`$binaryFormatString: $binaryFormatString"

    for ($i = 1; $i -le $patternCount; $i++) {
        Write-Verbose "`$i: $i"

        [string]$patternBinary = ([long][Convert]::ToString($i, 2)).ToString($binaryFormatString)
        Write-Verbose "`$patternBinary: $patternBinary"

        $currentPattern = [PSCustomObject]@{
            Pattern = $patternBinary
            Numbers = @()
            Sum = 0
        }

        for ($j = 0; $j -lt $patternBinary.Length; $j++) {
            Write-Verbose "`$patternBinary[$j]: $($patternBinary[$j])"

            if ($currentPattern.Pattern[$j] -eq '1') {
                $currentPattern.Numbers += $numbers[$j]
                $currentPattern.Sum += $numbers[$j]
            }
        }
        
        Write-Output $currentPattern
    }

}
$nums = 24,35,26,31,29
Get-AllPossibleSum2 -Numbers $nums | ? {$_.Sum -gt 100} | sort Sum | select -First 1

配列は Number プロパティに入っています。

Pattern Numbers          Sum
------- -------          ---
10111   {24, 26, 31, 29} 110

まとめ

情報Ⅰの授業で2進数を扱うときに、どうすれば生徒の興味を引いたり、2進数の有用性を伝えたりできるか考えていた時にこの問題に出会いました。

実生活でも「2択の組み合わせを全て考える」ということはままあるのではないでしょうか。そんなときに2進数が使えるよ、というのは有用性の話としていいのかもしれません。「お土産が5種類あって、買うか買わないかの2択の組み合わせを全て考えて予算内で収まるものを考える」とかでしょうか。テンション上がってきましたよ。

更新履歴

  • 説明」のコード内の変数名を訂正 (2021/09/26)
  • 説明」のコード内の変数名を訂正 (2022/05/18)
  • 説明」の一部表記を変更 (2022/05/18)
  • こちらもどうぞ」に記事を追加 (2022/05/18)