2022年3月31日木曜日

datetime の 曖昧なフォーマットに対応する忘備録

日付のフォーマットと制約が厳しい。
わりとありそうな日付の表記ゆれをパースするグラマーを構築してみた。
#include <boost/spirit/include/qi.hpp>  // boost::spirit::qi を利用します
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <iostream>
#include <string>


#include <boost/date_time.hpp>

namespace pt = boost::posix_time;
namespace qi = boost::spirit::qi;


struct timeinfo {
  int16_t   year_;
  uint16_t  month_;
  uint16_t  day_;
  uint16_t  hour_;
  uint16_t  minute_;
  uint16_t  second_;
  //uint32_t  millisec_;
  std::string millisec_;
};

BOOST_FUSION_ADAPT_STRUCT(
  timeinfo,
  (int16_t,     year_)
  (uint16_t,    month_)
  (uint16_t,    day_)
  (uint16_t,    hour_)
  (uint16_t,    minute_)
  (uint16_t,    second_)
  //(uint32_t,    millisec_)
  (std::string,  millisec_)
)

template <typename Iterator, typename Skipper>
struct time_grammar : qi::grammar<Iterator, timeinfo(), Skipper> {
  qi::rule<Iterator, timeinfo(), Skipper>  start_;
  qi::int_parser<int16_t,10,4,4> int4_;
  qi::uint_parser<uint16_t,10,2,2> uint2_;
  qi::rule<Iterator, std::string(), Skipper> numeric_;

  time_grammar() : time_grammar::base_type(start_, "time_grammar") {
    numeric_ = *qi::char_('0','9');
    start_ = (
        int4_ 
        >> -(qi::lit('/') | qi::lit('-')) >> uint2_
        >> -(qi::lit('/') | qi::lit('-')) >> uint2_
        //>> (qi::lexeme[qi::lit(' ')] | qi::lit('T'))
        >> -(qi::lit('T'))
        >> uint2_
        >> -(qi::lit(':')) >> uint2_
        >> -(qi::lit(':')) >> uint2_
        >> (
          //-( (qi::lit('.') | qi::lit(',')) >> qi::uint_)
          -( (qi::lit('.') | qi::lit(',')) >> numeric_)
          | qi::attr(0)
        )
        >> -(qi::lit('Z'))
    );
  }
};



int main() {
  
  std::string input[] = {
    "20210412T123455Z",
    "20210412T123455,987654321Z",
    "20210412T123455.987654321Z",
    "2004-03-21 12:45:33",
    "2004/03/21 12:45:33",
    "23.09.2004 04:12:21",
    "2003-02-11"
  };

  time_grammar<std::string::iterator, qi::standard::space_type> myg;
  for( int i = 0; i < sizeof(input)/sizeof(input[0]); i++ ) {
    timeinfo ti;
    qi::phrase_parse( input[i].begin(), input[i].end(), myg, qi::standard::space, ti );
    std::cout << "--------------" << std::endl;
    std::cout << ti.year_ << std::endl;
    std::cout << ti.month_ << std::endl;
    std::cout << ti.day_ << std::endl;
    std::cout << ti.hour_ << std::endl;
    std::cout << ti.minute_ << std::endl;
    std::cout << ti.second_ << std::endl;
    std::cout << ti.millisec_ << std::endl;
  }
  return 0;
}
2023/06/02 追記: lexeme がうまく動作しないのでスペースを無視した。millisec が ,020Z と指定された場合 2 に変換されてしまうので std::string で扱うようにした。バグでした。

2022年3月28日月曜日

boost::log windows で /dev/null 忘備録

boost::log でデバッグ用ログの埋め込みを行っていて、リリース時には取り除きたい場合の措置。
これだと、nul デバイスに対して出力する処理が入るので、負荷を取り除くという面では、よろしくないコード

ポイントは、
Windows では nul というファイルが /dev/null に相当する
nul というファイルは、どこにでも存在している扱いになるので、追加書き込みモードで開く必要がある点

#include <boost/date_time.hpp>

#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/support/date_time.hpp>

namespace logging = boost::log;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;
namespace sinks = boost::log::sinks;
namespace expr  = boost::log::expressions;

...

//#define DEBUG_LOG_FILE  "c:/logs/%Y_%m_%d.log"

...

  boost::log::add_file_log(
#ifdef DEBUG_LOG_FILE
    keywords::file_name = DEBUG_LOG_FILE,
#else
    keywords::file_name = "nul",
    keywords::open_mode = std::ios_base::out | std::ios_base::app,
#endif
    keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0),
    keywords::format = 
    (
        expr::stream
            << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
            << ": <" << logging::trivial::severity
            << "> " << expr::smessage
    ),
    keywords::auto_flush = true
  );
  BOOST_LOG_TRIVIAL(debug) << "this is a debug output.";

...

2022年1月24日月曜日

Vcpkg autolink を無効にする忘備録

vcpkg で、AutoLinkが有効になっている、明後日の方向のライブラリにリンクしてしまい、不可解なエラーに悩まされます。

なので、AutoLink を無効にしたい。

*.vcxprj を開きます。
PropertyGroup を検索し、以下の設定を追加します。
  <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='UnicodeRelease|x64'">
    <VcpkgAutoLink>false</VcpkgAutoLink>
  </PropertyGroup>
  <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='UnicodeRelease|Win32'">
    <VcpkgAutoLink>false</VcpkgAutoLink>
  </PropertyGroup>

UnicodeRelease|x64 等は、自分のビルド構成と合わせるようにしましょう。
以上。

vcpkg BuildTool を古いバージョンに変更備忘録

書いておかないと、忘れて調べ直す事になるので・・・
vcpkg でビルドをすると 最新バージョンのVisual Studio が使用されるようになって、それでは困る場合があるので、その設定の仕方。

vcpkg/triplets ディレクトリ下のファイルに
arm64-windows.cmake
arm-uwp.cmake
x64-linux.cmake
x64-osx.cmake
x64-uwp.cmake
x64-windows.cmake
x64-windows-static.cmake
x86-windows.cmake
とファイルが並んでいます。
その x64-windwos.cmake と x86-windows.cmake に対して
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE dynamic)
set(VCPKG_PLATFORM_TOOLSET "v140")
set(VCPKG_DEP_INFO_OVERRIDE_VARS "v140")
というように VCPKG_PLATFORM_TOOLSET, VCPKG_DEP_INFO_OVERRIDE_VARS を追加し、TOOLSET のバージョンを指定します。
以上。

なんか、一時的にブロガーのポストができなくなって、前のポストを一旦削除して、作り直しました。

C++データを16進表記相互変換

ちょくちょくデータを暗号化して16進数表記文字列に変換して保存し、保存された16進表記文字列をデータに復元する事をやるので、忘備録として記録する事に。こういう、ちょっとしたものでも、書こうと思ったら時間を取られる。
#include <string>
#include <iostream>

static int atob( int c ) {
  static const unsigned char btbl[] = {
    // 0     1     2     3     4     5     6     7     8     9     A     B     C     D     E     F
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  2
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05,  0x06, 0x07,  0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  3
    0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  0x0F, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  5
    0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  0x0F, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  6
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  7
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  8
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  9
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  A
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  B
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  C
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  D
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  E
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00   //  F
  };
  return btbl[ c ];
}


//! 16進文字列から unsigned char 配列への変換
/*
  @note 16進文字列は、0-9, A-F のみを利用した表記で
    1バイトを2文字で表記しているものとする。16進を示す 0x 等の
    接頭辞は含まれていない。
*/
void hexstr2str(
  const std::string& src,
  std::string& dst
) {
  dst.resize( (src.size() + 1) / 2 );
  for(size_t i = 0; i < src.size(); i++) {
    if( (i & 1) != 0 ) {
      dst[ i / 2 ] |= atob( src[i] );
    } else {
      dst[ i / 2 ] = atob( src[i] ) << 4;
    }
  }
}

//! unsigned char 配列から 16進文字列への変換
/*!
  @note 16進文字列は、0-9, A-F のみを利用した表記で
    1バイトを2文字で表記したものに変換される。16進を示す 0x 等の
    接頭辞は含まない。
*/
void str2hexstr(
  const std::string&  src,
  std::string&        dst
) {
  static const char atbl[] = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
  };
  dst.resize( src.size() * 2 );
  for( size_t i = 0; i < src.size(); i++ ) {
    dst[i*2  ] = atbl[ ((src[i] >> 4) & 0x0F) ]; // & 0x0F しないと算術シフトでアウトでした
    dst[i*2+1] = atbl[ (src[i] & 0x0F) ];
  }
}

int main() {
  std::string src = "Hello world!";
  std::string hex, dst;
  std::cout << src << std::endl;
  str2hexstr( src, hex );
  std::cout << hex << std::endl;
  hexstr2str( hex, dst );
  std::cout << dst << std::endl;
  return 0;
}

結果

Hello world!
48656C6C6F20776F726C6421
Hello world!
2022/01/14 追記: str2hexstr のコメントに書いたバグ修正をしました

2022年1月7日金曜日

JNI GetMethodID に指定するタイプ文字列 忘備録

JNI で Java method id を取得する GetMethodID 関数に渡す文字列のドキュメントが見つからないので、忘備録として記す事に

まず、Primitive Type の表

タイプ文字列Primitive Type
V void
Z boolean
B byte
C char
S short
I int
J long
F float
D double


クラスのタイプ文字列は、
 Ljava/lang/String;
というように L で始まり、class URIを書いて ; で閉じる

配列の場合は、
 [I
というように [ で始まり、タイプ文字列を書く
上記の場合 int [] に対応する

関数表記は
()V
([Ljava/lang/String;)I
というように、 ( 引数 ) 返り値 というように表記する
以上

2021年12月27日月曜日

D2メガテンの烙印の失敗がどの程度かモデルで検証してみた。

どうも、あまりにも失敗が目立って酷いので、コードを書いて検証してみることにした。

烙印のステータスアップの成功率 10% に対して40回以上失敗が連続するのは、どの程度で、最高何回ぐらい続くのか?
#include <iostream>
#include <random>
#include <array>
#include <algorithm>
#include <numeric>
#include <vector>

#define TRY_COUNT 10000

using seed_v_t = std::array<std::random_device::result_type, sizeof(std::mt19937)/sizeof(std::random_device::result_type)>;
seed_v_t create_seed_v() {
  std::random_device rnd;
  seed_v_t sed_v;
  std::generate(sed_v.begin(), sed_v.end(), std::ref(rnd));
  return sed_v;
}


std::mt19937 create_random_engine()
{
  const auto sed_v = create_seed_v();
  std::seed_seq seq(sed_v.begin(), sed_v.end());
  return std::mt19937(seq);
}

std::mt19937& random_engine()
{
  static thread_local std::mt19937 engine = create_random_engine();
  return engine;
}

int main() {
  std::mt19937& engine = random_engine();
  std::uniform_int_distribution<int> dist(0, 10);
  std::vector<int> cnts;
  int bingo = 0;
  int cnt = 0;
  for(int i = 0; i < TRY_COUNT; ++i) {
    int val = dist(engine);
    if( val != 0 ) ++cnt;
    else {
      cnts.push_back( cnt );
      cnt = 0;
    }
  }
  int acc = std::accumulate( cnts.begin(), cnts.end(), 0);
  std::cout << std::endl;
  std::cout << "成功回数         = " << cnts.size() << " / " << TRY_COUNT << std::endl;
  std::cout << "平均施行回数     = " << (acc / (double)cnts.size()) << std::endl;
  std::cout << "31回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 30); } ) << std::endl;
  std::cout << "41回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 40); } ) << std::endl;
  std::cout << "51回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 50); } ) << std::endl;
  std::cout << "61回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 60); } ) << std::endl;
  std::cout << "71回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 70); } ) << std::endl;
  std::cout << "最大連続失敗回数 = " << *std::max_element( cnts.begin(), cnts.end() ) << std::endl;
  std::cout << "連続40回以上の失敗に出会う確率 = " << 
    std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 40); } ) / (double)cnts.size() * 100.0 << "%" << std::endl;
}

結果は

成功回数         = 922 / 10000
平均施行回数     = 9.83406
31回以上連続失敗 = 50
41回以上連続失敗 = 20
51回以上連続失敗 = 8
61回以上連続失敗 = 3
71回以上連続失敗 = 0
最大連続失敗回数 = 70
連続41回以上の失敗に出会う確率 = 2.1692%

成功回数         = 883 / 10000
平均施行回数     = 10.3001
31回以上連続失敗 = 50
41回以上連続失敗 = 22
51回以上連続失敗 = 7
61回以上連続失敗 = 1
71回以上連続失敗 = 1
最大連続失敗回数 = 81
連続41回以上の失敗に出会う確率 = 2.49151%

成功回数         = 884 / 10000
平均施行回数     = 10.3032
31回以上連続失敗 = 60
41回以上連続失敗 = 27
51回以上連続失敗 = 11
61回以上連続失敗 = 5
71回以上連続失敗 = 2
最大連続失敗回数 = 84
連続41回以上の失敗に出会う確率 = 3.0543%


成功回数         = 881 / 10000
平均施行回数     = 10.3507
31回以上連続失敗 = 55
41回以上連続失敗 = 20
51回以上連続失敗 = 11
61回以上連続失敗 = 4
71回以上連続失敗 = 1
最大連続失敗回数 = 75
連続41回以上の失敗に出会う確率 = 2.27015%

成功回数         = 934 / 10000
平均施行回数     = 9.67024
31回以上連続失敗 = 46
41回以上連続失敗 = 20
51回以上連続失敗 = 5
61回以上連続失敗 = 0
71回以上連続失敗 = 0
最大連続失敗回数 = 57
連続41回以上の失敗に出会う確率 = 2.14133%

成功回数         = 947 / 10000
平均施行回数     = 9.55649
31回以上連続失敗 = 44
41回以上連続失敗 = 21
51回以上連続失敗 = 11
61回以上連続失敗 = 4
71回以上連続失敗 = 0
最大連続失敗回数 = 70
連続41回以上の失敗に出会う確率 = 2.21753%

ひどいケースは無くはないけど、プレイしていると、これよりは頻繁に40回以上連続失敗に遭遇していると思う。
また、思ったよりも連続失敗する事が多いのだなと思った。
しょっちゅう連続失敗でマッカが無くなるので、連続失敗の上限を設けてほしいです。
ユーザは、この仕様にかなりやる気を削がれていると思います

2022/1/10 追記: よくみたら、0-10じゃなくて、0-9で計算しないとあきませんやん。こうやって考えると d2の烙印の実装、バグっぽくありません?