Python(Tableクラス設計)

オブジェクト指向の勉強として任意のリストを整形して表示するクラスを設計しました。
クラスの仕様は
①二次元の表データを保持し、str関数で表の文字列データを返すことができる。( __str__メソッド )
②任意の要素にアクセスし、値を設定できる。(__setitem__メソッド)
③コンストラクタで表のデータをイニシャライズできる。(__init__メソッド)
④任意の要素の値を得ることができる。(__getitem__メソッド)
⑤表のデータに設定されているかどうか値の確認ができる(__contains__メソッド)

class Table:
    def __init__(self,xss=[[]]):
        """リストを表として整形する
           @param xss  表示したい2次元リスト(行の要素ごとをリストとする)"""        
        if not all([isinstance(xs,list) for xs in xss]):
            Table.err()

        def make_string(d):
            """辞書に登録されている要素から表の文字列を作成する関数
               @param d 2次元配列の座標をタプルのキーとする辞書
               @return  辞書の座標に要素を入れて作成した表の文字列"""
            
            def separator(xs,sep):
                """リスト内の文字列を指定した文字列で区切り、結合する関数
                   @param xs  文字列のリスト
                   @param sep 区切りに使用する文字列
                   @return    指定した文字列で区切り、要素を結合した文字列"""
                return sep + sep.join(xs) + sep

            def check(x,y,d):
                """辞書内の要素をチェックして値を返す関数
                   @param x 座標x
                   @param y 座標y
                   @param d 辞書
                   @return  該当する要素があればその値の文字列、
                            なければ' '(空白1つ)の文字列"""
                if (y,x) in d:
                    return str(d[y,x])
                else:
                    return " "
            
            tmp_cell_list = list(d.keys())            
            if len(tmp_cell_list)==0:
                return "++\n++"
            n = range(len(tmp_cell_list))           
            tmp_y,tmp_x = 0,0
            for i in n:
                if tmp_y < tmp_cell_list[i][0]:
                    tmp_y = tmp_cell_list[i][0]
                if tmp_x < tmp_cell_list[i][1]:
                    tmp_x = tmp_cell_list[i][1]
            
            tmp_list=[[check(x,y,d) for x in range(tmp_x+1)] for y in range(tmp_y+1)]            
            tmp_ps_list = [[len(tmp_list[y][x]) for y in range(tmp_y+1)]
                           for x in range(tmp_x+1)]            
            ps=[max(i) for i in tmp_ps_list]            
            str_line = separator(["-" * i for i in ps],"+")
            str_table_list=["|".join(line) for line in [["%*s"%v for v in zip(ps,tmp_list[n])]
                                                        for n in range(tmp_y+1)]]
            tmp_str = ""
            for n in range(tmp_y+1):
                tmp_str += str_line + "\n"
                tmp_str += "|" + str_table_list[n]+ "|\n"
            tmp_str += str_line + "\n"
            return tmp_str
        
        tmp_dict = {}            
        if xss != [[]]:
            tmp_y = len(xss)
            ns = [len(xs) for xs in xss]
            tmp_x = max(ns)
            tmp_dict[tmp_y-1,tmp_x-1] = " "
            for y in range(tmp_y):
                _count = 0
                while _count < ns[y]:
                    tmp_dict[y,_count] = xss[y][_count]
                    _count += 1

        self._table_dict = tmp_dict
        self.f = make_string

    @staticmethod
    def err(message=""):
        raise SyntaxError(message)

    def __str__(self):
        _table_str = self.f(self._table_dict)
        return _table_str

    def __contains__(self,value):
        return value in self._table_dict.values()

    def __getitem__(self,index):
        if index in self._table_dict:
            return self._table_dict[index]
        else:
            Table.err()

    def __setitem__(self,index,value):
        if len(index) == 2:
            if all([isinstance(i,int) for i in index]):
                self._table_dict[index] = value                
            else:
                Table.err()
        else:
            Table.err()

t=Table()
print(t)
print(111 in t)
t[2,3]=111
print(t)
print(t[2,3])
print(111 in t)

t2=Table([[1,2,3,4,5],[0,111,"hello"],[6,7]])
print(t2)
t2[2,3]=111
print(t2)
t2[10,10]= 2
print(t2)
#t2[1000,0]

引数はデフォルトで[[]]という2次元配列(リストのリスト)を設定し、データはdict(辞書)として保持する設計にしています。
デフォルトの場合はstrに"++\n++"を返すようにし、任意のデータを追加した場合は値に応じて表が拡張されるように内部関数を定義して、関数オブジェクトを保持し、クラス内で使用できるようにしました。
エラーチェックはコンストラクタの引数が2次元配列でない場合や要素を設定する場合の座標が二次元ではない、もしくは引数がint型でない場合、未定義要素の値を得ようとした場合に行っています。

設計のポイントはリスト内包表記、max関数、all関数、zip関数、len関数と辞書の活用です。

まず引数で2次元配列を受け取った場合、各行に表示する要素数を確認し、値があれば辞書に登録して保持させます。

得られた辞書の登録座標を確認し行と列の最大値を得た後に表のグリッドをリスト内包で作成しました。

ネストしたリスト内包ではforループのまとまりで分解して考えると分かりやすくなります。
tmp_list=[[check(x,y,d) for x in range(tmp_x+1)] for y in range(tmp_y+1)]
についてはもともと下記ようにループで記述し、if...else...の判定を内部関数checkとしてまとめました.

tmp_list=[]            
            for y in range(tmp_y+1):
                tmp_list.append([])
                for x in range(tmp_x+1):
                    if (y,x) in d:
                        tmp_list[y].append(str(d[y,x]))
                    else:
                        tmp_list[y].append(" ")

同様に列の文字数の最大値リストを作成して表の整形に活用する場合は
tmp_ps_list = [[len(tmp_list[y][x]) for y in range(tmp_y+1)] for x in range(tmp_x+1)]
のようなリスト内包を利用している部分は

tmp_ps_list=[]
            for x in range(tmp_x+1):
                tmp_ps_list.append([])
                for y in range(tmp_y+1):
                    tmp_ps_list[y].append(len(tmp_list[y][x]))

のようにforループで作成したものをリスト内包表記にしました。

プログラムの要素を分解して部分ごとの動作をPythonの場合はIDLEなどで確認することができるので、いきなり処理しようとせずに部分ごとに確認しながらプログラムを組み立てていきましょう。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

This site uses Akismet to reduce spam. Learn how your comment data is processed.