雑念ストレージ

プログラミング関連のメモとか

【Mermaid】Web上でMermaidを動かせる、Mermaid Live Editor

MermaidをWeb上で使えるMermaid Live Editorというサイトがある。
機密性を気にしなくていいような図なら、ここでササッと作ってもいいかもしれない。

個人的に、普段はPlantUMLをDockerで立てて使っている。
ただ、Gitのリビジョングラフの絵は、Mermaidのほうが見栄えがいい。

【HERZ】革のブックカバーを買った

革製品ブランドのHERZで、文庫本サイズのブックカバーを買った。

注文してから1ヶ月強で到着。
普段、受注生産のものを買うことがないので、待っている間も結構楽しみだった。

今のところ、小説を読むときに使おうかなと思ってる。文庫本サイズのノートカバーとして使ってもいいかも。

革はかなり厚みがある感じ。 自分のことだから、雑に扱ってキズだらけになりそうな気がする。

革の質感がすごく良い。フワフワ柔らかい感触。匂いも好き。

表面には縫い目もロゴも無いものを選んだので、シンプルな見た目で長く使えそう。

【Python】Enumで処理を分岐するなら、デフォルトの分岐はエラーにしておいたほうがいい気がする

Pythonで↓のようにEnumを分岐する処理を書いてみた。

from blood_types import BloodTypes


def uranai(blood_type: BloodTypes):
    """血液型占い"""

    match blood_type:
        case BloodTypes.A:
            print("大吉")
        case BloodTypes.B:
            print("中吉")
        case BloodTypes.O:
            print("小吉")

PythonJavaなどと違って、matchのルートに漏れがあってもコンパイルエラーにはしてくれない。
そのせいで、Enumに要素を追加する際に、実装漏れがないか結構気を使う。
(実際、↑のコードはAB型のルートが実装漏れしている)

そこで、以下のようにデフォルトのルートをNotImplementedErrorにしてみた。

from blood_types import BloodTypes


def uranai(blood_type: BloodTypes):
    """血液型占い"""

    match blood_type:
        case BloodTypes.A:
            print("大吉")
        case BloodTypes.B:
            print("中吉")
        case BloodTypes.O:
            print("小吉")
        case _:
            raise NotImplementedError()

↑の修正に加えて、Enumの全パターンを通すようなユニットテストも用意する。

import unittest

import blood_type_uranai
from blood_types import BloodTypes


class BloodTypeUranaiTest(unittest.TestCase):
    def test_uranai(self):
        # 全部の血液型で、エラーにならないことを確認
        for blood_type in BloodTypes:
            blood_type_uranai.uranai(blood_type)

こういうユニットテストを用意しておけば、
Enumに要素が追加された場合に実装漏れしている処理があっても、テストがコケるのですぐに気がつくはず。

【Python】Python3.10からmatch文が追加されて、Enumを if elif しなくてよくなった

Python3.10からmatch文が追加されたそう。

今までは、Enumを処理するにも↓のようにif elif を繰り返して書いていたのが

from blood_types import BloodTypes


def uranai(blood_type: BloodTypes):
    """血液型占い"""
    if blood_type == BloodTypes.A:
        print("大吉")
    elif blood_type == BloodTypes.B:
        print("中吉")
    elif blood_type == BloodTypes.O:
        print("小吉")
    elif blood_type == BloodTypes.AB:
        print("凶")
    else:
        raise NotImplementedError()

↓のようにmatch文できれいに書けるようになった。

from blood_types import BloodTypes


def uranai(blood_type: BloodTypes):
    """血液型占い"""

    match blood_type:
        case BloodTypes.A:
            print("大吉")
        case BloodTypes.B:
            print("中吉")
        case BloodTypes.O:
            print("小吉")
        case BloodTypes.AB:
            print("凶")
        case _:
            raise NotImplementedError()

match文はEnum専用というわけではなく、他にも色々使えるらしい。

Pythonってswitch文ないの!?と驚いていたけど、これは嬉しい。

【Python】auto()関数でEnumのvalueを自動設定する

PythonEnumを使おうとすると、各項目にvalueを設定する必要がある。
↓こんな感じ

from enum import Enum


class BloodTypes(Enum):
    A = "a"
    B = "b"
    O = "o"
    AB = "ab"

JavaC#から移ってきた人間には違和感があったけど、auto()関数を使えば、いちいち値を考えなくてもいいみたい。

from enum import Enum, auto


class BloodTypes(Enum):
    A = auto()
    B = auto()
    O = auto()
    AB = auto()

値が不要な場合のほうが多そうだし、基本はauto()関数を使えばよさそう。

【Postgresql】generate_series関数を集計に使用する

前回、generate_series関数でダミーデータを作る方法を書きました。

uranaga512.hatenablog.com

generate_series関数はちょっとしたデータの集計にも使えるので、今回はそれについて書きます。

日毎のレコード数の集計

例えば以下のようなテーブルがあったとします。
(何かしらのログが登録されるテーブルをイメージしています)

CREATE TABLE hoge_log (
  created_at timestamp NOT NULL
);

1日毎に何件のレコードがあるか調べたい場合、単にGROUP BYすればよいです。

SELECT
  date_trunc('DAY', created_at)::date AS log_date,
  count(*)
FROM
  hoge_log
GROUP BY
  log_date
ORDER BY
  log_date
;
/*
  log_date  | count 
------------+-------
 2019-01-01 |     2
 2019-01-02 |     1
 2019-01-04 |     3
 2019-01-05 |     1
(4 rows)
*/

ここでちょっと気になるのが
「件数が0件の場合、レコードが出力されない」
ということです。

上の例では、2019-01-03のレコードは出力されていません。

件数が0件の場合もレコードが出力されるようにしたい場合、generate_series関数が役に立ちます。

generate_seriesを使って、0件の日付も出力

SQLは以下のようになります。

SELECT
  series.log_date,
  count(hoge_log.*)
FROM
  (
    SELECT
      date_trunc(
        'DAY', 
        generate_series(
          '2019-01-01 00:00'::timestamp,
          '2019-01-05 00:00'::timestamp,
          '1 day')
      )::date AS log_date
  ) series
  LEFT JOIN hoge_log
    ON (series.log_date = date_trunc('DAY', hoge_log.created_at))
GROUP BY
  series.log_date
ORDER BY
  series.log_date
;
/*
  log_date  | count 
------------+-------
 2019-01-01 |     2
 2019-01-02 |     1
 2019-01-03 |     0
 2019-01-04 |     3
 2019-01-05 |     1
(5 rows)
*/

generate_series関数で日付の雛形を作ってあげて、それに対して実テーブルをLEFT JOINする形になっています。

count(hoge_log.*) のように、実テーブルのレコードだけをカウントしているのがポイントです。

【Postgresql】generate_series関数でダミーデータを作る

generate_seriesという関数がダミーデータを作るのに便利で、度々使ってます。

例えば以下のような感じで、簡単に行を生成できます。

SELECT generate_series(1, 10, 1); /* 1から10まで1ずつ増やす */
/*
 generate_series 
-----------------
               1
               2
               3
               4
               5
               6
               7
               8
               9
              10
(10 rows)
*/

これをFROM句に指定すれば、簡単にレコードを量産できます。

SELECT
  series AS id
  , 'hoge' AS value
FROM
  generate_series(1, 10, 1) series
;
/*
 id | value 
----+-------
  1 | hoge
  2 | hoge
  3 | hoge
  4 | hoge
  5 | hoge
  6 | hoge
  7 | hoge
  8 | hoge
  9 | hoge
 10 | hoge
(10 rows)
*/

増分を変えてみたり、、

SELECT
  series AS id
  , 'hoge' AS value
FROM
  generate_series(1, 10, 2) series /* 2ずつ増やす */
;
/*
 id | value 
----+-------
  1 | hoge
  3 | hoge
  5 | hoge
  7 | hoge
  9 | hoge
(5 rows)
*/

日付や時刻を引数に指定することもできます。

SELECT generate_series(
    '2019-01-01 00:00'::timestamp
    , '2019-01-07 00:00'::timestamp
    , '1 day');
/*
   generate_series   
---------------------
 2019-01-01 00:00:00
 2019-01-02 00:00:00
 2019-01-03 00:00:00
 2019-01-04 00:00:00
 2019-01-05 00:00:00
 2019-01-06 00:00:00
 2019-01-07 00:00:00
(7 rows)
*/
SELECT generate_series(
    '2019-01-01 00:00'::timestamp
    , '2019-01-01 05:00'::timestamp
    , '1 hour');
/*
   generate_series   
---------------------
 2019-01-01 00:00:00
 2019-01-01 01:00:00
 2019-01-01 02:00:00
 2019-01-01 03:00:00
 2019-01-01 04:00:00
 2019-01-01 05:00:00
(6 rows)
*/

日付や時刻を扱うときは第3引数がinterval型になるので、 '1 days' なんて書きかたができます。 (interval型の記法は8.5. 日付/時刻データ型を参考に)

random関数とかを絡めれば、もっと自然なダミーデータを作れそう。

便利!