TechFULの中の人

triple-four’s blog

C++個人的ハマりポイント紹介

はじめまして!TechFULで問題制作アルバイトをしているSiiiecです。
この記事ではC++でTechFUL問題を作成・解くときに自分がハマったところを紹介したいと思います。

TechFULでのC++について

この記事の作成時点にて、TechFULではコンパイラGCC5.3.1を使用しており、C++14が使用できます。

入力処理

std::cinの後にstd::getlineを呼ぶと入力を受け取れない

おそらく一度はハマる所。 スペースを含む文字列をN行読み取りたい、といったときに遭遇します。

ハマりコード

整数を読んだ後でスペースを含む文字列を読み込み、両方出力します。

#include <iostream>
#include <string>

int main()
{
    using namespace std;
    
    int n;
    string line;

    cin >> n;           // 整数を読み込み
    // cin.ignore();    // (1)
    getline(cin, line); // 一行(改行文字またはEOFに出会うまで)読み込み
    
    cout << n << endl;
    cout << line << endl;

    return 0;
}
入力
123
getline test
実行結果

Wandbox

123

一行目しか出力されていません。。。

なぜ?

以下のような処理が行われています。

  1. std::cinの読み取りでは、スペースまたは改行の手前まで読み込まれる。
  2. std::getlineでは改行の手前から読み込みを始めるため、一文字も読み込まれない。

残ってしまった改行文字を捨てる必要があり、ignoreを呼ぶことで文字を1文字読み込んで捨てることができます。
上のコードで(1)とコメントしている行をコメントアウトします。

コメントアウト後のコード

#include <iostream>
#include <string>

int main()
{
    using namespace std;
    
    int n;
    string line;

    cin >> n;
    cin.ignore();    // (1)
    getline(cin, line);
    
    cout << n << endl;
    cout << line << endl;

    return 0;
}

実行結果再び

Wandbox

123
getline test

期待通りの出力です。

乱数を扱う<random>

ここでは一様分布であるuniform_~_distributionについてのみ触れます。
以下の2つのものが用意されています。

  • 整数用:std::uniform_int_distribution
  • 実数用:std::uniform_real_distribution

std::uniform_int_distributionの範囲は閉区間

指定範囲の整数を等確率で発生させるstd::uniform_int_distributionですが、生成される値の範囲は区間[a, b](a以上b以下)となります。

ハマりコード

10個の乱数を出力します。

#include <iostream>
#include <random>

int main()
{
    using namespace std;

    mt19937 mt(444);                        // 結果を固定するためシード値は定数を使用
    uniform_int_distribution<> dist{0, 5};  // 範囲に0以上5以下を指定
    
    for (int i = 0; i < 10; ++i)
        cout << dist(mt) << " ";
        
    return 0;
}
実行結果

処理系によって異なります。
Wandbox

5 4 4 1 3 3 4 5 0 1 

0以上5以下の値が生成されています。
自分は範囲がa以上b未満だと勘違いし添字アクセスをしたことがありました。

std::uniform_real_distributionの範囲は半開区間

整数版とは異なり、std::uniform_real_distributionの範囲は半開区間[a, b)(a以上b未満)となります。

テストケースの生成やゲームでのパラメータなど乱数を使う場面は多いですが、範囲の境界でハマることがあります。

正規表現を扱う<regex>

TechFULの問題を解く場合使うことは少ないのですが、正規表現(regular expression)でのエラーです。

-エスケープされない

ハマりコード

文字列がa-0のどれかに一致しているか判定したいコードです。

#include <iostream>
#include <string>
#include <regex>

int main()
{
    using namespace std;
    try
    {
        vector<string> texts = { "a", "0", "-", "noMatch" };
        regex re{ R"([a\-0])" };    // ここで例外発生
        for (const auto& t : texts)
        {
            cout << t << " : ";
            if (regex_match(t, re)) // 正規表現にマッチしているか
                cout << "true" << endl;
            else
                cout << "false" << endl;
        }
    }
    catch (regex_error& e)
    {
        cout << "caught exception!" << endl;
        cout << e.what() << endl;
    }
    return 0;
}
実行結果

コンパイラのバージョンにより内容は変わります。
WandBox(GCC5.4.0)
WandBox(GCC6.3.0)

caught exception!
regex_error

例外発生してます。。。

なぜ?

おそらく[a\-0]内で-エスケープが処理されず[a-0]と解釈されています。このためa~0という不正な範囲となり例外が発生しています。
対処としては、[\-a0][a0\-]と書き換えることで範囲と解釈されないようにします。

これは記事作成時点のTechFULで使用しているGCC5.3.1にて起きる挙動であり、GCC7.1.0からは正しくエスケープされているようです。

GCC7.1.0での実行結果

Wandbox(GCC7.1.0)

a : true
0 : true
- : true
noMatch : false

終わりに

ここまで読んでいただきありがとうございます。個人的ハマりポイントなので参考にならない所あったかもしれません。(特にregex)
皆さんのコーディングの役に立てば幸いです。

Siiiecでした!