CodeIQのジェムストリング問題をPowerShellで解いてみた
結城浩さんがCodeIQで出題したジェムストリング問題。
限られた時間の中で解いて期限ギリギリに提出したのですが、使い慣れたJavaでかつマシンパワーに任せるという力技になってしまいました。
解答・解説を読み、高速化・省力化の手法を改めて学びなおしました。
というわけで、2月中ごろから本格的に手を付けてこれから習得しようとしているPowerShellで、改めてジェムストリング問題を解いてみました。
PowerShellで挑戦されている方は解答pdfの中には記載なかったかと思いますので初!かもしれません。
コード
まだPowerShell初心者丸出しですが生暖かい目でご覧下さい。
# Get-GemDays.ps1 ジェムストリング問題の計算 # usage # > Set-ExecutionPolicy -Scope CurrentUser Unrestricted # > .\Get-GemDays.ps1 # # 対象宝石パターンを発見したか $foundTarget = $false # メモ化の実施有無($true or $false) $memoized = $true #$memoized = $false # 宝石メモ $memo = @{} function Get-GemDays($gems, $target, $depth=0, $prevmatch=$false, $gemKeys=$null) { $days = 0 $match = $false if (!$gemKeys) { $gemKeys = $gems.Keys | %{$_} | sort } # アルファベット順に宝石の組合せ計算 $gemKeys | %{ if (!$match) { $gems.$_-- # メモ化のキー(配列だとうまくいかなかったので文字列) $gemMemoKey = $gems.Values | %{$_} | sort $gemMemoKey = $gemMemoKey -join "," if ($gems.$_ -ge 0) { $days++ # 対象宝石パターンと一致確認 $match = ($depth -eq 0 -or $prevmatch) -and $target[$depth] -eq $_ # 対象宝石パターンと完全一致した場合、以後の処理を行わない if ($match -and $depth -eq ($target.Length-1)) { $foundTarget = $true } if (!$foundTarget) { if (!$match -and $memo.Keys -contains $gemMemoKey -and $memoized) { # Write-Host "Memo-Hit:" $gemMemoKey $memo[$gemMemoKey] $days += $memo[$gemMemoKey] } else { $tmpdays = Get-GemDays $gems $target ($depth+1) $match $gemKeys $days += $tmpdays if (!$match -and $memoized) { $memo[$gemMemoKey] = $tmpdays # Write-Host "Memo-in:" $gemMemoKey $tmpdays } } } } $gems.$_++ } } # Write-Host "gems:" $gems.Values # Write-Host "days:" $days return $days } # "aaabcc" -> @{a=3;b=1;c=2} function Hash-Gems($gemstr) { $hashgems =@{}; $gemstr -split "" | %{if($_ -ne ""){$hashgems[$_]++}} return $hashgems } Measure-Command { # $days = Get-GemDays (Hash-Gems "aaabcc") "" $days = Get-GemDays (Hash-Gems "abbbbcddddeefggg") "eagcdfbe" } Write-Host "days:" $days
実行結果
メモ化有りの場合
約2秒。
> .\Get-GemDays.ps1 Days : 0 Hours : 0 Minutes : 0 Seconds : 2 Milliseconds : 148 Ticks : 21489916 TotalDays : 2.4872587962963E-05 TotalHours : 0.000596942111111111 TotalMinutes : 0.0358165266666667 TotalSeconds : 2.1489916 TotalMilliseconds : 2148.9916 days: 5578864439
メモ化無しの場合
# メモ化の実施有無($true or $false) #$memoized = $true $memoized = $false
に変えて実行。
・・・1時間経ちましたが終わる気配がありません。
気になること
break/continueの動作
for-each内で再帰呼出を行っているが、for-each内にbreakやcontinueを入れたら再帰呼出自体が終了してしまった。
ひとまずはif文のネストで回避したものの、動きが怪しいので余裕があれば調べてみます。
Where-Objectの扱い
「連想配列で、値が1以上のキー取り出し」がWhere-Objectを使ってできないか、気になったので後から調べてみました。
> $a = @{a=3;b=1;c=2;d=0;e=0;f=1} > $a.Keys | Where-Object {$a.$_ -gt 0} a f b c
どうもキー定義順にはならないようで、アルファベット順にするためにsortしています。