Friction River Software

  • お問い合わせ

CakePHP5入門【WebAPI編⑭】ナンバーズ3を予想①

A子

前回「引っ張り」について検証してみたわけだけど…

これって結構「当てになる」という印象だよね
いや、むしろ「当たる」?

B美

うまいこと言った…みたいなドヤ顔はやめなさいね(苦笑)
でも、それについては私も同意するわ

なんだったら「前々回」と「前回」の数字の組み合わせだけで「今回」の当選番号が決まってるってケースもあったし…

C菜

ですね~

あと、ある数字が2回連続した場合、3回目にも同じ数字が出現する…って法則(?)もありましたよ~

A子

あぁ、たしかに…

○○○」だけじゃなく「○○×○」とか「○×○○」「○×○×○」ってパターンもあったね
(「○○」は同じ数字が連続してる…ってことね)

B美

とりあえず「ナンバーズ3」のほうだけ予想してみましょう
(「ナンバーズ4」については到底当てられる気がしないし…(苦笑))

A子

まず「引っ張り」を使うのはマストだね

「前回(1回前)」「前々回(2回前)」だけじゃなく、「3回前」と「4回前」も見ないとダメかな

C菜

「○○×◎」「○×○◎」「○×○×◎」というパターンを考えるとそうですね~
(「◎」が今回分の予想数字です~)

B美

「0」から「9」までの各数字にスコア(得点)を付けるとして…

ケーススコア備考
1回前に出現+3○◎
2回前に出現+3○○◎,○×◎
(1回前または2回前の
どちらかに出現していて、かつ)
3回前に出現
+2○×○◎,○○×◎
(2回前だけに出現していて、かつ)
4回前に出現
+1○×○×◎

という感じでどうかしら?

A子

そうすると、各パターンの得点合計は

ケーススコア
○○◎6
○×○◎,○○×◎5
○×○×◎4
×○◎,○×◎3

…ってことかな?

C菜

これに「曜日(七曜)」ごとの最頻値の数字に「+1」、「六曜」ごとの最頻値の数字に「+1」したらどうでしょう~?
(各桁ごとじゃなく、各桁の合計で判断して~)

その場合、最低点が「0」で、最高点が「8」の9段階評価になりますよ~

B美

ふむ、良いかもしれない…

あくまでもメインは「引っ張り」で、出目の偏りのほうはサブ的な位置付けになるわけね

A子

とにかくこの考え方でやってみようよ

ダメなら、また別のやり方を考えれば良いじゃん

B美

あと、最新のデータが必要な「引っ張り」だから、「年月日」指定での予想はできないわよ

WebAPIを呼び出した日の予想ってことになるわね

A子

ん?ちょっと待ってよ

曜日」や「時間」の判定も必要じゃない?
だって、土曜日や日曜日に呼び出した場合は「月曜日」分の予想になるわけだし…

あと、夜の8時にアクセスした場合、その日の分の予想はおかしいよね
(抽選は夜7時過ぎくらいのはず…)

C菜

「平日」に必ず抽選されるんでしょうか~?

B美

唯一の例外は、年末年始

大晦日(12月31日)と正月三が日(1月1日~1月3日)については、たとえ平日でも抽選は無いわ

C菜

ということは~

1.WebAPIを呼び出した日の「年月日」及び「時間」を求める
2.「年月日」から曜日を求め、土曜日なら「+2」、日曜日なら「+1」する
  (この場合の予想日は「月曜日」となる)
3.その日が年末年始かどうかを判定し、そうであれば「年月日」を1月4日とする
  (1月4日が土日であるかどうかの判定も行う)
4.2と3の条件に合致しない場合、「時間」が【0:00~17:59】の間にあるかどうかを判定する
  (これ以外の時間の場合、エラーを返す)
5.その「年月日」を予想日ととらえ、最新のデータを4件取得してスコアを算出する

というアルゴリズムでどうでしょうか~?

A子

おぉ、さすがはC菜だね

あ、4番って、平日の18時以降はエラーにするってこと?

B美

その日の分のナンバーズを購入できるのは、たしか18時頃までのはずだし、その日の当選番号が最新データとして(このWebアプリに)反映されるのが22時だからね
(スクレイピングのクーロン設定を22時にしたから…)

A子

だったら【22:00~23:59】のアクセスは翌日分と考えれば良いじゃん

B美

超・面倒くさい!

翌日が大晦日や土曜日って場合もあるし…

A子

うっ、たしかに…(苦笑)

B美

とにかく、まずは「src/Controller/ApiController.php」の中に新たなメソッドを作ってみなさい
(話はそれからよ)

C菜

了解です~

num3Prediction()」というメソッド名にしましょう~
(URLでは「…/api/num3_prediction」という呼出しになりますね~)

A子

まずは1番だね

1.WebAPIを呼び出した日の「年月日」及び「時間」を求める

時間を扱うわけだから、当然「DateTime」クラスになるよね?

use Cake\I18n\DateTime;

C菜

たしか、こうですよ~

$date = DateTime::now();
$year = intval($date->format('Y'));
$month = intval($date->format('n'));
$day = intval($date->format('j'));
$hour = intval($date->format('G'));

あ、整数化(intval)は念のためです~

A子

んじゃ、次は2番…

2.「年月日」から曜日を求め、土曜日なら「+2」、日曜日なら「+1」する
  (この場合の予想日は「月曜日」となる)

「曜日」を求めるのに「SixSevenWeekComponent」を使う?

C菜

いえ、formatメソッドで求められますよ~

$week = $date->format('w');

これで0(日曜日)から6(土曜日)までの数値が得られますね~

A子

だったらこうすれば良いね

//日曜日ならば1日進める
if ($week == 0) {
    $date->modify('+1 day');
}

//土曜日ならば2日進める
if ($week == 6) {
    $date->modify('+2 day');
}

C菜

次は年末・年始判定です~

3.その日が年末年始かどうかを判定し、そうであれば「年月日」を1月4日とする
  (1月4日が土日であるかどうかの判定も行う)

こんな感じでしょうか~?

//年末年始判定
$month = intval($date->format('n'));
$day = intval($date->format('j'));

if ($month == 12 && $day == 31) {
    $date = $date->modify('+4 day');
}

if ($month == 1 && $day == 1) {
    $date = $date->modify('+3 day');
}

if ($month == 1 && $day == 2) {
    $date = $date->modify('+2 day');
}

if ($month == 1 && $day == 3) {
    $date = $date->modify('+1 day');
}

A子

そのあとにもう一度「土日判定」が必要だよ

$week = $date->format('w');

//日曜日ならば1日進める
if ($week == 0) {
    $date->modify('+1 day');
}

//土曜日ならば2日進める
if ($week == 6) {
    $date->modify('+2 day');
}

C菜

そうでした~(苦笑)
では、次です~

4.2と3の条件に合致しない場合、「時間」が【0:00~17:59】の間にあるかどうかを判定する
  (これ以外の時間の場合、エラーを返す)

(年末年始以外の)平日の場合、時間判定を行いますね~

//エラーフラグ
$error_flag = false;

//時間判定
$hour = intval($date->format('G'));
if (in_array($hour, [18, 19, 20, 21, 22, 23])) {
    $error_flag = true;
}

B美

2と3の条件に合致しない場合」という条件指定を忘れてるわよ

A子

あー、2番や3番の項目で日付をずらしたときのために、下記のフラグを用意しよう

//平日フラグ(true…平日,false…土日または年末年始)
$weekday_flag = true;

んで、もしも土日や年末・年始の条件に合致した場合、「false」を代入するの
…ってわけで、時間判定はこうなるね

//平日ならば時間判定
if ($weekday_flag) {
    $hour = intval($date->format('G'));
    if (in_array($hour, [18, 19, 20, 21, 22, 23])) {
        $error_flag = true;
    }
}

B美

ちなみに動作テストなんだけど

$date = DateTime::now();

の箇所を

$date = new DateTime('2026-04-01 20:00:00');

…って感じで(一時的に)変えれば、任意の日時を検証できるからね
(上記の例は、2026年4月1日(水)の夜8時ってこと)

C菜

ここまでをまとめてみますね~

//ナンバーズ3を予想
public function num3Prediction()
{
    //エラーフラグ
    $error_flag = false;

    //平日フラグ(true…平日,false…土日または年末年始)
    $weekday_flag = true;

    //現在の日時を取得
    $date = DateTime::now();

    //曜日判定
    $week = $date->format('w');

    //日曜日ならば1日進める
    if ($week == 0) {
        $date = $date->modify('+1 day');
        $weekday_flag = false;
    }

    //土曜日ならば2日進める
    if ($week == 6) {
        $date = $date->modify('+2 day');
        $weekday_flag = false;
    }

    //年末年始判定
    $month = intval($date->format('n'));
    $day = intval($date->format('j'));

    if ($month == 12 && $day == 31) {
        $date = $date->modify('+4 day');
        $weekday_flag = false;
    }

    if ($month == 1 && $day == 1) {
        $date = $date->modify('+3 day');
        $weekday_flag = false;
    }

    if ($month == 1 && $day == 2) {
        $date = $date->modify('+2 day');
        $weekday_flag = false;
    }

    if ($month == 1 && $day == 3) {
        $date = $date->modify('+1 day');
        $weekday_flag = false;
    }

    //再度、曜日判定
    $week = $date->format('w');

    //日曜日ならば1日進める
    if ($week == 0) {
        $date = $date->modify('+1 day');
    }

    //土曜日ならば2日進める
    if ($week == 6) {
        $date = $date->modify('+2 day');
    }

    //平日ならば時間判定
    if ($weekday_flag) {
        $hour = intval($date->format('G'));
        if (in_array($hour, [18, 19, 20, 21, 22, 23])) {
            $error_flag = true;
        }
    }

    //最終年月日・曜日・六曜
    $year = intval($date->format('Y'));
    $month = intval($date->format('n'));
    $day = intval($date->format('j'));
    $seven = $this->SixSevenWeek::getSeven($year, $month, $day);
    $six = $this->SixSevenWeek::getSix($date->format('Y-m-d'));

    ・・・(予想処理)・・・

    if ($error_flag) {
        //ステータス(時間外エラー)
        $status = [
            'code' => 500,
            'message' => 'Access Denied'
        ];
        $result = null;
    } else {
        //ステータス(成功)
        $status = [
            'code' => 200,
            'message' => 'Success'
        ];

        //日付情報
        $lottery = [
            'lottery_date_str' => $year.'年'.$month.'月'.$day.'日',
            'lottery_week_str1' => $seven['week_str_long'],
            'lottery_week_str2' => $seven['week_str_short'],
            'lottery_rokuyo_str' => $six['rokuyo_str']
        ];

        //予想結果
        $prediction = [
            'num3' => null
        ];

        //日付情報と予想結果を結合
        $result = array_merge($lottery, $prediction);
    }

    //JSON化する前の連想配列(最終形態)
    $json = [
        'status' => $status,
        'result' => $result
    ];

    //JSONエンコードした結果を返す
    $this->autoRender = false;
    header("Content-Type: application/json");
    echo json_encode($json);
}

これで日付のテストができますよ~

A子

カレンダーを見ながら様々な年月日でテストしてみたけど、全く問題なかったよ

B美

それじゃ、最後の5番ね

5.その「年月日」を予想日ととらえ、最新のデータを4件取得してスコアを算出する

私のほうでやってみたわよ
(「$record」という配列にスコアを加算していくからね)

//予想処理
$record = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
if (!$error_flag) {
    //フラグ配列
    $pre1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; //1回前に出現しているか(出現した場合「1」)
    $pre2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; //2回前に出現しているか(出現した場合「1」)
    $pre3 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; //3回前に出現しているか(出現した場合「1」)

    //最新のデータを4件取得
    $numbers = $this->Numbers->find()->order(['lottery_time' => 'desc'])->limit(4);
    foreach ($numbers as $idx => $val) {
        $num3_place1 = $val->num3_place1;
        $num3_place10 = $val->num3_place10;
        $num3_place100 = $val->num3_place100;
        $num3_array = [$num3_place100, $num3_place10, $num3_place1];
        $num3_array = array_unique($num3_array); //重複排除

        //1回前
        if ($idx == 0) {
            foreach ($num3_array as $num) {
                $record[$num] += 3;
                $pre1[$num] = 1;
            }
        }

        //2回前
        if ($idx == 1) {
            foreach ($num3_array as $num) {
                $record[$num] += 3;
                $pre2[$num] = 1;
            }
        }

        //3回前
        if ($idx == 2) {
            foreach ($num3_array as $num) {
                if ($pre1[$num] == 1 && $pre2[$num] == 1) {
                    //1回前と2回前の両方に出現している場合、何もしない
                } elseif ($pre1[$num] == 1 || $pre2[$num] == 1) {
                    //1回前と2回前のどちらかに出現している場合、3回目にも出現していれば加点
                    $record[$num] += 2;
                    $pre3[$num] = 1;
                }
            }
        }

        //4回前
        if ($idx == 3) {
            foreach ($num3_array as $num) {
                if ($pre1[$num] == 0 && $pre2[$num] == 1 && $pre3[$num] == 0) {
                    //2回前だけに出現している場合、4回目を見る
                    $record[$num] += 1;
                }
            }
        }
    }

    ・・・(ここで最頻値による加点)・・・

}

C菜

最頻値による加点」については、別にprivateメソッドを作りましょう~

//ナンバーズ3における曜日と六曜の最頻値
//$type:1…曜日(七曜)ごと,2…六曜ごと
//$value:SixSevenWeekコンポーネントで求めた配列
//戻り値は最頻値の添字を配列で返す
private function num3_mode($type, $value)
{
    //二つのタイプの振り分け(データベース検索条件)
    switch ($type) {
        case 1:$where = "lottery_week_int={$value['week_int']}";
            break;
        case 2:$where = "lottery_rokuyo_int={$value['rokuyo_int']}";
            break;
    }

    $field1 = 'num3_place1';
    $field10 = 'num3_place10';
    $field100 = 'num3_place100';

    //numbersテーブルを検索(一の位)
    $connection = ConnectionManager::get('default');
    $sql =<<<EOF
        select {$field1},count(*) as cnt
          from numbers
          where {$where}
          group by {$field1}
          order by {$field1};
    EOF;
    $result = $connection->execute($sql)->fetchAll('assoc');
    $place1 = (new Collection($result))->extract('cnt')->toList();

    //numbersテーブルを検索(十の位)
    $sql =<<<EOF
        select {$field10},count(*) as cnt
          from numbers
          where {$where}
          group by {$field10}
          order by {$field10};
    EOF;
    $result = $connection->execute($sql)->fetchAll('assoc');
    $place10 = (new Collection($result))->extract('cnt')->toList();

    //numbersテーブルを検索(百の位)
    $sql =<<<EOF
        select {$field100},count(*) as cnt
          from numbers
          where {$where}
          group by {$field100}
          order by {$field100};
    EOF;
    $result = $connection->execute($sql)->fetchAll('assoc');
    $place100 = (new Collection($result))->extract('cnt')->toList();

    //全ての桁を合計する
    $result_array = [];
    for ($i = 0; $i < 10; $i++) {
        $result_array[$i] = $place1[$i] + $place10[$i] + $place100[$i];
    }

    //最頻値の添字を求める
    $max = max($result_array);
    $max_idxs = array_keys($result_array, $max, true);

    return $max_idxs;
}


A子

えっとー
…ということは、さっきのB美のコードの末尾に以下のコードを追加すれば良いのかな?

//最頻値で加点
$seven_array = $this->num3_mode(1, $seven);
foreach ($seven_array as $num) {
    $record[$num] += 1;
}
$six_array = $this->num3_mode(2, $six);
foreach ($six_array as $num) {
    $record[$num] += 1;
}

これでどうよ

B美

ふむ
A子もなかなかやるようになったわね(感心、感心…)

それじゃ、ここまでをテストするためにJSONの中に「$record」を入れてみましょう

//予想結果
$prediction = [
    'num3' => null
];

の箇所をこうするの

//予想結果
$prediction = [
    'num3' => $record
];

これで各数字のスコアが分かるってわけ







C菜

テスト用に「apitest03.html」という名前のHTMLファイルを作ってみました~


A子

さすがはC菜だね

んじゃ、ブラウザからアクセスしてみよう
ちなみに、アクセスした日は2026年4月12日の日曜日だったから、この結果は翌日の月曜日分ってことだね

C菜

4」と「9」のスコアが高いので、この二つを軸に予想すれば良いですね~

B美

あ、最新の4件分(先週の火・水・木・金)の当選番号は以下の通りよ

実施回当選番号
第6956回228
第6957回941
第6958回944
第6959回047

どうやらきちんとスコア計算ができてるみたいね

A子

あれ?
9」は5点じゃないの?

C菜

曜日(七曜)六曜による1点の加算のせいですね~
(おそらく「1」と「9」に1点ずつ入っていると推測できます~)

A子

なるほどなるほど

いやー
明日(4月13日)の抽選結果が楽しみだね

B美

最終的な予想(「490」とか「493」とか)までは到達できなかったけど、今回はここまでにしておきましょう
(ちょっと長くなっちゃったからね(苦笑))

あと、読者の皆さんのほうでも確認できるようにしたから、興味があればこのリンクをクリックしてみてね
(リンクをクリックすると別ウィンドウで開きます)