0
0

More than 3 years have passed since last update.

C# .NetCoreで、ボートレース結果の解析 読み込みその1

Last updated at Posted at 2020-10-27

やらなきゃいけないこと

前回からの続き。
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;
    }

InternodeTitlesRaceTitlesはそれぞれ、節間、レースタイトルのリストとして登録する。
Resultsはレース結果(各配当、人気)をリストとして登録する。
(解析時、配当オンリーの際はこちらのほうが、負荷が少ないため。)
PlayerResultInternodeTitles,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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0