Go
golang

goで構造体内に定義した配列をネストして値を取得するやり方

構造体内に別構造体の配列を定義して、その配列がネストしていくようなイメージのものを作って再帰的に値を取り出してみたかったので試してみました。

イメージ

やりたかったのは、配列の中に配列が...さらに配列が...のようなイメージのものをやってみたかった。
pythonで言うと以下のようなイメージ(ざっくり感がパない)

example.py
#!/usr/bin/env python3
array = [1, 2, 3]
array2 = [4, 5, 6]
array3 = [7,8,9]
array2.append(array3)
array.append(array2)

print(array)

def rec(a):
    for i in a:
        if(isinstance(i, list)):
            rec(i)
        else:
           print(i)

for i in array:
    if(isinstance(i, list)):
        rec(i)
    else:
       print(i)

これを実行すると以下のような結果になります。
classを作ってやったほうがイメージが近いのではと思いましたがめんd

$ ./example.py
[1, 2, 3, [4, 5, 6, [7, 8, 9]]]
1
2
3
4
5
6
7
8
9

ようは、配列の要素を一つずつ取り出して行ってもし配列型だった場合は再度forを回して再帰的に値を取得していくものがGoで実装したかった!!

使用する構造体について

ここでは TestChildTests の構造体を例にしてみたいと思います。
Testの Nameフィールド で定義した名前を親にして、それの子を ChildTestsフィールド に定義していく形にします。

type Test struct {
    Name       string
    ChildTests []ChildTests
}

type ChildTests struct {
    Name       string
    ChildTests []ChildTests
}

ChildTests構造体の ChildTestsフィールド は自身のスライスを定義しているので以下のようにネストするような実装が可能になります。

s := []Test{
    Test{
        Name: "HOGE",
        ChildTests: []ChildTests{
            ChildTests{
                Name: "FUGA",
                ChildTests: []ChildTests{
                    ChildTests{
                        Name: "HIGE",
                    },
                },
            },
        },
    },
}

実装例

親には HOGETaro を指定して、それぞれの子を設定して実行してみます。
Test構造体 のスライスを作って RecursiveCall 関数に渡して子が1個以上ある場合は GetChild に渡して再帰的に値を取得しています。
GetChild の引数は interface として受け取って reflect を駆使してやってみました。。。

main.go
package main

import (
    "fmt"
    "reflect"
)

type Test struct {
    Name       string
    ChildTests []ChildTests
}

type ChildTests struct {
    Name       string
    ChildTests []ChildTests
}

func GetChild(ts interface{}, c int) {
    s := reflect.ValueOf(ts)
    v := reflect.Indirect(s.Index(0))
    t := v.Type()
    for i := 0; i < t.NumField(); i++ {
        if t.Field(i).Name == "Name" {
            c += 2
            for i := 0; i < c; i++ {
                fmt.Print(" ")
            }
            fmt.Println(v.Field(i).String())
        }
        if t.Field(i).Name == "ChildTests" && v.Field(i).Len() > 0 {
            GetChild(v.Field(i).Interface(), c)
        }
    }
}

func RecursiveCall(t []Test) {
    for _, i := range t {
        fmt.Println(i.Name)
        if len(i.ChildTests) >= 1 {
            GetChild(i.ChildTests, 0)
        }
    }
}

func main() {
    s := []Test{
        Test{
            Name: "HOGE",
            ChildTests: []ChildTests{
                ChildTests{
                    Name: "FUGA",
                    ChildTests: []ChildTests{
                        ChildTests{
                            Name: "HIGE",
                            ChildTests: []ChildTests{
                                ChildTests{
                                    Name: "HHH",
                                },
                            },
                        },
                    },
                },
            },
        },
        Test{
            Name: "Taro",
            ChildTests: []ChildTests{
                ChildTests{
                    Name: "Hanako",
                },
            },
        },
    }
    RecursiveCall(s)
}

実行結果

子がある場合は、スペースでインデントを入れています。

$ go run main.go
HOGE
  FUGA
    HIGE
      HHH
Taro
  Hanako

今回は、結構力任せでやっていますが期待していたことが出来たのでひとまずこれでよしとしよう。。。