※過去記事はこちら。AlteryxユーザーのためのAdvent of Codeの始め方、1日目、2日目、3日目
4日目です。
タイトルは、「Camp Cleanup」。キャンプのクリーンアップです。
4問目は、非常に素直な問題です。
今回は、入力された文字列から数値の範囲を2つ作り、特定の関係性(包含関係なのか、一部かぶっているのか)を持つものをカウントするという問題です。
入力データとしては、以下のようになっています。
2-4,6-8
2-3,4-5
5-7,7-9
2-8,3-7
6-6,4-6
2-6,4-8
1行目であれば、2から4の数値の範囲と6から8の数値の範囲を示しています。
Part1は、各行の2つの数字の範囲において、片方の数字の範囲がもう一方の数字の範囲の中に完全に入っているものをカウントします。
Part2は、両方の数字の範囲が一部でも被っていればカウントします。
非常にシンプルですね!
・ネ
・
・タ
・
・バ
・
・レ
直接値を比較する
今回のよう問題は、データをバラして複数の数値列にすれば、あとはフィルターツールで該当データを抜き出すだけです。
「2-4,6-8」といったデータをバラけさせるのは、正規表現ツールで「(\d+)-(\d+),(\d+)-(\d+)」としてパースを使えばデータ型まで指定するのでセレクトツールを省略することができます。
その他、列分割ツールでも可能です。ちなみに、列分割ツールですが、「,」で区切ったあとにさらに「-」で区切らなくても、「,-」と区切り文字に入力すれば一気に4つに分割してくれます。つまり、以下のように区切り文字に複数設定すれば全て区切り文字として認識して分割してくれます。
上の設定による処理結果は以下のとおりです。
ここまではPart1もPart2も一緒です。
Part1を解いてみる
ここまで準備が整えば、フィルターツールで一発です。以下のような数式となります。
([RegExOut1]<=[RegExOut3] AND [RegExOut4]<=[RegExOut2])
OR
([RegExOut3]<=[RegExOut1] AND [RegExOut2]<=[RegExOut4])
ワークフローとしては以下のようになります。
Part2を解いてみる
Part2もPart1とほぼ同じ考え方で数式が少し複雑になるだけです。前半の数式はPart1と同じ、後半の2つのAND条件については、一部値の範囲がかぶる範囲を計算しています。
([RegExOut1]<=[RegExOut3] AND [RegExOut4]<=[RegExOut2])
OR
([RegExOut3]<=[RegExOut1] AND [RegExOut2]<=[RegExOut4])
OR
([RegExOut1]<=[RegExOut3] AND [RegExOut3]<=[RegExOut2])
OR
([RegExOut1]<=[RegExOut4] AND [RegExOut4]<=[RegExOut2])
ワークフローとしては以下の通りとなります。
ツールゴルフをしてみる
正規表現ツールを使わず、REGEX_Replace関数を駆使すれば、フィルタツールのみでも対応できます。
もしくは、フォーミュラツールで実装することでさらに数を減らすことが可能です(さすがにフォーミュラツールで実装する時は1つの式にまとめると見にくすぎるので、内部的には小分けにした方がおすすめです)。
今回の場合、Part1は以下のような式で出力しています。
IF (ToNumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$1"))<=ToNumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$3")) AND ToNumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$4"))<=ToNumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$2")))
OR
(ToNumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$3"))<=ToNumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$1")) AND ToNumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$2"))<=ToNumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$4")))
THEN 1 ELSE 0 ENDIF
Part2はさらに長いです。
IF (Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$1"))<=Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$3")) AND Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$4"))<=Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$2")))
OR
(Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$3"))<=Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$1")) AND Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$2"))<=Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$4")))
OR
(Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$1"))<=Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$3")) AND Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$3"))<=Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$2")))
OR
(Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$1"))<=Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$4")) AND Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$4"))<=Tonumber(REGEX_Replace([Input], "(\d+)-(\d+),(\d+)-(\d+)", "$2")))
THEN 1 ELSE 0 ENDIF
さすがにデバッグもしんどくなるので、以下のように分割することをおすすめします。
別解
今回の主流の手法が上記の手法なのですが、個人的には上の方法は解いた後に試しています。
今回私が最初に行った手法は、以下のとおりです。
- 値の範囲から実際に数字を全て作る(行生成ツール使用)
- 2つの範囲をそれぞれカンマ区切りで結合する(行の先頭と後尾にそれぞれカンマをつけておく)
- Contain関数で比較後カウント(Part1の解答)
- 1のデータについて2つの範囲同士で結合し、結合されるものがあれば値の範囲がかぶるということなので、それをカウント
この方法の簡単なところは、不等号をどのように作るか、というロジックを全く考える必要がない点です。
特にPart1については特殊な方法かもしれません。例としては、「1-5,2-3」という入力があったとき、それぞれ「,1,2,3,4,5,」と「,2,3,」というものが出来上がるため、Contains関数を使うことで範囲内に入ればTrueとなります(前後逆にしたものとOR条件にする必要があります)。
Part1部分はこのような形になります。
結合ツールのところで、「,1,2,3,4,5,」というようなものがようやくできています。
Part2については、単なる結合を行ってカウントしているだけです。Part1、2両方含むワークフローは以下のとおりです。
Part2の方が圧倒的にシンプルですね。ロジック作るときにたまにハマるのでそれの対策にはなるかもしれません。
実は、Part2がもう少し難易度が高くなると思っていて、全ての数字を作ってみる、という方法を取ったのですが、結論としては主流の方法で十分だった、というオチがついてます・・・。
まとめ
- 4日目は単なるデータ比較の問題でした。
- 最速の人で7分ということでそれほど難易度は高くないかと思います。
- 警戒しすぎて全部データ作る方法でやってしまったのがもしかしたらミスかもしれないですが、逆に不等号のところで時間食いそうだったので、結果的にはこれでヨシと思っています(Part2はおかげでめちゃくちゃ簡単でした)。その代わりツールゴルフで最小叩き出すには主流派の方法であるべきでした。
- 個人タイム:Part1 8分56秒、Part2 10分47秒(Private Leaderboardで3位)
コメント