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

#PHPとPythonでジェネレータを利用してネストループ表記を回避する
下記のようなJSONをデコードして利用する場合、ネスト（入れ子）でループ処理を記載しまうことがある。

```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
<?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とほぼ同様な書き方。

```py
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
<?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と同様の実現方法となる。

```py
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`を分けたほうがシンプルで可読性が良いかもしれない。

```py
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の場合

```text:
Array
(
    [0] => 2
    [1] => 6
    [2] => 20
    [3] => 60
)
Array
(
    [0] => 2
    [1] => 6
    [2] => 20
    [3] => 60
)
```

###Pythonの場合
```text:出力結果
[2, 6, 20, 60]
[2, 6, 20, 60]
```

####補足：Pythonの辞書の並び順について
`json.load`の第二引数に`object_pairs_hook=OrderedDict`を指定しない場合、サンプルを何度か実行していると要素の順序が異なる場合がある。

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

```text:パターン2
[6, 2, 60, 20]
[6, 2, 60, 20]
```

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