PHPとPythonでジェネレータを利用してネストループ表記を回避する
下記のようなJSONをデコードして利用する場合、ネスト(入れ子)でループ処理を記載しまうことがある。
{
"a": {"A": {"A0": [1, 2], "A1": [3, 4]}, "B": {"A0": [5, 6], "A1": [7, 8]}},
"b": {"A": {"A0": [10, 20], "A1": [30, 40]}, "B": {"A0": [50, 60], "A1": [70, 80]}}
}
このとき、キーA0
のインデックス1
の値抽出は以下のような結果となるが、PHPやPythonでその処理を実現する際、2重ループや3重ループとしてしまうケースがある。
[2, 6, 20, 60]
PHP7とPython3.3以降ではyield from
を利用したジェネレータが利用可能なため、この機能を利用して、ネストループから脱却できるか検証する。
検証ではPHP7.1.5、Python3.5.0を利用。
ネストループで実現した場合
3重ループ、2重ループで実現したときの例をPHPとPythonのコードを記載する。
PHP7の場合
array_walk
等は利用しない前提で、foreach
のみで実施。
<?php
$nest = json_decode(file_get_contents("nest.json"), true);
/*
$nest = [
"a" => ["A" => ["A0" => [1,2], "A1" => [3,4]], "B" => ["A0" => [5,6], "A1" => [7,8]]],
"b" => ["A" => ["A0" => [10,20], "A1" => [30,40]], "B" => ["A0" => [50,60], "A1" => [70,80]]]
];
*/
$r = [];
foreach ($nest as $v1) {
foreach ($v1 as $v2) {
foreach ($v2 as $k => $v3) {
if ($k === "A0") {
$r[] = $v3[1];
}
}
}
}
print_r($r);
$r = [];
foreach ($nest as $v1) {
foreach ($v1 as $v2) {
if (isset($v2["A0"][1])) {
$r[] = $v2["A0"][1];
}
}
}
print_r($r);
Python3の場合
PHPとほぼ同様な書き方。
import json
from collections import OrderedDict
nest = json.load(open("nest.json"), object_pairs_hook=OrderedDict)
"""
nest = {
"a": {"A": {"A0": [1,2], "A1": [3,4]}, "B": {"A0": [5,6], "A1": [7,8]}},
"b": {"A": {"A0": [10,20], "A1": [30,40]}, "B": {"A0": [50,60], "A1": [70,80]}}
}
"""
r = []
for v1 in nest.values():
for v2 in v1.values():
for k, v3 in v2.items():
if k == "A0" and len(v3) > 1:
r.append(v3[1])
print(r)
r = []
for v1 in nest.values():
for v2 in v1.values():
if "A0" in v2 and len(v2["A0"]) > 1:
r.append(v2["A0"][1])
print(r)
yield from
で実現した場合
PHPもPythonも同じ様な書き方で実現できる。
PHP7の場合
比較的シンプルになる。配列の階層がさらに深くなってもループ処理を追加する必要がなくなる。
<?php
$nest = json_decode(file_get_contents("nest.json"), true);
function zslice ($n)
{
foreach($n as $k => $v) {
yield from $v;
}
}
$r = [];
foreach(zslice(zslice($nest)) as $k => $v) {
if ($k == "A0") {
$r[] = $v[1];
}
}
print_r($r);
$r = [];
foreach(zslice($nest) as $k => $v) {
if (isset($v["A0"][1])) {
$r[] = $v["A0"][1];
}
}
print_r($r);
Python3の場合
dict
で扱っているため、若干複雑な処理が必要だが、基本はPHP7と同様の実現方法となる。
import json
from collections import OrderedDict
nest = json.load(open("nest.json"), object_pairs_hook=OrderedDict)
def zslice(n):
r = n.values() if isinstance(n, dict) else n
for v in r:
if isinstance(v, dict):
d = v.items()
elif isinstance(v, tuple) and len(v) > 1:
d = v[1].items()
else:
raise ValueError
yield from d
r = []
for k, v in zslice(zslice(nest)):
if k == "A0" and len(v) > 1:
r.append(v[1])
print(r)
r = []
for k, v in zslice(nest):
if "A0" in v and len(v["A0"]) > 1:
r.append(v["A0"][1])
print(r)
その他
下記のようにzslice
を分けたほうがシンプルで可読性が良いかもしれない。
import json
from collections import OrderedDict
nest = json.load(open("nest.json"), object_pairs_hook=OrderedDict)
def zslice1(n):
for v in n.values():
yield from v.items()
def zslice2(n):
for k, v in n:
yield from v.items()
r = []
for k, v in zslice2(zslice1(nest)):
if k == "A0" and len(v) > 1:
r.append(v[1])
print(r)
r = []
for k, v in zslice1(nest):
if "A0" in v and len(v["A0"]) > 1:
r.append(v["A0"][1])
print(r)
出力結果
ここで記載されているPHPとPythonサンプルコードの出力結果を記載。
PHPの場合
Array
(
[0] => 2
[1] => 6
[2] => 20
[3] => 60
)
Array
(
[0] => 2
[1] => 6
[2] => 20
[3] => 60
)
Pythonの場合
[2, 6, 20, 60]
[2, 6, 20, 60]
補足:Pythonの辞書の並び順について
json.load
の第二引数にobject_pairs_hook=OrderedDict
を指定しない場合、サンプルを何度か実行していると要素の順序が異なる場合がある。
[60, 20, 6, 2]
[60, 20, 6, 2]
[6, 2, 60, 20]
[6, 2, 60, 20]
dict
作成時に要素の順序が保証されないので、整合性を保つにはOrderedDict
を利用する。