CakePHP5入門【WebAPI編⑲】ナンバーズ3を予想③
A子
ナンバーズ3の当選番号を予想して、大儲けしていくよ
B美
そんなにうまくいくわけがないじゃない…
C菜
それで『予想』の方針は決まってるんですか~?
A子
(【WebAPI編⑮】を参照)
あれでスコアと的中結果を比較・検討してみたら何となく法則が分かったんで、こういうアルゴリズムを考えてみたよ
C菜
A子
刮目せよ!
|
1.スコアが1以上である数字は、3個から6個のパターンがある
2.3個~4個の場合、的中数字は1個であることが多い 3.5個~6個の場合、的中数字は2個であることが多い 4.5個の場合のみ、下記のように予想する 5.5個の中から2個を選択する(5C2=10通り) 6.スコアゼロである残りの5個と組み合わせて、50通りの予想を作成する 7.その50通りについて、過去の出現時期を調べ、最も古いものから10通りを選出する |
これでどうよ(ドヤ顔)
あと、スコアは「引っ張り」のみで算出して、曜日や六曜の最頻値は考慮してないよ
(「VerificationController.php」がそうだったからね)
B美
悪くは無いと思うんだけど、4番ってどうなの?
予想できない日が多過ぎるかも…
C菜
| 6C2=15通りになるので、残り(スコアゼロ)の4個と組み合わせて60通り |
どうでしょうか~?
B美
| 6C2=15通りになるので、残り(スコアが1以上)の4個と組み合わせて60通り |
A子
まとめると、こんな感じかな
| パターン | スコア | 組み合わせ作成方法 | |
|---|---|---|---|
| 0である数字の個数 | 1以上である数字の個数 | ||
| A | 6 | 4 | 6C2×4=60通り |
| B | 5 | 5 | 5C2×5=50通り |
| C | 4 | 6 | 6C2×4=60通り |
B美
良いんじゃないかしら?
C菜
A子
(誰でも実行できるように…)
|
1.日時チェック(年末年始も考慮)
2.スコア計算は「連続加点版」アルゴリズムのみ(曜日・六曜加算は行わない) 3.スコアがゼロでない数字の個数が3個以下または7個以上の場合は終了 4.「TopController.php」のprivateメソッド「combination」を使って数字の組み合わせを作成 4-1.パターンA…スコアゼロの6個の数字から2個を作成 4-2.パターンB…スコア1以上の5個の数字から2個を作成 4-3.パターンC…スコア1以上の6個の数字から2個を作成 5.残りの数字と組み合わせる 5-1.パターンA…15個×4個(スコア1以上)=60個 5-2.パターンB…10個×5個(スコアゼロ)=50個 5-3.パターンC…15個×4個(スコアゼロ)=60個 6.候補数字の過去の出現時期を調査し、最も古いものから10通りを画面表示 7.PDFファイルとしての出力もサポート |
こんな感じでどうかな?
B美
(古いものを優先して選ぶ…って考え方)
はたして妥当なのかどうか…
C菜
1年間くらい実験データを取ってみて、妥当性を検証すれば良いんじゃないでしょうか~?
B美
あ、でもそれならコマンドクラスを作って、それをcron実行したほうが良いんじゃないかしら?
(毎日チェックするのが大変…というか、確認するのを忘れそう(苦笑))
A子
(コマンドクラスからも使えるように…)
C菜
それなら同じコンポーネント内に「permutation」メソッドも入れちゃいましょう~
A子
|
cd html/numapp[Enter]
bin/cake bake component PermutationAndCombination[Enter] |
これで「src/Controller/Component/PermutationAndCombinationComponent.php」ができるはず…
(【WebAPI編④】を参照)
C菜
あ、せっかくなので今回は「static」ではなく、インスタンス化が必要な形にしましょうよ~
A子
|
/**
* Combination 組み合わせ nCr (r ≦ n) * * @param array $arr 元となる配列 * @param int $r 1セットあたりの要素数 * @return null|array */ public 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; } |
上記(combination)は前に作ったやつとほとんど同じ(「private」を「public」に変更しただけ)なんだけど、順列(permutation)のほうは下記になるよ
|
/**
* Permutation 順列 nPr (r ≦ n) * * @param array $arr 並べる元となる配列 * @param int $r 並べる1列(1セット)あたりの要素数 * @return null|array */ public function permutation(array $arr, int $r): ?array { //数値添字配列にする $arr = array_values($arr); $n = count($arr); $result = [];//最終的に二次元配列にして返す //nPr の条件に一致していなければ null を返す if ($n < 1 || $n < $r) { return null; } if ($r === 1) { foreach ($arr as $item) { $result[] = [$item]; } } if ($r > 1) { //$itemが先頭になる順列を算出する foreach ($arr as $key => $item) { //$itemを除いた配列を作成 $newArr = array_filter($arr, function($k) use($key) { return $k !== $key; }, ARRAY_FILTER_USE_KEY); //再帰 $recursion = $this->permutation($newArr, $r - 1); foreach ($recursion as $one_set) { array_unshift($one_set, $item); $result[] = $one_set; } } } return $result; } |
C菜
あと、それに伴い、「initialize」メソッドと「combinationResult」メソッドの修正も~
A子
| $box_set = $this->PermutationAndCombination->combination($digits, 3); |
B美
忘れてたわね(笑)
A子
…んなことはいいから、次はコマンドクラスを作ろう
| bin/cake bake command num3box[Enter] |
これで「src/Command/Num3boxCommand.php」が出来上がるよ
C菜
PDFファイルとして出力するので、「mPDF」のuse文も必要です~
あと、DateTimeやConnectionManagerも~
|
use Cake\Controller\ComponentRegistry;
use App\Controller\Component\PermutationAndCombinationComponent; use Mpdf\Mpdf; use Mpdf\HTMLParserMode; use Mpdf\Config\ConfigVariables; use Mpdf\Config\FontVariables; use Cake\I18n\DateTime; use Cake\Datasource\ConnectionManager; |
…で、次は「initialize」メソッド(必要なフィールドも…)ですよ~
|
class Num3boxCommand extends Command
{ private $Numbers; private $PermutationAndCombination; public function initialize(): void { parent::initialize(); $this->Numbers = $this->fetchTable('Numbers'); $this->PermutationAndCombination = new PermutationAndCombinationComponent(new ComponentRegistry()); } |
テーブルのフェッチとコンポーネントのロードですね~
A子
B美
だって、cron実行するんだからね
(月~金の0時30分に実行する…って感じ)
C菜
シェルスクリプトを書いて、そちらで実行判断しても良いですけど~
A子
B美
とりあえずシェルスクリプト無しを前提に組んでみましょう
A子
んじゃ、とりあえず「ApiController」クラスの中の「num3Prediction」メソッドの中身を「src/Command/Num3boxCommand.php」の「execute」メソッドにコピーしてっと…
このあと、不要な部分を削ったりして修正していこう
C菜
|
//各数字のスコアを判定
$scoreMore = []; //スコア1以上 $scoreLess = []; //スコアゼロ foreach ($record as $idx => $val) { if ($val > 0) { array_push($scoreMore, $idx); } else { array_push($scoreLess, $idx); } } |
例えば、$recordの中身が[3,5,0,0,4,0,0,3,0,5]だった場合、$scoreMoreは[0,1,4,7,9]、$scoreLessは[2,3,5,6,8]になりますよ~
A子
|
//スコア有無の数で処理を切り分ける
$box_set = []; $scoreMoreCount = count($scoreMore); $scoreLessCount = count($scoreLess); if ($scoreMoreCount == 4 && $scoreLessCount == 6) { //パターンA $combi = $this->PermutationAndCombination->combination($scoreLess, 2); foreach ($combi as $row) { foreach ($scoreMore as $val) { array_push($box_set, [$row[0], $row[1], $val]); } } } elseif ($scoreMoreCount == 5 && $scoreLessCount == 5) { //パターンB $combi = $this->PermutationAndCombination->combination($scoreMore, 2); foreach ($combi as $row) { foreach ($scoreLess as $val) { array_push($box_set, [$row[0], $row[1], $val]); } } } elseif ($scoreMoreCount == 6 && $scoreLessCount == 4) { //パターンC $combi = $this->PermutationAndCombination->combination($scoreMore, 2); foreach ($combi as $row) { foreach ($scoreLess as $val) { array_push($box_set, [$row[0], $row[1], $val]); } } } |
これで$box_setには要素3個の一次元配列が配列要素として格納されるはず…
(いわゆる「配列の配列」だね)
B美
|
//ボックス値の組み立て
$box = []; if (count($box_set) > 0) { 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けたの文字列 } } |
A子
C菜
あ、もちろん「protected」は「private」に変更しますけど~
A子
|
//直近の出現時期と出現頻度をデータベース検索
$result = $this->doCombination($box); |
C菜
|
//フォントディレクトリ(デフォルト)
$defaultConfig = (new ConfigVariables())->getDefaults(); $fontDirs = $defaultConfig['fontDir']; //フォント(デフォルト) $defaultFontConfig = (new FontVariables())->getDefaults(); $fontData = $defaultFontConfig['fontdata']; //mPDFのインスタンス化 $mpdf = new Mpdf([ 'tempDir' => TMP.'mpdf', 'fontDir' => array_merge($fontDirs, [ROOT.DS.'fonts']), 'format' => 'A4', 'orientation' => 'P', 'fontdata' => $fontData + [ 'ipaexg' => [ 'R' => 'ipaexg.ttf' ], 'ipaexm' => [ 'R' => 'ipaexm.ttf' ], ], 'default_font' => 'ipaexg', ]); |
「PdfController」の「makepdf」メソッドから(一部を)コピーしただけですよ~
B美
それでは(多分)パーミッションエラーになるわよ
A子
なんでさ
以前のコードと全く同じはずだけど…
B美
でも、cronって誰の権限で動かすんだっけ?
C菜
ユーザ権限です~
(この開発環境では「bimi」ですね~)
B美
まぁ、「tmp/mpdf」のパーミッションを「757」にしても良いんだけど、セキュリティを考慮して別ディレクトリを作りましょう
|
cd html/numapp/tmp[Enter]
mkdir mpdf2[Enter] |
だから、Mpdfをインスタンス化する際は今作ったこっちを指定します
|
$mpdf = new Mpdf([
'tempDir' => TMP.'mpdf2', |
…ってことね
A子
んじゃ、あとはヘッダーとコンテンツを作ってPDF化するだけだね
C菜
|
//ヘッダー
$css =<<<EOF table { border-collapse: collapse; width: 80%; margin: 0 auto; font-size: 9px; } table th{ border:1px solid black; text-align: center; background-color: #afffaf; line-height: 0; } table td{ border:1px solid black; text-align: center; line-height: 0; } .top10{ background-color: yellow; } EOF; //コンテンツ $content = ''; if (count($result) == 0) { $content .=<<<EOF <div style="width: 100%;text-align: center;"> <h3>ボックス値(予想)</h3> 【{$year}年{$month}月{$day}日】 <br /><br /><br /> <span style="color: red;">スコアの状態が予想可能な範囲ではありません。</span> EOF; } else { $content .=<<<EOF <div style="width: 100%;text-align: center;"> <h3>ボックス値(予想)一覧</h3> 【{$year}年{$month}月{$day}日】 <br /> <table> <tbody> <tr> <th style="width: 50px;">順位</th> <th style="width: 100px;">ボックス値</th> <th style="width: 200px;">直近の出現時期</th> <th style="width: 100px;">直近の当選番号<br />(ストレート)</th> <th style="width: 100px;">このボックス値<br />の出現回数</th> </tr> EOF; foreach ($result as $idx => $val) { $rank = $idx + 1; if ($rank <=10) { $content .= "<tr><td class='top10'>{$rank}</td><td class='top10'>{$val[0]}</td><td class='top10'>{$val[1]}(第{$val[2]}回)</td><td class='top10'>{$val[3]}</td><td class='top10'>{$val[4]}回</td></tr>"; } else { $content .= "<tr><td>{$rank}</td><td>{$val[0]}</td><td>{$val[1]}(第{$val[2]}回)</td><td>{$val[3]}</td><td>{$val[4]}回</td></tr>"; } } $content .=<<<EOF </tbody> </table> </div> EOF; } |
(別ファイルとして)HTMLファイルを作れば(ブラウザ上で)表示確認できるのが良いですよね~
A子
|
//PDFへの書き出し
$mpdf->WriteHTML($css, HTMLParserMode::HEADER_CSS); $mpdf->WriteHTML($content, HTMLParserMode::HTML_BODY); |
B美
|
//格納ディレクトリ
$dir = WWW_ROOT."pdf".DS; if (!is_dir($dir)) { mkdir($dir, 0755, true); } //ファイル名の決定と保存 $fileName = $dir.'numbers3box_'.date('Ymd').'.pdf'; $mpdf->Output($fileName, 'F'); |
ファイル保存のやり方は超簡単!
「Output」メソッドの第一引数に「ファイル名」、第二引数に「F」を渡すだけ…
C菜
あと、「execute」メソッドの全体像をこの下に表示しておきましょう~
B美
(コード量がかなり多くなっちゃったけど…(苦笑))
それじゃ、実行確認してみましょうか
「MATE端末」を開いて、次のコマンドを打ち込んでみてね
|
cd html/numapp[Enter]
bin/cake num3box[Enter] |
A子
んじゃ、「numapp/webroot/pdf」ディレクトリを確認してみよう
↓
C菜
当たるかどうかは別として…(笑)
B美
このアルゴリズムでは、ダブルやトリプルが当選番号になったときって、100%はずれるからね
(「112」や「595」、「777」等)
A子
あくまでも三つの数字が全て異なることが前提だから、それは仕方ない…
C菜
(月~金の午前0時30分に実行しますよ~)
数ヶ月後の結果が楽しみです~
A子
あれ?
エラーかな?
B美
非推奨のメソッドを使ってるから、警告が出ただけよ
(「order」ではなく「orderBy」を使うべき…ってこと)
C菜
B美
ま、とにかくこの警告を止めたければ「config/app.php」のエラーレベルを書き換えるか、非推奨のメソッドを使うのをやめるかなんだけど…
面倒だからエラーレベルを書き換えましょう
(下記の赤線箇所を追加しただけ…)
A子
C菜
B美
面倒くさいのよ(苦笑)


