子供の身長制限、出発前にプログラムで検出する話
家族でテーマパークに行く予定を立てているエンジニア向けの記事。特に子供が3人以上いて、年齢もバラバラで、旅程を組む時に「このアトラクションは何cmから乗れるんだっけ」を何度も確認し直した経験がある人向け。自分はこれで現地で1回痛い目を見た。
結論から書く。身長制限や年齢制限は「制約」として定義できる。人力で1つずつ確認するのではなく、家族構成とアトラクション一覧を突き合わせて矛盾を機械的に検出すればいい。これは制約充足問題(CSP)としてかなり素直にモデル化できる。自分はこれをC言語ではなくJavaScriptで単一HTMLとして実装したが、考え方自体はC言語のロジック設計と変わらない。
問題の深掘り:なぜ現地で気づくのか
自分の失敗を先に書く。去年の夏、子供3人(8歳、5歳、2歳)を連れてテーマパークに行った。事前に人気アトラクションを4つピックアップして、当日の時間割まで紙に書いて準備した。
現地に着いて2つ目のアトラクションの列に並んでいる時に、スタッフから「身長117cm未満のお子様はご利用いただけません」と言われた。5歳の子がちょうど115cmで、アウトだった。
このアトラクションは事前にWebサイトで確認していたつもりだった。ただ確認したのは1つ目と3つ目で、2つ目は「たぶん大丈夫だろう」で飛ばしていた。4つのアトラクション×3人の子供で12パターンの組み合わせを全部頭の中でチェックするのは、朝の準備で疲れている状態だと普通に漏れる。
これがなぜ起きるかというと、身長制限の確認は「人間の記憶力とやる気に依存したチェック作業」になっているからだと思う。エンジニアなら分かる話で、手作業のチェックリストは項目数が2桁を超えたあたりから漏れが出やすい。しかも制限値は施設によって110cm、115cm、120cmとバラバラで、単純な一覧表を作っても見比べる時に見落とす。
自分の場合、この失敗で2つ目のアトラクションの整理券が無駄になり、5歳の子は泣き、代替案を探すのに30分使った。この30分は旅程全体の後ろの予定を全部押した。
解決手順:制約充足問題として組む
ここからは実際にどう組んだかを書く。C言語エンジニアであれば、この考え方は組み込みの状態遷移やリソース割り当てのロジックと近いものとして理解できると思う。
ステップ1:制約を「変数・ドメイン・制約式」に分解する
CSPの基本は3つの要素で成り立っている。
- 変数(Variables):誰が、どのアトラクションに乗るか
- ドメイン(Domain):各変数が取りうる値の範囲(この場合は身長・年齢)
- 制約(Constraints):ドメイン同士が満たすべき条件
家族旅行の場合、変数は「子供1人 × アトラクション1つ」の組み合わせになる。子供が3人、アトラクションが4つなら12個の変数ができる。
ステップ2:データを構造化する
自分がまず作ったのは、家族の情報とアトラクションの情報をそれぞれJSON的な構造で持つことだった。C言語で書くならこういう構造体イメージになる。
typedef struct {
char name[32];
int height_cm;
int age_months;
} FamilyMember;
typedef struct {
char name[64];
int min_height_cm; // 0なら制限なし
int max_height_cm; // 0なら制限なし
int min_age_months; // 0なら制限なし
int requires_guardian; // 保護者同伴が必要か
} Attraction;
ここでのポイントは「制限なし」を0で表現するか、別途フラグで持つかを最初に決めておくこと。自分は最初0で統一していたが、身長制限0cmと「制限なし」を混同するバグを作ってしまい、後でフラグ方式に直した。この手戻りに半日使った。
ステップ3:制約チェック関数を書く
int check_constraint(FamilyMember *m, Attraction *a) {
if (a->min_height_cm > 0 && m->height_cm < a->min_height_cm) {
return 0; // 身長不足でNG
}
if (a->max_height_cm > 0 && m->height_cm > a->max_height_cm) {
return 0; // 身長超過でNG(小さい子供専用アトラクション等)
}
if (a->min_age_months > 0 && m->age_months < a->min_age_months) {
return 0; // 年齢不足でNG
}
return 1; // OK
}
このチェックを家族全員×アトラクション全部の組み合わせで回せば、事前に「誰がどこに乗れないか」を全部洗い出せる。人間が手作業でやると漏れる12パターンのチェックも、プログラムなら1回の実行で全部出る。
ステップ4:矛盾を早期に検出する
単純な制約チェックだけでなく、旅程全体としての矛盾も検出したい。例えば「2歳児は保護者同伴必須のアトラクションだが、その時間帯に別の子供の予約が別施設で入っている」というような、時間軸をまたいだ制約もある。これは変数間の依存関係として扱う必要がある。
自分はこの部分で最初、単純なループで全組み合わせを総当たりしていた。子供3人、アトラクション6つ、時間帯4区分だと72通りになり、C言語で書くならそれほど重くないが、JavaScriptで書いていた時にブラウザが一瞬固まった。ここは後でメモ化して枝刈りするロジックに直した。
落とし穴と対処
実際に組んでみて詰まった点を書く。
1つ目は制限値のデータ収集。公式サイトの表記が「110cm以上」なのか「110cm未満不可」なのか、日本語の言い回しが施設によって違う。これはNDA的な話ではないが、機械的にスクレイピングするのが難しく、結局は手入力でデータベースを作ることになった。ここは自動化を諦めて手作業にした部分になる。
2つ目は予約解禁日の扱い。宿や新幹線の予約は解禁日が施設ごとに違い、しかも「何日前」というルールが変わることがある。自分は最初これを固定ルールで推定しようとしたが、変更が入るたびにロジックを直すはめになった。最終的には推定をやめて、毎日実際の予約サイトを観測して解禁されたかどうかを記録する方式に変えた。推定より観測の方が壊れにくいという実感がある。
3つ目は保護者同伴条件。これは家族構成によって「誰が同伴できるか」が変わるため、単純な身長・年齢チェックだけでは足りず、家族メンバー間の関係性(誰が大人か)も変数に含める必要があった。ここを見落として、最初のバージョンでは大人1人しかいない前提でロジックを組んでしまい、後で家族構成を可変にする改修が入った。
この問題、自分も現地で困った経験があって、解決のために段取りたびというツールを作った。旅程データと家族データを入れると、身長制限・時間重複・予約解禁日の3つを出発前にまとめてチェックできるようにしている。
関連ツール
段取りたびは、家族の制約から旅程の破綻を出発前に検出する、オフラインで動く旅程チェッカー。上で書いたCSPロジックと予約解禁日の観測の仕組みを組み込んでいる。LLMは使わず単一HTMLで動く設計にしていて、ネット環境がない場所でも開けるようにしてある。
→ 段取りたび
まとめ
身長制限や予約重複の確認は、人間の記憶力に頼ると2桁の組み合わせを超えたあたりから漏れが出やすい。これはCSPとして構造化すれば機械的に検出できる問題で、C言語の状態管理や組み込みのリソース割り当てと考え方は近い。自分は一度現地で泣かれてから作った仕組みだが、事前に1回でも組んでおけば同じ失敗は避けられると思う。
著者:ぽん(@pon_freelance)
C言語実務23年、組み込み/制御系。
副業で技術記事販売と自作ツール販売をやっている。
書いているもの:
- 段取りたび - 家族の制約から旅程の破綻を出発前に検出する、オフラインで動く旅程チェッカー
(その他:(なし))
同じ手で作った道具を、インストールなしで試せます — 自分が実際に使っている自作ツールを、ブラウザからそのまま動かせる形で並べています。登録は不要です。
→ RunStack(無料で試す)