Help us understand the problem. What is going on with this article?

python dictionary の強化版【演算子で計算可能な辞書】を作った

More than 1 year has passed since last update.

CalDict

  • 演算子で計算可能な辞書
  • python dictionary の強化版
  • collection.UserDictを継承

演算

辞書に対して+, -, *, / , //, %, **, <, <=, >, >=の演算子を使うことができます。

>>> cdic = CalDict(a=1, b=5, c=15)
>>> cdic + 5
{'a': 6, 'b': 10, 'c': 20}
>>> cdic - 5
{'a': -4, 'b': 0, 'c': 10}
>>> cdic * 5
{'a': 5, 'b': 25, 'c': 75}
>>> cdic / 5
{'a': 0.2, 'b': 1.0, 'c': 3.0}
>>> cdic // 5
{'a': 0, 'b': 1, 'c': 3}
>>> cdic % 5
{'a': 1, 'b': 0, 'c': 0}
>>> cdic ** 5
{'a': 1, 'b': 3125, 'c': 759375}
>>> -cdic
{'a': -1, 'b': -5, 'c': -15}
>>> cdic > 0
True
>>> cdic > 1
False
>>> -cdic <= -1
True

self演算

辞書自身に演算子を作用させて内容を書き換えることができます。

>>> cdic = CalDict(a=1, b=5, c=15)
>>> cdic -= 5
>>> cdic
{'a': -4, 'b': 0, 'c': 10}

逆演算

演算子は右からでも左からでも作用させることができます。

>>> cdic = CalDict(a=2, b=5, c=10)
>>> 2 / cdic
{'a': 1.0, 'b': 0.4, 'c': 0.2}

要素演算

それぞれのキーに対して演算可能です。

>>> cdic = CalDict(a=1, b=-1, c=15)
>>> bdic = CalDict(a=1, b=1, c=1)
>>> cdic += bdic
>>> cdic  # all key
{'a': 2, 'b': 0, 'c': 16}
>>> cdic + {'a': 5, 'c':-5}  # particular keys
{'a': 7, 'b': 0, 'c': 11}

スライス

通常の辞書のスライスに加えて、マルチスライスを使うことができます。

>>> cdic = CalDict(a=1, b=5, c=15)
>>> cdic['a']  # normal slice
1
>>> cdic['a','c']  # multiple slice
{'a': 1, 'c': 15}
>>> list(cdic['a','c'].values())  # get list values
[1, 15]

関数を適用

一つ以上の関数をキーに対して作用させることができます。
pandas.DataFrame().apply() メソッドを参考に設計しました。

>>> cdic = CalDict(a=1, b=5, c=15)
>>> cdic.apply(sum)
21
>>> cdic.apply(lambda x: x**2)
{'a': 1, 'b': 25, 'c': 225}
>>> cdic.apply(lambda x,y,z: x*y*z, 10, 0.5)
{'a': 5.0, 'b': 25.0, 'c': 75.0}
>>> cdic.apply([max,min])
{'max': 15, 'min': 1}

統計

基本的な統計関数max, min, sum, meanをサポートしています。
追記 sortメソッドにより値で並べ替えできるようにしました。

>>> cdic = CalDict(a=1, b=5, c=15)
>>> cdic.max()
15
>>> cdic.min()
1
>>> cdic.sum()
21
>>> cdic.mean()
7.0
>>> cdic.sort(reverse=True)
{'c': 15, 'b': 5, 'a': 1}

ソース

ichiban.py
#!/usr/bin/env python3
"""
# CalDict
* Caluculatable dictionary with operator
* python dictionary enhancement
* inherit of UserDict
"""
from collections import UserDict


class CalDict(UserDict):
    """Caluculatable dictionary with operator
    usage:
        # calculate
        >>> cdic = CalDict(a=1, b=5, c=15)
        >>> cdic + 5
        {'a': 6, 'b': 10, 'c': 20}
        >>> cdic - 5
        {'a': -4, 'b': 0, 'c': 10}
        >>> cdic * 5
        {'a': 5, 'b': 25, 'c': 75}
        >>> cdic / 5
        {'a': 0.2, 'b': 1.0, 'c': 3.0}
        >>> cdic // 5
        {'a': 0, 'b': 1, 'c': 3}
        >>> cdic % 5
        {'a': 1, 'b': 0, 'c': 0}
        >>> cdic ** 5
        {'a': 1, 'b': 3125, 'c': 759375}
        >>> -cdic
        {'a': -1, 'b': -5, 'c': -15}
        >>> cdic > 0
        True
        >>> cdic > 1
        False
        >>> -cdic <= -1
        True

        # self calculate
        >>> cdic = CalDict(a=1, b=5, c=15)
        >>> cdic -= 5
        >>> cdic
        {'a': -4, 'b': 0, 'c': 10}

        # reverse caluculate
        >>> cdic = CalDict(a=2, b=5, c=10)
        >>> 2 / cdic
        {'a': 1.0, 'b': 0.4, 'c': 0.2}

        # element add
        >>> cdic = CalDict(a=1, b=-1, c=15)
        >>> bdic = CalDict(a=1, b=1, c=1)
        >>> cdic += bdic
        >>> cdic  # all key
        {'a': 2, 'b': 0, 'c': 16}
        >>> cdic + {'a': 5, 'c':-5}  # particular keys
        {'a': 7, 'b': 0, 'c': 11}

        # slice
        >>> cdic = CalDict(a=1, b=5, c=15)
        >>> cdic['a']  # normal slice
        1
        >>> cdic['a','c']  # multiple slice
        {'a': 1, 'c': 15}
        >>> list(cdic['a','c'].values())  # get list values
        [1, 15]

        # function apply
        >>> cdic = CalDict(a=1, b=5, c=15)
        >>> cdic.apply(sum)
        21
        >>> cdic.apply(lambda x: x**2)
        {'a': 1, 'b': 25, 'c': 225}
        >>> cdic.apply(lambda x,y,z: x*y*z, 10, 0.5)
        {'a': 5.0, 'b': 25.0, 'c': 75.0}
        >>> cdic.apply([max,min])
        {'max': 15, 'min': 1}

        # stats
        >>> cdic = CalDict(a=1, b=5, c=15)
        >>> cdic.max()
        15
        >>> cdic.min()
        1
        >>> cdic.sum()
        21
        >>> cdic.mean()
        7.0
        >>> cdic.sort(reverse=True)
        {'c': 15, 'b': 5, 'a': 1}
    """

    def __init__(self, **kwargs):
        super().__init__(self, **kwargs)

    def __add__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: self[i] + value[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: v + value for k, v in self.items()})

    def __radd__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: value[i] + self[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: value + v for k, v in self.items()})

    def __sub__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: self[i] - value[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: v - value for k, v in self.items()})

    def __rsub__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: value[i] - self[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: value - v for k, v in self.items()})

    def __mul__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: self[i] * value[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: v * value for k, v in self.items()})

    def __rmul__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: value[i] * self[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: value * v for k, v in self.items()})

    def __truediv__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: self[i] / value[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: v / value for k, v in self.items()})

    def __rtruediv__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: value[i] / self[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: value / v for k, v in self.items()})

    def __floordiv__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: self[i] // value[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: v // value for k, v in self.items()})

    def __rfloordiv__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: value[i] // self[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: value // v for k, v in self.items()})

    def __mod__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: self[i] % value[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: v % value for k, v in self.items()})

    def __rmod__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: value[i] % self[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: value % v for k, v in self.items()})

    def __pow__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: self[i]**value[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: v**value for k, v in self.items()})

    def __rpow__(self, value):
        try:
            dic = self.copy()
            if isinstance(value, dict) or isinstance(value.data, dict):
                dic.update(**{i: value[i]**self[i] for i in value.keys()})
            return dic
        except AttributeError:
            return CalDict(**{k: value**v for k, v in self.items()})

    def __neg__(self):
        return CalDict(**{k: -v for k, v in self.items()})

    def __gt__(self, value):
        return all(i > value for i in self.values())

    def __lt__(self, value):
        return all(i < value for i in self.values())

    def __ge__(self, value):
        return all(i >= value for i in self.values())

    def __le__(self, value):
        return all(i <= value for i in self.values())

    def __getitem__(self, key):
        if len(key) < 2:
            return self.data[key]
        return CalDict(**{i: self.data[i] for i in key})

    def apply(self, func, *args, **kwargs):
        """Using one or more operations over keys
        usage:
            cdic = CalDict(a=1, b=5, c=15)
            cdic.apply(sum)
            21
            cdic.apply(lambda x: x**2)
            {'a': 1, 'b': 25, 'c': 225}
            cdic.apply(lambda x,y,z: x*y*z, 10, 0.5)
            {'a': 5.0, 'b': 25.0, 'c': 75.0}
            cdic.apply([max,min])
            {'max': 15, 'min': 1}
        """
        if isinstance(func, (list, tuple)):
            return CalDict(
                **{f.__name__: self.apply(f, *args, **kwargs)
                   for f in func})
        else:
            try:
                return CalDict(
                    **{k: func(v, *args, **kwargs)
                       for k, v in self.items()})
            except (ValueError, AttributeError, TypeError):
                return func(self.values(), *args, **kwargs)

    def max(self):
        """return max of values"""
        return self.apply(max)

    def min(self):
        """return min of values"""
        return self.apply(min)

    def sum(self):
        """return sum of values"""
        return self.apply(sum)

    def mean(self):
        """return mean of values"""
        return self.apply(sum) / len(self)

    def sort(self, key=lambda x: x[1], reverse=False):
        """sorted by values"""
        return CalDict(**dict(sorted(self.items(), key=key, reverse=reverse)))


if __name__ == '__main__':
    import doctest
    doctest.testmod()

Github - u1and0/ichiban
一番くじシミュレータの実装を易しくするために作った"道具"です。作っているうちに汎用性が高いことに気が付いたので記事化しました。
↑実装方法を変えたのでもう使っていません。

pandas.DataFrame()を使えばできることですが、クラスの継承などを覚えたかったので、あえてcollection.UserDictを使用して一からクラスを作りました。
容易にライブラリをインストールできないサバイバル環境にも使えます。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away