メモの日々


2025年02月20日(木) [長年日記]

[c++] C++の正規表現メモ

<regex>の使い方をメモ。

正規表現オブジェクトとそれを使った文字列に対するマッチ処理について以下をメモする。

  • std::basic_regex<CharT> (正規表現)
  • std::regex_match() (全体マッチ判定)
  • std::regex_search() (部分マッチ判定)
  • std::regex_iterator<Iterator> (部分マッチ結果の列挙)
  • std::regex_token_iterator<Iterator> (部分マッチ結果のサブマッチの列挙)

これら以外に文字列の置換機能もあるがそれについては割愛する。

std::basic_regex<CharT> (正規表現)

正規表現はstd::basic_regexクラステンプレートを使って表現する。

std::regex re{R"(abc (\d\d).(\d\d))"};
  • std::regex は std::basic_regex<char> の別名であり、通常はこれを使うだろう。
  • 「R"(...)"」部分は生文字リテラルであり、正規表現を書く際にはこの記法が役に立つ。
  • 使える正規表現はデフォルトではECMAScript互換。コンストラクタで構文を切り替えることもできる。
  • ひらがななどの非ASCII文字のサポートは期待できない模様(よくわかっていない)。

std::regex_match() (全体マッチ判定)

std::regex_match()を使うことで、文字列全体が正規表現にマッチするかどうかを判定できる。

void regex_match1()
{
    static const std::regex re{R"(\w+ \d+/\d+/\d+)"};
    const bool is_matched1 = std::regex_match("hello 2025/01/02", re);
    const bool is_matched2 = std::regex_match("hello world 2025/01/02", re);

    std::cout << "regex_match1" << std::endl;
    std::cout << is_matched1 << std::endl; //=> 1
    std::cout << is_matched2 << std::endl; //=> 0
}
  • 文字列の一部分ではなく全体がマッチする必要があることに注意。

regex_match()の引数にstd::match_resultsオブジェクトを指定すると、正規表現で指定したキャプチャグループ部分にマッチした内容を参照することができる。

void regex_match2()
{
    static const std::regex re{R"(\w+ (\d+)/(\d+)/(\d+))"};
    std::cmatch m;
    const bool is_matched = std::regex_match("hello 2025/01/02", m, re);

    std::cout << "regex_match2" << std::endl;
    std::cout << is_matched << std::endl; //=> 1
    std::cout << m[0] << std::endl;       //=> hello 2025/01/02
    std::cout << m[1] << std::endl;       //=> 2025
    std::cout << m[2] << std::endl;       //=> 01
    std::cout << m[3] << std::endl;       //=> 02
}
  • std::cmatch は std::match_results<const char*> の別名。
    • std::match_resultsクラステンプレートにはこのようなエイリアスがいくつか定義されている。が、std::string_viewを使う場合は std::match_results<std::string_view::const_iterator> などとしないといけないようだ。
  • 上記の m[0] などで得られるのは単なる文字列ではなくstd::sub_matchオブジェクト。std::match_results は std::sub_match を要素に持つコンテナだと考えることができる。

std::regex_search() (部分マッチ判定)

std::regex_search()を使うことで、文字列が正規表現にマッチする部分を持つかどうかを判定できる。

void regex_search()
{
    static const std::regex re{R"(\w+ (\d+)/(\d+)/(\d+))"};
    std::cmatch m;
    const bool is_matched = std::regex_search("hello world 2025/01/02", m, re);

    std::cout << "regex_search" << std::endl;
    std::cout << is_matched << std::endl; //=> 1
    std::cout << m[0] << std::endl;       //=> world 2025/01/02
    std::cout << m[1] << std::endl;       //=> 2025
    std::cout << m[2] << std::endl;       //=> 01
    std::cout << m[3] << std::endl;       //=> 02
}

std::regex_iterator<Iterator> (部分マッチ結果の列挙)

std::regex_iteratorクラステンプレートを使うと、std::regex_search()を繰り返し呼び出し、それぞれの呼び出しに対するマッチ結果(std::match_results オブジェクト)を順番に得ることができる。

void regex_iterator()
{
    static const std::regex re{R"(\w+ (\d+)/(\d+)/(\d+))"};
    const std::string s = "hello world 2025/01/02 japan 1/2/3/4/5/6/7/8/9";

    std::cout << "regex_iterator" << std::endl;
    for (std::sregex_iterator it{s.begin(), s.end(), re}, end;
        it != end;
        ++it)
    {
        std::cout << (*it)[0] << std::endl;
        std::cout << (*it)[1] << std::endl;
        std::cout << (*it)[2] << std::endl;
        std::cout << (*it)[3] << std::endl;
        std::cout << "---" << std::endl;
    }
}
regex_iterator
world 2025/01/02
2025
01
02
---
japan 1/2/3
1
2
3
---
  • sregex_iterator は regex_iterator<string::const_iterator> の別名。

std::regex_token_iterator<Iterator> (部分マッチ結果のサブマッチの列挙)

std::regex_token_iteratorクラステンプレートは regex_iterator に似ているが、マッチ結果ではなくサブマッチ(std::sub_match オブジェクト)の内容を直接得られるところが違う。

void regex_token_iterator()
{
    static const std::regex re{R"(\w+ (\d+)/(\d+)/(\d+))"};
    const std::string s = "hello world 2025/01/02 japan 1/2/3/4/5/6/7/8/9";

    std::cout << "regex_token_iterator" << std::endl;
    for (std::sregex_token_iterator it{s.begin(), s.end(), re, {1, 3}}, end;
        it != end;
        ++it)
    {
        std::cout << *it << std::endl;
    }
}
regex_token_iterator
2025
02
1
3
  • sregex_token_iterator は regex_token_iterator<string::const_iterator> の別名。
  • コンストラクタの4番目の引数で、何番目のサブマッチを列挙するかを指定する。ここに -1 を指定することでマッチしなかった部分を列挙することもできる。

2025年01月16日(木) [長年日記]

[windows][python] WindowsでのデフォルトのPython環境

Windows 11で使用するPythonはMicrosoft Storeからインストールするようにしている。

Microsoft StoreでのPythonはバージョン毎に別のアプリとして公開されているが、複数のバージョンをインストールしたときに使われるPythonをどう制御すればいいのか。

Windows上でのデフォルトのPython環境

コマンドプロンプト上などでpythonコマンドを実行したときに使われるバージョンは、次で表示される設定画面から制御できるようだ。

  • [設定]→[アプリ]→[アプリの詳細設定]→[アプリ実行エイリアス]

この画面の一覧の中で、デフォルトで使用したいバージョンのPythonの設定をすべて「オン」にすることで、そのバージョンがデフォルトで使われるようになる(コマンド毎にオンにするバージョンを変えることもできるが、それは混乱の元にしかならないだろう)。

アプリ実行エイリアス

Visual Studio上でのデフォルトのPython環境

Visual Studio 2022でPythonプロジェクトを作成したとき、プロジェクトで明示的にバージョン指定をしないとPython環境として「グローバル デフォルト」と表示されるものが使われるようだ。そして、これは上述したWindows上でのデフォルト環境を変更してもそれに追従しなかった。

Visual Studio上のグローバルデフォルトは、次で表示されるPython環境ウィンドウで制御できるようだ。

  • [ツール]→[Python]→[Python環境]

この画面の環境の一覧にて太字で表示されているものがグローバルデフォルトな模様。

これを変更するには、一覧で非グローバルデフォルト環境を選択し、「概要」欄の「これを新しいプロジェクトに対する既定の環境にする」をクリックすればよい。その後でPythonプロジェクトを開き直すと、Python環境として表示されるPythonのバージョンが変わった。

Python環境ウィンドウ


2024年11月26日(火) [長年日記]

[android][net] AndroidのBLEを使った通信で512バイトを超える送信データが破棄される

AndroidのBluetooth APIを使ったBLE通信で、送信データが破棄されれ送信されないことがあったので調べた。

原因はAndroid 13と14での動作の変更だった。

Android 13での変更

ちゃんとした資料は見つけられなかったが、Android 13から512バイトを超えるデータを送受信できなくなったというissueがちらほら見つかる。これなど。

次の変更が原因のようだ。

この変更は、次に示すBluetoothの仕様でattribute valueの最大長が512バイトに制限されていることへ準じるために行われている。

The maximum length of an attribute value shall be 512 octets.

どうも、Bluetoothでデータ送信を行う際のデータ長の上限としてATT(Attribute Protocol)のMTUサイズから3バイト引いた値を使うコードが多いようで(手元のコードもそうなっていた)、AndroidのATT MTUサイズは最大で517バイトになり得て、そうなったときに 517 - 3 = 515 バイトのデータを送信しようとしてしまい、その結果Android 12までであれば送信できていたのにAndroid 13になったら送信できなくなったという問題があるようだ。

Android 14での変更

Android 14では次の変更がされたとある。

これは、BluetoothGatt#requestMtu()によりMTUサイズの拡張を要求したときに引数の値によらず常に517バイトが使われるということのようだ。

手元のコードでは、上述したAndroid 13での変更に対する対策として BluetoothGatt#requestMtu() で515を使うようになっていたが、この対策はAndroid 14以降では意味がなくなってしまうことになる。

変更への対応方法

送信データの長さの上限を (MTUサイズ - 3) とするだけでは問題があるということになる。

送信データの長さの上限は (min(512, MTUサイズ - 3)) とする必要がある。

AndroidのATT MTUサイズの上限が517バイトなのは何故か

検索すると、AndroidのATT MTUサイズの上限は517バイトではなく515バイトであるべきだと書いている人を見かける。これに対しては、次のissueにGoogleの人による説明があった。

I would like to provide you more details about why 517 was chosen instead of 515:

The number 517 was selected because the GATT_MAX_MTU_SIZE constant was set to 517 since the initial drop of Android Bluetooths stack in 2012.

A deeper reason is that the Bluetooth Specification allows the maximum size of an ATT attribute to be 512 bytes and the largest command ATT_PREPARE_WRITE_REQ has 5 bytes of header. Hence 512 + 5 = 517.


2024年10月08日(火) [長年日記]

[python] SQLModelのdatetimeに対する動作

どうしてそうなるのかは理解できていないが、事実をメモ。

SQLModel0.0.22を使っている。

datetime型のフィールドを持つモデルを定義して、そのmodel_validate_json()によりJSONからのでシリアライズを行ったときに次のようになる。

  • table=True無しで定義したモデルはdatetime型のフィールドにdatetime型の値が格納される
  • table=True有りで定義したモデルはdatetime型のフィールドにstr型の値が格納されてしまう

検証コードと実行結果を示す。

from datetime import datetime

from sqlmodel import Field, SQLModel


class A(SQLModel):
    id: int = Field(primary_key=True)
    dt: datetime


class B(SQLModel, table=True):
    id: int = Field(primary_key=True)
    dt: datetime


json = '{ "id": 1, "dt": "2024-01-02T03:45:06+09:00" }'
a = A.model_validate_json(json)
b = B.model_validate_json(json)
print(type(a.dt))
print(type(b.dt))
<class 'datetime.datetime'>
<class 'str'>

(追記)

上記のようになるのは、データベースとして使用していたのがSQLiteで、SQLiteのdatetime型はtext型と同じだからのようだった。


2024年10月01日(火) [長年日記]

[windows][net][howto] WSLにポートフォワードで外部からアクセス可能にする

WSLに外部からSSHでアクセスできるようにする方法をメモ。なお、試した環境はWindows 11ではなくWindows 10。

WSL上でsshdを使えるようにする方法は割愛。WSLをホストしているWindowsからはWSLへSSH接続できるものとする。

ポートフォワーディング設定

Windowsホスト上でnetsh interface portproxyコマンドを使ってWSLへのポートフォワーディングを設定する。なお、この設定には管理者権限が必要。

> netsh interface portproxy add v4tov4 `
    listenport=22222 listenaddress=0.0.0.0 `
    connectport=22 connectaddress=192.0.2.100

設定の確認はshow allサブコマンドでできる。

> netsh interface portproxy show all

ipv4 をリッスンする:         ipv4 に接続する:

Address         Port        Address         Port
--------------- ----------  --------------- ----------
0.0.0.0         22222       192.0.2.100     22

これにより、Windowsホストのポート22222へのアクセスはWSL(のアドレスを仮に192.0.2.100とした)のポート22へ転送されるようになる。

ファイアウォール設定

上記に加え、Windowsのファイアウォール設定を変更しないとアクセスできなかった。ファイアウォールの設定はnetsh advfirewallコマンドを使って行える。こちらも要管理者権限。

> netsh advfirewall firewall add rule `
    name="WSL-SSH" `
    protocol=TCP `
    localport=22222 `
    dir=in `
    action=allow

設定の確認は次の通り。

> netsh advfirewall firewall show rule name=WSL-SSH

規則名:                               WSL-SSH
----------------------------------------------------------------------
有効:                                 はい
方向:                                 入力
プロファイル:                         ドメイン,プライベート,パブリック
グループ:
ローカル IP:                          任意
リモート IP:                          任意
プロトコル:                           TCP
ローカル ポート:                      22222
リモート ポート:                      任意
エッジ トラバーサル:                  いいえ
操作:                                 許可
OK

以上の設定で外部からWSLのSSHへのアクセスができるようになった。

設定の削除方法

ファイアウォール設定とポートフォワード設定を削除して元に戻すには次のようにする。

> netsh advfirewall firewall delete rule name=WSL-SSH

1 規則を削除しました。
OK
> netsh interface portproxy delete v4tov4 listenport=22222 listenaddress=0.0.0.0

NetNat系コマンドとの違い

以前にHyper-V上のVMへ外部からアクセスする方法をメモしていて、このときはnetshではなくAdd-NetNatStaticMappingコマンドなどを使っていた。

WSLについても同じようにしてできるのかもしれないが、netshを使うと仮想スイッチの設定を気にしなくていいのでこちらの方がWSL向きな気がする。

違いは理解できていない。