やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

ツリー構造の試作

 超適当。

成果物

テスト用ツリー

  • A
    • A1
      • A11
    • A2
  • B
    • B1

 上記のようなツリー構造を作ってみた。

コード

run.sh

csc -nologo -nullable:enable -langversion:preview *.cs
chmod 755 ./Program.exe
./Program.exe

 インデクサでツリー構造のパスを指定している。

Program.cs

class Program
{
    static void Main(string[] args)
    {
        var tree = new Tree<string>();
        tree["A"] = "A";
        tree["A/A1"] = "A1";
        tree["A/A1/A11"] = "A11";
        tree["A/A2"] = "A2";
        tree["B"] = "B";
        tree["B/B1"] = "B1";
        Console.WriteLine($"tree.Root.Children.Count: {tree.Root.Children.Count}");
        ShowTree(tree);
    }
    static void ShowTree(Tree<string> tree)
    {
        foreach (var node in tree.Root.Children) { ShowNode(node); }
    }
    static void ShowNode(Node<string> node, int index=0)
    {
        if (null != node) { Console.WriteLine($"{(new string('*', index)).Replace("*", "    ")}{node.Value}"); }
        if (null != node?.Children) {
            index++;
            foreach (var n in node.Children) { ShowNode(n, index); }
        }
    }
}

CodeTree.cs

public class Tree<V>
{
    public static readonly char DELIMITER = '/';
    public char Delimiter { get; protected set; }
    protected IList<Node<V>> children { get; set; }
    public IReadOnlyList<Node<V>> Children { get; }
    public Node<V> Root { get; protected set; }
    public V this[string path] {
        set {
            // 未存なら新規生成
            // 既存なら指定値を代入する。インデクサで指定した位置にあるNodeに。
            FindAndNewNode(path).Value = value;
        }
        get {
            // 未存なら新規生成
            // 既存なら値を返す。インデクサで指定した位置にあるNodeの。
            return FindAndNewNode(path).Value;
        }
    }
    public Tree(char delimiter='/')
    {
        Delimiter = delimiter;
        children = new List<Node<V>>();
        Children = new ReadOnlyCollection<Node<V>>(children);
        Root = new Node<V>("Root");
    }
    private Node<V> FindAndNewNode(string path)
    {
        var first = path.Split(DELIMITER)[0];
        foreach (var child in Root.Children) {
            if (first == child.Key) { return child.FindAndNewNode(path, 0, child); }
        }
        return Root.FindAndNewNode(path, 0, Root);
    }
}

CodeNode.cs

public class Node<V>
{
    public string Key { get; protected set; }
    public virtual V Value { get; set; }
    public Node<V> Parent { get; set; }
    protected IList<Node<V>> children { get; set; }
    public IReadOnlyList<Node<V>> Children { get; }
    public Node<V> Add(string key, V value=default) {
        foreach (var child in Children) {
            if (child.Key == key) { return this; } // 既存なら何もしない
        }
        children.Add(new Node<V>(key, value, this)); // 未存なら新規生成する
        return this;
    }

    public V this[string path] {
        set {
            if (String.IsNullOrEmpty(path)) { throw new Exception("インデクサには1字以上の文字列を指定してください。"); } 
            // 未存なら新規生成または例外またはnull
            // 既存なら指定値を代入する。インデクサで指定した位置にあるNodeに。
            FindAndNewNode(path, 0, this).Value = value;
        }
        get {
            if (String.IsNullOrEmpty(path)) { throw new Exception("インデクサには1字以上の文字列を指定してください。"); } 
            // 未存なら新規生成または例外またはnull
            // 既存なら値を返す。インデクサで指定した位置にあるNodeの。
            return FindAndNewNode(path, 0, this).Value;
        }
    }
// https://www.infoq.com/jp/articles/csharp-nullable-reference-case-study/
#nullable disable
    public Node(string key, V value=default, Node<V> parent=default)
#nullable enable
    {
        Parent = parent; Key = key; Value = value;
        children = new List<Node<V>>();
        Children = new ReadOnlyCollection<Node<V>>(children);
    }
    public Node<V>? FindNode(in string path, int depth, in Node<V> node)
    {
        string[] keys = path.Split(Tree<V>.DELIMITER);
        if (depth == keys.Length -1) {
            if (node.Key == keys[depth]) { return node; }
//                else { throw new NotExist(); }
            else { return null; }
        }
        else {
            foreach (var child in node.Children) {
                if (child.Key == keys[depth]) {
                    FindNode(path, ++depth, child);
                }
            }
        }
//            throw new NotExist();
        return null;
    }
#nullable disable
    public Node<V> FindAndNewNode(in string path, int depth, in Node<V> node)
    {
        string[] keys = path.Split(Tree<V>.DELIMITER);
        Console.WriteLine($"{path} {depth} {node?.Key} {keys[depth]}");
        if (depth == keys.Length -1) {
            if (node.Key == keys[depth]) { return node; }
            else {
                // 未存なら新規生成
                var youngest_brother = new Node<V>(keys[depth], default(V), (null == node.Parent) ? node : node.Parent);
                if (null == node.Parent) { // nodeがルートなら
                    node.children.Add(youngest_brother);
                } else {
                    node.Parent.children.Add(youngest_brother);
                }
                return youngest_brother;
            }
        }
        else {
            depth++;
            Node<V> target = null;
            if (node.Key == keys[depth]) { target = FindNode(path, depth, node); }
            else {
                foreach (var child in node.Children) {
                    if (child.Key == keys[depth]) {
                        target = FindAndNewNode(path, depth, child);
                        break;
                    }
                }
                // 未存なら新規生成
                if (null == target) {
                    var child = new Node<V>(keys[depth]);
                    node.children.Add(child);
                    target = FindAndNewNode(path, depth, child);
                }
            }
            return target;
        }
    }
#nullable enable
}

出力結果

A
    A1
        A11
    A2
B
    B1

 デバッグ出力を取り除いた抜粋。それっぽくできている。

既存バグ

Program.cs

class Program
{
    static void Main(string[] args)
    {
        var tree = new Tree<string>();
//        tree["A"] = "A";
//        tree["A/A1"] = "A1";
        tree["A/A1/A11"] = "A11";
        tree["A/A2"] = "A2";
//        tree["B"] = "B";
        tree["B/B1"] = "B1";
        Console.WriteLine($"tree.Root.Children.Count: {tree.Root.Children.Count}");
        ShowTree(tree);
    }

 親を作らず、先に子をつくるとバグる。

所感

 コード汚すぎ。もう何書いているかわかんない。ハードルが高い。一旦C#ツアーに戻って基礎学習しよう。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux