2つの tuple の結合(2)

2つの tuple の結合(1)に続けて、、、
結合元のタプルの要素数=2であるなら、
内包表示で、タプルのインデックスに注意を払えば、もっと簡単に書けるはずで、、

a = [ ('A',1103),('B',1104),('C',1105) ]
b = [ ('A','red'),('C','yellow'),('D','blue') ]

dict() で、辞書を用意

a_dict = dict(a)
b_dict = dict(b)

a LEFT JOIN b ON a[0] = b[0]

# a ← b
a_left_b = [ (x[0], x[1], b_dict.get(x[0], None)) for x in a ]

b LEFT JOIN a ON b[0] = a[0]

# b ← a
b_left_a = [ (x[0], a_dict.get(x[0], None), x[1]) for x in b ]

a LEFT JOIN b ON a[0] = b[0] WHERE b[2] NOT NULL

# a ← b where color is not null
a_left_b_C = list(filter(lambda y:y[2] is not None, [ (x[0], x[1], b_dict.get(x[0], None)) for x in a ]))

b LEFT JOIN a ON b[0] = a[0] WHERE a[2] NOT NULL

# b ← a where color is not null
b_left_a_C = list(filter(lambda y:y[1] is not None, [ (x[0], a_dict.get(x[0], None), x[1]) for x in b ]))
print(a_dict)
print(b_dict)
print('\n-- a LEFT JOIN b ON a.name=b.name --')
print(a_left_b)
print('\n-- b LEFT JOIN a ON b.name=a.name  --')
print(b_left_a)
print('\n-- a LEFT JOIN b ON a.name=b.name WHERE color is not null --')
print(a_left_b_C)
print('\n-- b LEFT JOIN a ON b.name=a.name WHERE id not null --')
print(b_left_a_C)

print の結果

{'A': 1103, 'B': 1104, 'C': 1105}
{'A': 'red', 'C': 'yellow', 'D': 'blue'}

-- a LEFT JOIN b ON a.name=b.name --
[('A', 1103, 'red'), ('B', 1104, None), ('C', 1105, 'yellow')]

-- b LEFT JOIN a ON b.name=a.name  --
[('A', 1103, 'red'), ('C', 1105, 'yellow'), ('D', None, 'blue')]

-- a LEFT JOIN b ON a.name=b.name WHERE color is not null --
[('A', 1103, 'red'), ('C', 1105, 'yellow')]

-- b LEFT JOIN a ON b.name=a.name WHERE id not null --
[('A', 1103, 'red'), ('C', 1105, 'yellow')]

2つの tuple の結合(1)

先日は、dict リスト の結合だったので今度は、tupleリストの結合

a = [ ('A',1103),('B',1104),('C',1105) ]
b = [ ('A','red'),('C','yellow'),('D','blue') ]

先頭インデックス [0] をキーとして結合する。
それぞれキーのdict を用意する

a_dict = { t[0]:t for t in a }
b_dict = { t[0]:t for t in b }

以下のように set を使う方法は、順番が定まらないので tuple として使用できない

c = [ tuple(set(at + b_dict.get(at[0],(None,None)))) for at in a ]

結合で生成する tuple タプルは、+ 演算子以外に更新する方法がないので、
演算子以外の方法として、一時的に list に変換してからマージするメソッドを
用意する

# tuple をマージするメソッド
def mergeTuple(t, u):
    x = list(t)
    for i in range(len(t)-1):
        x.append(None if u[i] is None else u[i+1])
    return tuple(x)

a LEFT JOIN b ON a[0] = b[0]

# a ← b
a_left_b = [ mergeTuple(at, b_dict.get(at[0], (None,None))) for at in a ]

b LEFT JOIN a ON b[0] = a[0]

# b ← a
b_left_a = [ mergeTuple(bt, a_dict.get(bt[0], (None,None))) for bt in b ]

a LEFT JOIN b ON a[0] = b[0] WHERE b[2] NOT NULL

# a ← b where color is not null
a_left_b_C = list(filter(lambda x:x[2] is not None, [ mergeTuple(at, b_dict.get(at[0], (None,None))) for at in a ]))

b LEFT JOIN a ON b[0] = a[0] WHERE a[2] NOT NULL

# b ← a where color is not null
b_left_a_C = list(filter(lambda x:x[2] is not None, [ mergeTuple(bt, a_dict.get(bt[0], (None,None))) for bt in b ]))
print(a_dict)
print(b_dict)
print('\n-- a LEFT JOIN b ON a.name=b.name --')
print(a_left_b)
print('\n-- b LEFT JOIN a ON b.name=a.name  --')
print(b_left_a)
print('\n-- a LEFT JOIN b ON a.name=b.name WHERE color is not null --')
print(a_left_b_C)
print('\n-- b LEFT JOIN a ON b.name=a.name WHERE id not null --')
print(b_left_a_C)

print の結果

{'A': ('A', 1103), 'B': ('B', 1104), 'C': ('C', 1105)}
{'A': ('A', 'red'), 'C': ('C', 'yellow'), 'D': ('D', 'blue')}

-- a LEFT JOIN b ON a.name=b.name --
[('A', 1103, 'red'), ('B', 1104, None), ('C', 1105, 'yellow')]

-- b LEFT JOIN a ON b.name=a.name  --
[('A', 'red', 1103), ('C', 'yellow', 1105), ('D', 'blue', None)]

-- a LEFT JOIN b ON a.name=b.name WHERE color is not null --
[('A', 1103, 'red'), ('C', 1105, 'yellow')]

-- b LEFT JOIN a ON b.name=a.name WHERE id not null --
[('A', 'red', 1103), ('C', 'yellow', 1105)]

2つの dict の結合

DBのように、2つの dictionary リストの結合(LEFT JOIN)をする。

2つの dictionary のリスト

a = [ {'name':'A','id':1103},{'name':'B','id':1104},{'name':'C','id':1105} ]
b = [ {'name':'A','color':'red'},{'name':'C','color':'yellow'},{'name':'D','color':'blue'} ]

それぞれ、結合キーの dict を用意する。

a_dict = { t['name']:t for t in a }
b_dict = { t['name']:t for t in b }

a LEFT JOIN b ON a.name = b.name

# a ← b
a_left_b = [{**at, **b_dict.get(at['name'], {'name':at['name'], 'color':None})} for at in a]

b LEFT JOIN a ON b.name = a.name

# b ← a
b_left_a = [{**bt, **a_dict.get(bt['name'], {'name':bt['name'], 'id':None})} for bt in b]

a LEFT JOIN b ON a.name = b.name WHERE color IS NOT NULL

# a ← b where color is not null
a_left_b_C = list(filter(lambda x:x['color'] is not None, [{**at, **b_dict.get(at['name'], {'name':at['name'], 'color':None})} for at in a]))

b LEFT JOIN a ON b.name=a.name WHERE id not null

# b ← a where id is not null
b_left_a_C = list(filter(lambda x:x['id'] is not None, [{**bt, **a_dict.get(bt['name'], {'name':bt['name'], 'id':None})} for bt in b]))
print(a_dict)
print(b_dict)
print('\n-- a LEFT JOIN b ON a.name=b.name --')
print(a_left_b)
print('\n-- b LEFT JOIN a ON b.name=a.name  --')
print(b_left_a)
print('\n-- a LEFT JOIN b ON a.name=b.name WHERE color is not null --')
print(a_left_b_C)
print('\n-- b LEFT JOIN a ON b.name=a.name WHERE id not null --')
print(b_left_a_C)

print の結果

{'A': {'name': 'A', 'id': 1103}, 'B': {'name': 'B', 'id': 1104}, 'C': {'name': 'C', 'id': 1105}}
{'A': {'name': 'A', 'color': 'red'}, 'C': {'name': 'C', 'color': 'yellow'}, 'D': {'name': 'D', 'color': 'blue'}}

-- a LEFT JOIN b ON a.name=b.name --
[{'name': 'A', 'id': 1103, 'color': 'red'}, {'name': 'B', 'id': 1104, 'color': None}, {'name': 'C', 'id': 1105, 'color': 'yellow'}]

-- b LEFT JOIN a ON b.name=a.name  --
[{'name': 'A', 'color': 'red', 'id': 1103}, {'name': 'C', 'color': 'yellow', 'id': 1105}, {'name': 'D', 'color': 'blue', 'id': None}]

-- a LEFT JOIN b ON a.name=b.name WHERE color is not null --
[{'name': 'A', 'id': 1103, 'color': 'red'}, {'name': 'C', 'id': 1105, 'color': 'yellow'}]

-- b LEFT JOIN a ON b.name=a.name WHERE id not null --
[{'name': 'A', 'color': 'red', 'id': 1103}, {'name': 'C', 'color': 'yellow', 'id': 1105}]

format関数で金額桁区切りを表示する

Python format関数で置換後の幅を指定する方法は、
 {:n}
と、: の後に幅 n を指定する。

print('1234567890')
print('{:5}'.format('A')+"-")

# 1234567890
# A    -

となる。

数値を千の単位で、カンマを差し込む時は、
 {:,}

print('{:,}'.format(123456))

# 123,456

実践を考えて、数字の後に'円'が付くものだけを対象にする場合で
さらに表示幅を大きくして揃えたい。


編集前のテキスト、、

txt = 'id=1023  price:1000円\n' \
      'id=1024  price:300円\n' \
      'id=1025  price:210500円\n' \
      'id=1026  price:1500000円\n'

関数を定義して

def priceRepl(m):
    return '{:11,}'.format(int(m.group().replace('円', ''))) + ' 円'

result = re.sub(r'[0-9]+円', priceRepl, txt)

print(result)
# id=1023  price:      1,000 円
# id=1024  price:        300 円
# id=1025  price:    210,500 円
# id=1026  price:  1,500,000 円

関数なんか定義せずにラムダで、、

result = re.sub(r'[0-9]+円', lambda m:'{:11,}'.format(int(m.group().replace('円', '')))+' 円', txt)

print(result)
# id=1023  price:      1,000 円
# id=1024  price:        300 円
# id=1025  price:    210,500 円
# id=1026  price:  1,500,000 円

Python 正規表現置換でリストによる置換を考える

Python で、

str = '"1": "orange",  "2":"lemon", "3":"apple"'
alist = [ 'A', 'B', 'C' ]

このように str に対する正規表現 [a-z]+ にマッチする置換を
リスト alist で順に置換したい。
期待値は、、

"1": "A",  "2":"B", "3":"C"

Python正規表現・置換でここまで実現するには次の工夫が必要だ。
re.sub で、"{インデックス}" に変換して format 関数で置換するという手順にする。

sub に渡す replace 関数を置換するリストを指定することで提供するクラスを用意する。

import re

class Replsequence():
    def __init__(self, ary):
        self.n = -1
        self.len = len(ary)
    def handle(self, m):
        self.n = self.n + 1
        return '{%d}' % self.n if self.n < self.len else m.group()

実行

result = re.compile(r'[a-z]+').sub(Replsequence(alist).handle, str, re.MULTILINE).format(*alist)
print(result)

# "1": "A",  "2":"B", "3":"C"

置換するリスト、Replsequence インスタンス生成で渡すリストが少ない場合、

alist = [ 'A', 'B' ]

の時は、3番目まで置換ではなく、2番目までの置換になり

"1": "A",  "2":"B", "3":"apple"

となる。
ただし、注意が必要なのは、置換される元の文字列に、"{" , "}" が存在する場合は、
この方法が使用できない。
format関数に頼るからだ。

itertools の groupby

あるリストをグルーピングするのに、便利な標準ライブラリ itertools の groupby

これを使用するには、事前に対象をグルーピングするキーでソートしてしておかなければならない。

from functools import reduce

from itertools import groupby

users = [
    { "key": "A", "value": 15 },
    { "key": "C", "value": 32 },
    { "key": "B", "value": 24 },
    { "key": "A", "value": 12 },
    { "key": "C", "value": 35 },
    { "key": "B", "value": 22 },
    { "key": "A", "value": 17 }
]
# 事前にソートする
users.sort(key=lambda u:u['key'])

# key でグルーピング
for key, group in groupby(users, key=lambda e:e['key']):
    print('key = %s' % key)
    for user in group:
        print(user)

# key = A
# {'key': 'A', 'value': 15}
# {'key': 'A', 'value': 12}
# {'key': 'A', 'value': 17}
# key = B
# {'key': 'B', 'value': 24}
# {'key': 'B', 'value': 22}
# key = C
# {'key': 'C', 'value': 32}
# {'key': 'C', 'value': 35}
for key, group in groupby(users, key=lambda e:e['key']):
    sumvalue = sum([ u['value'] for u in group])
    print('key = %s  sum = %s' % (key, sumvalue))

# key = A  sum = 44
# key = B  sum = 46
# key = C  sum = 67

reduce でリストの重複有無をチェックする

高階関数 functools.reduce(function, sequence, initial=None) は、
リストの存在チェック in Iterable の代用に書けますが、
わざわざ in Iterable の代わりに以下のように書くことはありません。

from functools import reduce

list = ['a','b','c','d','e']
sts = reduce(lambda r,t:r or t=='c', list, False)

しかし、リストの要素が重複がないユニークであるか否かをチェックするのに
使うことができます。

それには、Java と違ってリスト追加の append メソッドが戻り値として
何も返さないという欠点を念頭におかなければなりません。
さらに、Pythonのラムダは複数行の処理を書くようになってないので、無理やり配列orタプルで
実行するように書いて、

addList = lambda b,a:(b in a, a.append(b))[0]
status = addList('d', list)

この status は、True です。
これを踏まえて以下のように
reduce のイニシャライザに、(False, []) というタプルを指定することで、

if reduce(lambda r,t:(r[0] or addList(t, r[1]), r[1]), list, (False, []))[0]:
    print('重複あり')
else:
    print('重複なし')

と、addList = lambda b,a:(b in a, a.append(b))[0] を使用して書けます。

この addList lambda を宣言せずに記述できるわけで、

if reduce(lambda r,t:(r[0] or (t in r[1], r[1].append(t))[0], r[1]), list, (False, []))[0]:
    print('重複あり')
else:
    print('重複なし')

全て同じは、

if len(set(list)) == 1:
       print('全て同じ')

全てユニークは、

if len(list)==len(set(list)):
    print('全てユニーク')

と、書けるのだけれども、、
無理やりに reduce() で書くと、、

if reduce(lambda r, t:((r[1].append(t), r[1].count(t))[1], r[1]), list, (0, []))[0]==len(list):
    print('全て同一要素である')
else:
    print('全て同一要素でない')

if reduce(lambda r, t:(r[0] and (r[1].append(t), r[1].count(t)==1)[1], r[1]), list, (True, []))[0]:
    print('全てユニーク')
else:
    print('重複あり')