やらなきゃいけないこと
前回からの続き。
C# .NetCoreで、ボートレース結果の解析 構造検討
https://qiita.com/TamanegiTarao/items/04f43d96e50cb3eeaa03
以下の情報取得を行えるようにする。
- 節間タイトル
- レースタイトル
- レース情報
- 選手結果
開発環境
- Windows10
- Visual Studio Community 2019
- C# .Net Core 3.1
リスト
public class RaceResults
{
public List<InternodeTitle> InternodeTitles;
public List<RaceTitle> RaceTitles;
public List<RaceResult> Results;
public List<PlayerResult> PlayerResults;
}
InternodeTitles
、RaceTitles
はそれぞれ、節間、レースタイトルのリストとして登録する。
Results
はレース結果(各配当、人気)をリストとして登録する。
(解析時、配当オンリーの際はこちらのほうが、負荷が少ないため。)
PlayerResult
はInternodeTitles
,ReceTitles
は各リストに紐づけるようにする。
Shift-JISのエンコード
.NetCoreの場合 Shift-JISのEncodingを使用するには
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
をEncoding前までに宣言しておく必要がある。System.Text
をusingしておくこと。
(https://qiita.com/sugasaki/items/0639ea9ca07f1ba7a9e0)
List重複対策
上記の通り、節間タイトル、レースタイトルはリストで管理するため、
読み込んだ文字列がリスト内にあるか確認する必要がある。
if( List.Exist(x => x.SearchParameter == ReadParameter))
{
/* 存在する際の処理 */
}
else
{
/* 存在しない際の処理 */
}
読み込みの流れ
前回の例にとってデータの取得する位置を決める。
STARTK ←無視
24KBGN ←1.開催場コードを取得
大 村[成績] 10/ 1 第6回夜の本命・波乱 第 5日 ←無視
←無視
*** 競走成績 ***←無視
←無視
第6回夜の本命・波乱決定戦 ←2.節間タイトルを取得
←無視
第 5日 2019/10/ 1 ボートレース大 村←3.節間日、開催年月日の取得
←ここから無視
-内容については主催者発行のものと照合して下さい-
[払戻金] 3連単 3連複 2連単 2連複
1R 3-1-2 3360 1-2-3 260 3-1 1560 1-3 290
...略...
12R 1-4-6 2010 1-4-6 810 1-4 530 1-4 390
←ここまで無視
1R 一 般 H1800m 曇り 風 北西 1m 波 1cm←4.レース番号、レースタイトル、風向、風速の取得
着 艇 登番 選 手 名 モーター ボート 展示 進入 スタートタイミンク レースタイム 差し ←5.決まり手の取得
-------------------------------------------------------------------------------
01 3 2841 吉 田 稔 70 75 6.82 3 0.14 1.50.6←6.選手結果の取得
...略...
06 5 3856 水 野 暁 広 43 21 6.79 5 0.24 . . ←6.選手結果の取得
単勝 3 2150 ←7.ここからレース結果の取得
複勝 3 170 1 120
2連単 3-1 1560 人気 6
2連複 1-3 290 人気 2
拡連複 1-3 170 人気 3
2-3 140 人気 2
1-2 120 人気 1
3連単 3-1-2 3360 人気 12
3連複 1-2-3 260 人気 1 ←7.ここまでレース結果の取得
2~12Rは省略←4~7を繰り返す。
24KEND
22KBGN←1~7を繰り返す
...略
05KEND
FINALK
1~3の開催場や節間タイトルなどの全体的な情報と、4~7のレース毎の情報に分けることができる。
1~3の間と4~7の間は、それぞれ、行数で管理することが望ましそう。
欠場の場合はどのような記載になるかは動かしてから考える。
コーディング
文字列は基本的に使いたくない。
たくさんのデータを拾いたいのでなるべく文字列で保持したくない。
レースタイトル等は仕方ないが、複数の要素しかないも文字列は数値で管理したいため、
以下の通り、Enumやintへの変換を行った
- 風向き
public enum WindDirection { None, N, NE, E, ES, S, SW, W, WN };
public static WindDirection WindDirectionNumber(string str)
{
switch (str)
{
case "無風 ": return WindDirection.None;
case "北 ": return WindDirection.N;
case "北東 ": return WindDirection.NE;
case "東 ": return WindDirection.E;
case "南東 ": return WindDirection.ES;
case "南 ": return WindDirection.S;
case "南西 ": return WindDirection.SW;
case "西 ": return WindDirection.W;
case "北西 ": return WindDirection.WN;
default: return WindDirection.None;
}
}
3文字固定で検索を行うようにした。
- 決まり手
public enum Kimarite { Nige, Makuri, Sashi, Makurisashi, Megumare, Nuki, Huseiritsu }
public static Kimarite KimariteNumber(string str)
{
switch (str)
{
case "逃げ ": return Kimarite.Nige;
case "差し ": return Kimarite.Sashi;
case "まくり ": return Kimarite.Makuri;
case "まくり差し": return Kimarite.Makurisashi;
case "抜き ": return Kimarite.Nuki;
case "恵まれ ": return Kimarite.Megumare;
case " ": return Kimarite.Huseiritsu;
default: return Kimarite.Nige;
}
}
- 着順のフライング、失格、欠場
フライングや欠場の場合は数値ではなく特定の英数字になる。
単純にParseではフライングなのか欠場なのか判断できないため、
以下の通り、Parse失敗時は着順がつかなかった理由を判別できるようにした。
public static int ResultNumber(string str)
{
string[] resultList =
{
"F ","L ","K0","K1",
"S0","S1","S2"
};
int rtn;
if (int.TryParse(str, out rtn) == false)
{
for (int i = 0; i < resultList.Length; i++)
{
if (str == resultList[i])
{
return i * (-1);
}
}
return resultList.Length * (-1);
}
return rtn;
}
文字列から数値への変換
Parse
は変換ができないと例外が発生するため、TryParse
を使用し、
汎用の関数を用意した。
static public int IntParse(string str)
{
int rtn = 0;
if(!int.TryParse(str,out rtn))
{
rtn = 0;
}
return rtn;
}
static public double DoubleParse(string str)
{
double rtn = 0.0;
if (!double.TryParse(str, out rtn))
{
rtn = 0;
}
return rtn;
}
実際にリードして読み込む
今までの内容を踏まえてリード部分を作成した。
public void Read(string file)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
using (var sr = new StreamReader(file, Encoding.GetEncoding("shift_jis")))
{
InternodeTitle internodeTitle = new InternodeTitle();
RaceTitle raceTitle;
RaceInfo raceInfo = new RaceInfo();
RaceResult raceResult = new RaceResult();
Race raceBase = new Race();
Race race;
int inLineInfo = -9999;
int inLinePlayer = -9999;
int place = 0;
bool entryfirst = false;
bool nextKakurenSecond = false;
bool nextKakurenThired = false;
while (sr.EndOfStream == false)
{
var line = sr.ReadLine();
if (line.Contains("KBGN"))
{ // 1.開催場コードの取得
place = Lib.IntParse(line.Replace("KBGN", ""));
inLineInfo = 0;
}
else if (inLineInfo == 5) // 文字列を含むかどうかで拾うのがむつかしい
{ // 2.節間タイトルを取得
// https://csharp-ref.com/program_linq-lambda.html
string title = _StringExtraction(line, 10, 50);
if (InternodeTitles.Exists(x => x.Title == title))
{
internodeTitle = InternodeTitles.OrderBy(x => x.Start).FirstOrDefault();
entryfirst = false;
}
else
{
internodeTitle = new InternodeTitle();
internodeTitle.Title = title;
entryfirst = true;
InternodeTitles.Add(internodeTitle);
}
}
else if (inLineInfo == 7)// 文字列を含むかどうかで拾うのがむつかしい
{ // 3.節間日、開催年月日の取得
raceBase.Place = place;
raceBase.InDay = Lib.IntParse(_StringExtraction(line, 5, 2));
DateTime dateTime = new DateTime(Lib.IntParse(_StringExtraction(line, 19, 4)),
Lib.IntParse(_StringExtraction(line, 24, 2)),
Lib.IntParse(_StringExtraction(line, 27, 2)));
raceBase.Date = dateTime;
if (entryfirst)
internodeTitle.Start = dateTime;
internodeTitle.End = dateTime;
}
else if (line.Contains("H1800m"))
{ // 4.レース番号、レースタイトル、風向、風速の取得
race = raceBase.Clone();
race.RaceNum = Lib.IntParse(_StringExtraction(line, 2, 2));
// レースタイトル
var title = _StringExtraction(line, 12, 12);
if (RaceTitles.Exists(x => x.Title == title))
{
raceTitle = RaceTitles.Where(x => x.Title == title).FirstOrDefault();
}
else
{
raceTitle = new RaceTitle() { Title = title };
RaceTitles.Add(raceTitle);
}
// レース情報
raceInfo = new RaceInfo();
raceResult = new RaceResult();
raceInfo.WindDirectionSet(_StringExtraction(line, 59, 6));
raceInfo.WindMetor = Lib.IntParse(_StringExtraction(line, 65, 2));
raceInfo.RaceTitle = raceTitle;
raceInfo.Race = race;
raceInfo.RaceResult = raceResult;
raceInfo.InternodeTitle = internodeTitle;
}
else if (line.Contains(" 着 艇 登番 選 手 名 モーター ボート 展示 進入 スタートタイミンク レースタイム"))
{ // 5.決まり手の取得
raceInfo.KimariteSet(_StringExtraction(line, 66, 10));
inLinePlayer = -1; // この次の行が --------------だから
}
else if (1 <= inLinePlayer && inLinePlayer <= 6)// 文字列を含むかどうかで拾うのがむつかしい
{ // 6.選手結果の取得
PlayerResult adder = new PlayerResult();
adder.RaceInfo = raceInfo;
adder.Number = Lib.IntParse(_StringExtraction(line, 8, 4));
adder.Result = PlayerResult.ResultNumber(_StringExtraction(line, 2, 3));
adder.Wakuban = Lib.IntParse(_StringExtraction(line, 6, 1));
adder.Cource = Lib.IntParse(_StringExtraction(line, 46, 1));
adder.Motor = Lib.IntParse(_StringExtraction(line, 30, 2));
adder.Boat = Lib.IntParse(_StringExtraction(line, 34, 3));
adder.Tenji = Lib.DoubleParse(_StringExtraction(line, 39, 4));
adder.Start = Lib.DoubleParse(_StringExtraction(line, 51, 4));
PlayerResults.Add(adder);
}
else if (6 < inLinePlayer && !line.Contains("不成立"))
{ // ←7.レース結果の取得
if (line.Contains("単勝"))
{ // ここの特払い、傾向つかめれば統一したい。
if (line.Contains("特払い"))
{
raceResult.Tansho.First = -1;
raceResult.Tansho.Yen = Lib.IntParse(_StringExtraction(line, 27, 4));
}
else
{
raceResult.Tansho.First = Lib.IntParse(_StringExtraction(line, 17, 1));
raceResult.Tansho.Yen = Lib.IntParse(_StringExtraction(line, 25, 6));
}
}
if (line.Contains("複勝"))
{
raceResult.Fukusho1.First = Lib.IntParse(_StringExtraction(line, 17, 1));
raceResult.Fukusho1.Yen = Lib.IntParse(_StringExtraction(line, 25, 6));
raceResult.Fukusho2.First = Lib.IntParse(_StringExtraction(line, 33, 1));
raceResult.Fukusho2.Yen = Lib.IntParse(_StringExtraction(line, 41, 6));
}
if (line.Contains("2連単") || line.Contains("2連複") || line.Contains("各連複") ||
nextKakurenSecond || nextKakurenThired)
{
ResultTwo res = line.Contains("2連単") ? raceResult.NirenTan :
line.Contains("2連複") ? raceResult.NirenFuku :
line.Contains("各連複") ? raceResult.Kakuren1 :
nextKakurenSecond ? raceResult.Kakuren2 : raceResult.Kakuren3;
res.First = Lib.IntParse(_StringExtraction(line, 17, 1));
res.Second = Lib.IntParse(_StringExtraction(line, 19, 1));
res.Yen = Lib.IntParse(_StringExtraction(line, 25, 6));
res.Popler = Lib.IntParse(_StringExtraction(line, 40, 3));
// 各連複の2.3番目は取れないので
nextKakurenSecond = res == raceResult.Kakuren1 ? true : false;
nextKakurenThired = res == raceResult.Kakuren2 ? true : false;
}
if (line.Contains("3連単") || line.Contains("3連複"))
{
ResultThree res = line.Contains("3連単") ? raceResult.SanrenTan : raceResult.SanrenFuku;
res.First = Lib.IntParse(_StringExtraction(line, 17, 1));
res.Second = Lib.IntParse(_StringExtraction(line, 19, 1));
res.Thired = Lib.IntParse(_StringExtraction(line, 21, 1));
res.Yen = Lib.IntParse(_StringExtraction(line, 25, 6));
res.Popler = Lib.IntParse(_StringExtraction(line, 40, 3));
if (res == raceResult.SanrenFuku)
inLinePlayer = -9999;
}
}
inLinePlayer++;
inLineInfo++;
}
}
}
思ったより見にくい。保守するなら工夫が必要かも。
結果
例のファイルを読み込んで、結果を出力するようにしてみた。
結果を一部抜粋。
第6回夜の本命・波乱決定戦 ,一 般 ,24,2019,10,01,5,1,0,2150,3,0,170,3,0,120,1,6,1560,3,1,2,290,1,3,0,0,0,0,0,0,0,0,0,0,0,0,12,3360,3,1,2,1,260,1,2,3,2,8,1,2841,1,3,3,70,75,6.82,0.14
第13回多摩川蛭子カップ外向発売所開設2周年記念 ,蛭子選抜戦 ,5,2019,10,01,1,12,0,430,2,0,130,2,0,470,3,13,3480,2,3,9,2990,2,3,0,0,0,0,0,0,0,0,0,0,0,0,50,14740,2,3,4,11,2640,2,3,4,2,4,2,3909,1,2,2,49,167,6.72,0.19
コード
作成したコードをGithubに上げました。
https://github.com/TamanegiTarao/BoatRaceResultReader/tree/master
まとめ
ようやく、結果の読み込みが行えるようになった。
多くのファイルを読み込んでくと、排他処理追加する必要が出てきそうな感じがする。
今後は、細かい処理のと、スタート展示の情報が競争成績のファイルには存在していないため、
スタート展示のスクレイピングについてもやっていきたいと思う。
次回
C# .NetCoreで、ボートレース結果の解析 読み込みその2
https://qiita.com/TamanegiTarao/items/ca2d15ed550d87da6ed2