LoginSignup
14
5

More than 5 years have passed since last update.

PHPとPythonでネストループを回避する

Last updated at Posted at 2017-06-07

PHPとPythonでジェネレータを利用してネストループ表記を回避する

下記のようなJSONをデコードして利用する場合、ネスト(入れ子)でループ処理を記載しまうことがある。

nest.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を指定しない場合、サンプルを何度か実行していると要素の順序が異なる場合がある。

パターン1
[60, 20, 6, 2]
[60, 20, 6, 2]
パターン2
[6, 2, 60, 20]
[6, 2, 60, 20]

dict作成時に要素の順序が保証されないので、整合性を保つにはOrderedDictを利用する。

14
5
3

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
14
5