Friction River Software

  • お問い合わせ

CakePHP5入門【WebAPI編⑰】ボックス予想

A子

「引っ張り」を使って候補となる数字をいくつかピックアップしてさ

それを組み合わせて「ナンバーズ3」用の3けたの数字を作るとするじゃん?

B美

ふむ
それがどうかしたの?

A子

うん、「ボックス」用の予想を作るときってさ
候補となる数字が4つだったらすぐにできるわけよ(結果も4つしかないからね)

でも、候補数字が5つや6つの場合って、ちょっと大変だなぁ…って思ってね

C菜

自動化したいってことでしょうか~?

A子

そういうこと

ダブル(112や355等)やトリプル(111や222等)は考慮しなくて良いからさ
WebAPIとしてじゃなくて、Webアプリの便利機能として作ってみたいんだよね

B美

ふむ
それはちょっと面白いわね

やってみましょうか

C菜

まずはビュー(数字選択フォーム)からですね~
機能としては以下の二点を押さえておきたいです~

・6つのドロップダウンリストを作り、最低でも4つ以上選択されていないとエラー
・重複した値を選択できないようにする

重複排除をどのように行えば良いのか、さっぱり分かりませんけど~(苦笑)

A子

そういうときはChatGPT先生の出番だね

ちょっと質問してみるよ



A子

大変だった…(苦笑)

(CSSフレームワークの)Bulmaとの連携がうまくいかなくて、3~4回試行錯誤したよ

C菜

でも、できたんですよね~?

A子

うん
こんな感じかな

あ、ファイル名は「templates/Top/combination_input.php」ね





C菜

「src/Controller/TopController.php」の中に「combinationInput」メソッドも書きましたよ~
(中身は空ですけど~)

//指定された数字を組み合わせて「ボックス」用の値を作成(入力画面)
public function combinationInput()
{
}

そのあと、ブラウザからアクセスしてみました~

http://192.168.1.205/numapp/top/combination_input

A子

左端のドロップダウンリストで「3」を選んだあと、その一つ右のやつを選択しようとすると…

うん、一覧から「3」が消えてる
(「2」と「4」の間に本来あるべき「3」が無い…なお「5」以降は見えてないだけで、ちゃんとあるよ)

C菜

選択した個数が4つ未満のときのエラー表示も試してみました~

A子

次は、このフォームの送信先なんだけど、Formヘルパーで「combination_result」宛てにしてることからわかるように、「combinationResult」メソッドになるね

//指定された数字を組み合わせて「ボックス」用の値を作成(結果画面)
public function combinationResult()
{
    if ($this->request->is('post')) {
        $digits = $this->request->getData('digits');
    }
}

B美

あ、ちょっと待って!

その書き方じゃ、6つ全て選んだときは良いけど、4つや5つのときって困るわよ

C菜

え?なぜでしょうか~?

B美

例えば「0」「1」「2」「3」の4つを選択したとき、結果は…

["0", "1", "2", "3", "", ""]
になるの

なので、こう書くべきね

//指定された数字を組み合わせて「ボックス」用の値を作成(結果画面)
public function combinationResult()
{
    if ($this->request->is('post')) {
        $digits = array_filter($this->request->getData('digits'), 'strlen');
    }
}

A子

ん?ググったら「array_filter」の引数は一つだけで良さそうなんだけど…?

$digits = array_filter($this->request->getData('digits'));
…ってことね

B美

それだと、結果がこうなるわよ

["1", "2", "3"]

C菜

空文字列("")だけじゃなく、ゼロ("0")まで消えちゃってます~(苦笑)

A子

なるほどねぇ
んじゃ、次はその数字の組み合わせを作る方法だね

ChatGPT先生に聞いてみたら、こういうメソッドを教えてもらったよ

/**
* Combination 組み合わせ nCr (r ≦ n)
*
* @param array $arr 元となる配列
* @param int $r 1セットあたりの要素数
* @return null|array
*/

private function combination(array $arr, int $r): ?array
{
    //数値添字配列にする
    $arr = array_values($arr);

    $n = count($arr);
    $result = [];

    //条件不一致
    if ($n < 1 || $n < $r) {
        return null;
    }

    //1個選ぶ場合
    if ($r === 1) {
        foreach ($arr as $item) {
            $result[] = [$item];
        }
    }

    //2個以上
    if ($r > 1) {
        foreach ($arr as $key => $item) {
            $newArr = array_slice($arr, $key + 1);

            //再帰
            $recursion = $this->combination($newArr, $r - 1);

            if ($recursion !== null) {
                foreach ($recursion as $one_set) {
                    array_unshift($one_set, $item);
                    $result[] = $one_set;
                }
            }
        }
    }

    return $result;
}


B美

ふむ、さっきの入力値の配列(["0", "1", "2", "3"])をこのメソッドに渡した場合、得られるのはこんな配列ね

[
    ["0", "1", "2"],
    ["0", "1", "3"],
    ["0", "2", "3"],
    ["1", "2", "3"]
]

ここから
["012", "013", "023", "123"]
を作るには…

//指定された数字を組み合わせて「ボックス」用の値を作成(結果画面)
public function combinationResult()
{
    $box = [];

    if ($this->request->is('post')) {
        $digits = array_filter($this->request->getData('digits'), 'strlen');
        $box_set = $this->combination($digits, 3); //ボックスの組み合わせを作成
        foreach ($box_set as $row) {
            sort($row, SORT_NUMERIC);
            $candidate = ($row[0] * 100) + ($row[1] * 10) + ($row[2] * 1);
            array_push($box, sprintf("%03d", $candidate)); //ゼロパディングした3けたの文字列
        }

    }
}

…って感じかな
これで「$box」の中身は、["012", "013", "023", "123"]になるはず…

C菜

さすがはB美部長です~

あとは、これをビューファイル(templates/Top/combination_result.php)で表示するだけですね~

A子

ちょっと待って!
ここまでやったんだから、もう少し機能を追加しよう

各ボックス値の出現頻度(回数)直近の出現時期なんかを調べられないかな?

C菜

データベーステーブル(numbers)にボックス検索用の項目を追加しないとダメなんじゃないでしょうか~?

ちょっと面倒です~(苦笑)

B美

ん?
現状のスキーマ構造でも大丈夫よ

例えば、ボックス値「012」を検索したいときって、「百の位」「十の位」「一の位」の中で(最小値が「0」、最大値が「2」、それ以外が「1」)という条件で検索すれば良いのよ

A子

言ってる意味は分かるけど、どうやってやるのか見当もつかん…

C菜

ですね~

B美

そうねぇ

SQLで書けば、こうなるわ
(「012」を調べる場合)

select lottery_time,lottery_date_dt,lottery_date_str, num3_str
  from numbers
  where least(num3_place100, num3_place10, num3_place1)=0
  and (num3_place100+num3_place10+num3_place1)-least(num3_place100, num3_place10, num3_place1)-greatest(num3_place100, num3_place10, num3_place1)=1
  and greatest(num3_place100, num3_place10, num3_place1)=2
  order by lottery_date_dt desc;

あ、最後にソート(order by)してるのは、直近の出現時期を求めるためね

A子

least」は最小、「greatest」は最大って意味だっけ?

C菜

そうなりますね~

問題は「1」と等しいかを調べてる箇所なんですけど、全部足したものから最小値と最大値を引く~?
え~っと、あ!

(0+1+2)-0-2って、確かに1になりますね~

B美

そういうこと

面倒だから「ConnectionManager」を使って、直接さっきのSQL文を実行すれば良いと思うわよ
(というか、CakePHP風に書くやり方が分からん(苦笑))

A子

よし
んじゃ、やってみるか

$result = [];
・・・
//各ボックス値の詳細情報を取得
//直近の出現時期とその当選番号、過去の出現頻度等
$connection = ConnectionManager::get('default');
foreach ($box as $value) {
    //各桁の分解
    $digit = str_split($value);

    //データベース検索
    $sql =<<<EOF
        select lottery_time,lottery_date_dt,lottery_date_str, num3_str
          from numbers
          where least(num3_place100, num3_place10, num3_place1)={$digit[0]}
          and (num3_place100+num3_place10+num3_place1)-least(num3_place100, num3_place10, num3_place1)-greatest(num3_place100, num3_place10, num3_place1)={$digit[1]}
          and greatest(num3_place100, num3_place10, num3_place1)={$digit[2]}
          order by lottery_date_dt desc;
    EOF;
    $search_result = $connection->execute($sql)->fetchAll('assoc');

    //結果を格納
    if (count($search_result) > 0) {
        $recentry = $search_result[0]['lottery_date_str']; //直近の出現時期
        $lottery_time = $search_result[0]['lottery_time']; //直近の実施回
        $num3 = $search_result[0]['num3_str']; //直近のストレート数字
        $frequentry = count($search_result); //出現頻度(回数)
        array_push($result, [$value, $recentry, $lottery_time, $num3, $frequentry]);
    } else {
        array_push($result, [$value, '-', '-', '-', 0]);
    }
}

こんなもんでどう?

B美

ほほう
A子もなかなかやるようになったじゃない

結果である「$result」の中身は…

[
    [ボックス値, 直近の出現時期, 直近の実施回, 直近のストレート数字, このボックス値の出現回数],
    ・・・(以下、同じ形式の配列が並ぶ)・・・
]

…って感じかな?

C菜

あ~
この「$result」の中身の並びを「出現時期の昇順」や「出現回数の降順」にすることはできないでしょうか~?

一次元配列の中身をソートするのは「sort」関数でできますけど、これって「配列の配列」ですよね~?

B美

できるわよ
そういうときは「array_multisort」関数を使うの

まぁ、書き方が少し難しいから、それは私のほうでやっておきましょう

//直近の出現時期が古いものから順に並べ替え(ソートには「実施回」を使用)
array_multisort(array_column($result, 2), SORT_ASC, $result);

A子

そこにある数字の「2」って、配列の2列目ってこと?

つまり「出現時期の年月日」かな?

B美

違うわよ

配列はゼロスタートだから、先頭から3列目の「実施回」ね
(てか、コメントにも書いてるじゃない(苦笑))

C菜

分かりました~

出現時期で使ってる「lottery_date_str」って、日本語だからですよ~
(○○年○月○日という形式はソートに適さないですもんね~)

A子

なるほどね
んじゃ、「出現回数の降順」にするなら…

array_multisort(array_column($result, 4), SORT_ASC, $result);

…ってこと?

B美

惜しい!
降順にするなら、「array_multisort」関数に渡す第二引数を「SORT_DESC」に変えてね

array_multisort(array_column($result, 4), SORT_DESC, $result);

C菜

それじゃ、最後に(全体像を)まとめておきましょう~

あ、ビューファイル(templates/Top/combination_result.php)については、難しくないので(ここでの掲示は)省略しますね~


A子

よし、実行確認してみよう

あ、その前にトップページにリンクを作ったよ



C菜

スコア算出(連続減点版)における過去の実績(第6983回)で、試してみましょう~

えっと~
「0」「4」「6」「7」「8」の5つを入力して「実行」ボタンを押してみますね~

A子

このときの当選番号が「674」だったわけだけど…

お!
ボックス値として二番目に「467」があるね

C菜

この開発環境のデータって古いんじゃないですか~?

最新のデータだと、「467」はもっと下位になると思うんですけど~
(2026年5月14日に出現したばっかりだし…)

B美

あー、ごめん
更新をさぼってたから、2026年5月1日時点のデータみたい(汗)

最新のデータだったら、こうなるわね

A子

いやいや、予想するときって2026年5月14日の18時以前なんだから、古いほうのデータで良いんじゃないの?
(まだ2026年5月14日のデータが当選番号として登録されてない状態…ってこと)

B美

たしかにね…

さて、これで当初の目的は達成したかしら

C菜

バッチリだと思います~

読者の皆さんもぜひ使ってみてください~

A子

まーた、メタ発言だよ(苦笑)