LoginSignup
7
4

More than 3 years have passed since last update.

pythondict辞書キーを正規表現で検索する

Last updated at Posted at 2020-10-25

dictキーを正規表現で検索したい

dict辞書は高速で非常に使い勝手が良い。
キーを正規表現で検索したい場合普通はこうすると思うが、多用する場合は面倒だ。

a = dict(abc=1, def=2) #-> {"abc": 1, "def": 2}
s = re.compile(".bc")
ret = []
for k in a:
    if s.search(k):
        ret.append(a[k])
print(ret) #-> [1]

# 内包表記で短くなってもせいぜいこんな感じだが、可読性が悪く、忘れたころにソースを読むと何をやりたかったのかよくわからん。
s = re.compile(".bc")
[a[k] for k in a if s.search(k)] #-> [1]

これが面倒なので標準のdictを拡張してこんな感じで使えるようにしてみた。

a = rdict(abc=1, def=2) #-> {"abc": 1, "def": 2}
a.search(".bc") #-> [1]

ソースはこう。

import re
from functools import lru_cache

@lru_cache(16) # re.compileは結構重めな処理なので、同じ正規表現パターンはキャッシュし、性能稼ぐ
def re_call(pattern, flags=0, call_re_func="search"):
    return re.compile(pattern, flags=flags).__getattribute__(call_re_func)

class rdict(dict):
    def _filter(self, _callable):
        return (k for k in self if _callable(k))

    def isin(self, key_or_function):
        if callable(key_or_function):
            return any(True for _ in self._filter(key_or_function))
        return dict.__contains__(self, key_or_function)

    def findall(self, key_or_function):
        if callable(key_or_function):
            return [dict.__getitem__(self, key) for key in self._filter(key_or_function)]
        return dict.__getitem__(self, key_or_function)

    def search(self, pattern, flags=0):
        return [dict.__getitem__(self,key) for key in self if re_call(pattern, flags, "search")(key)]

    def fullmatch(self, pattern, flags=0):
        return [dict.__getitem__(self,key) for key in self if re_call(pattern, flags, "fullmatch")(key)]

    def __setitem__(self, key_or_function, value):
        if callable(key_or_function):
            for key in self._filter(key_or_function):
                dict.__setitem__(self, key, value)
        else:
            return dict.__setitem__(self, key_or_function, value)

    def __delitem__(self, key_or_function):
        if callable(key_or_function):
            for key in list(self._filter(key_or_function)):
                dict.__delitem__(self, key)
        else:
            return dict.__delitem__(self, key_or_function)

その他にもいくつか関数を追加してるが使い方のイメージはこう。

正規表現パターンで一致するキーがあるかどうかを調べる


    >>> a.isin(re.compile(".b.*").search)
    True
    >>> a.isin(re.compile(".z.*").search)
    False

その他1、条件が真になる場合のキーを持つ場合の値を返す

    >>> a.findall(lambda x: len(x) == 3)
    [1, 2]

その他2、キーを範囲で検索し、値を返す
findallの引数はcallableなら、何でも動くので以下のような応用も可


    >>> from datetime import datetime
    >>> b = funcdict()
    >>> b[datetime(2020,1,1)] = "2020/01/01"
    >>> b[datetime(2020,2,1)] = "2020/02/01"
    >>> b[datetime(2020,3,1)] = "2020/03/01"

    >>> def between_0131_0202(x):
    ...    return datetime(2020,1,31) < x and x < datetime(2020,2,2)
    >>> b.findall(between_0131_0202)
    ['2020/02/01']

    >>> def less_0401(x):
    ...    return x < datetime(2020, 4, 1)
    >>> b.isin(less_0401)
    True

    >>> def grater_0401(x):
    ...    return x > datetime(2020, 4, 1)
    >>> b.isin(grater_0401)
    False

    >>> b.findall(less_0401)
    ['2020/01/01', '2020/02/01', '2020/03/01']

あとは、条件にマッチするキーの値を一括で変更する機能

    >>> b[less_0401] = "test"
    >>> b
    {datetime.datetime(2020, 1, 1, 0, 0): 'test',
     datetime.datetime(2020, 2, 1, 0, 0): 'test',
     datetime.datetime(2020, 3, 1, 0, 0): 'test'}

ついでに、条件にマッチするキーを一括で削除する機能

    >>> del b[between_0131_0202]
    >>> b
    {datetime.datetime(2020, 1, 1, 0, 0): 'test',
     datetime.datetime(2020, 3, 1, 0, 0): 'test'}
7
4
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
7
4