none
動的に変数を宣言する方法 RRS feed

  • 質問

  • 環境
    win10
    visualstudio2017

    フォーラムに投稿されているタイトル(linqの再帰について※)のような
    プログラムを組みたいのですが、組み方がわからず、アドバイスがほしいです。

    ※ごめんなさい。リンクの貼り方がわかりません。

    経路データをcsvより取得しています。
    ・1レコードにFrom、To列が存在します。
    ・経路に分岐があれば、同じFrom、もしくは同じToが存在します。
    ・レコードの順番は経路順ではないため、バラバラです。

    例の始まりーーーーーーーーーーーーーーーーーーーーー
    static Route GetRoot()
    {
                Point start = new Point("Start");
                Point p01 = new Point(1);
                Point p02 = new Point(2);
                Point p03 = new Point(3);
                Point p04 = new Point(4);
                Point p05 = new Point(5);
                Point p06 = new Point(6);
                Point p07 = new Point(7);
                Point p08 = new Point(8);
                Point p09 = new Point(9);
                Point p10 = new Point(10);
                Point p11 = new Point(11);
                Point p12 = new Point(12);
                Point p13 = new Point(13);
                Point p14 = new Point(14);
                Point p15 = new Point(15);
                Point end = new Point("End");

                //地点間距離に関する定義が提示されていないので適当
                start.Add(p01, 0);

                p01.Add(p02, 2);
                p01.Add(p03, 3);

                p02.Add(p15, 15);

                p03.Add(p04, 4);
                p03.Add(p12, 4);

                p04.Add(p05, 5);
                p04.Add(p10, 10);

                p05.Add(p06, 6);

                p06.Add(p07, 7);

                p07.Add(p08, 8);

                p08.Add(p09, 9);

                p09.Add(end, 0);

                p10.Add(p11, 11);
                p11.Add(p08, 8);

                p12.Add(p13, 13);
                p13.Add(p14, 14);

                p14.Add(p11, 11);
                p15.Add(p12, 12);

                return new Route() { Point = start, Weight = 0 };
    }

    例の終わりーーーーーーーーーーーーーーーーーーーーー


    投稿主がやっているように、経路の距離を導き出したいです。

    gekkaさんが示している例のように、変数に格納したいのですが
    どのくらい座標が存在するかは不明ですし、レコード順もバラバラです。

    そのため、上記のプログラムを動的にやる場合、どのように記載すればよいのか
    アドバイス、または参考になるサイトを教えてください。

    また、例では同じIDを持つ変数の場合、同じ階層に格納していますが
    本当に同じようにやりたいです。


    よろしくお願いします。

    2019年9月15日 17:40

回答

  • Listや配列に格納すればいいだけです

    namespace ConsoleApp1
    {
        using System;
        using System.Linq;
        using System.Collections.Generic;
    
        class Program
        {
            static void Main(string[] args)
            {
                System.IO.StringReader reader = new System.IO.StringReader(GetTestCSVString());
    
                Route start = GetRoot(reader, "Start", true);
                Console.WriteLine(start.ToStringMinRoute());
                Console.WriteLine(start.ToStringMaxRoute());
                Console.ReadLine();
            }
    
            private static string GetTestCSVString()
            {
                return
                "From,To,Weight" + "\r\n"
                + "Start,1,1" + "\r\n"
                + "1,3,3" + "\r\n"
                + "3,4,4" + "\r\n"
                + "4,5,5" + "\r\n"
                + "5,6,6" + "\r\n"
                + "6,7,7" + "\r\n"
                + "7,8,8" + "\r\n"
                + "8,9,9" + "\r\n"
                + "9,End,0" + "\r\n"
                + "4,10,10" + "\r\n"
                + "10,11,11" + "\r\n"
                + "11,8,8" + "\r\n"
                + "3,12,12" + "\r\n"
                + "12,13,13" + "\r\n"
                + "13,14,14" + "\r\n"
                + "14,11,11" + "\r\n"
                + "1,2,2" + "\r\n"
                + "2,15,15" + "\r\n"
                + "15,12,12" + "\r\n";
            }
    
            static Route GetRoot(System.IO.TextReader tr, string rootID = "Start", bool skipHeader = true)
            {
                List<Record> records = new List<Record>();
                while (tr.Peek() != -1)
                {
                    string line = tr.ReadLine();
                    if (skipHeader)
                    {
                        skipHeader = false;
                        continue;
                    }
                    string[] parts = line.Split(',');
    
                    //CSVの行の値をRecordクラスに格納
                    Record record = new Record();
                    record.From = parts[0];
                    record.To = parts[1];
                    record.Weight = int.Parse(parts[2]);
    
                    records.Add(record);
                }
    
                //FromとToの重複しない位置の一覧を作る
                object[] pointIDs = records.SelectMany(record => new object[] { record.From, record.To }).Distinct().ToArray();
    
                //位置に対応するPointクラスの一覧を作る
                List<Point> points = pointIDs.Select(id => new Point(id)).ToList();
    
                //recordのFromとToに対応するPointを探して設定
                foreach (Record record in records)
                {
                    record.FromPoint = points.FirstOrDefault(p => object.Equals(p.Id, record.From));
                    record.ToPoint = points.FirstOrDefault(p => object.Equals(p.Id, record.To));
                }
    
                //Fromの位置をキーにしてグループ化
                foreach (IGrouping<Point, Record> group in records.GroupBy(record => record.FromPoint))
                {
                    //グループから行き先の一覧を作る
                    IEnumerable<Route> routes = group.Select(record => new Route() { Point = record.ToPoint, Weight = record.Weight });
    
                    //行き先の一覧を格納
                    Point from= group.Key;
                    from.List.AddRange(routes);
                }
    
                //開始位置を探す
                Point start = points.FirstOrDefault(p => object.Equals(p.Id.ToString(), rootID));
    
                //結果として返す
                return new Route() { Point = start, Weight = 0 };
            }
        }
        class Record
        {
            public object From;
            public object To;
            public int Weight;
    
    
            public Point FromPoint;
            public Point ToPoint;
    
    
        }
    
        class Point
        {
            public Point(object id)
            {
                this.Id = id;
                List = new List<Route>();
            }
            public object Id { get; set; }
            public List<Route> List { get; set; }
            public void Add(Point p, int weight)
            {
                List.Add(new Route() { Point = p, Weight = weight });
            }
        }
    
        class Route
        {
            /// <summary>位置</summary>
            public Point Point { get; set; }
    
            /// <summary>位置までの距離</summary>
            public int Weight { get; set; }
    
            /// <summary>ここからEndまでの最短距離</summary>
            public int Min
            {
                get
                {
                    return (Point.List.Count == 0 ? 0 : Point.List.Min(_ => _.Min)) + this.Weight;
                }
            }
    
            /// <summary>ここからEndまでの最長距離</summary>
            public int Max
            {
                get
                {
                    return (Point.List.Count == 0 ? 0 : Point.List.Max(_ => _.Max)) + this.Weight;
                }
            }
    
            /// <summary>最短距離の順路</summary>
            public IEnumerable<Route> GetMinRoute()
            {
                if (this.Point.List.Count == 0)
                {
                    return new Route[] { this };
                }
                //return new Route[] { this }.Union(this.Point.List.OrderBy(r => r.Min).First().GetMinRoute());
                return new Route[] { this }.Union(this.Point.List.OrderBy(r => (r.Point.List.Count == 0 ? 0 : r.Point.List.Min(_ => _.Min)) + this.Weight).First().GetMinRoute());
            }
    
            /// <summary>最長距離の順路</summary>
            public IEnumerable<Route> GetMaxRoute()
            {
                if (this.Point.List.Count == 0)
                {
                    return new Route[] { this };
                }
                //return new Route[] { this }.Union(this.Point.List.OrderBy(r => r.Max).Last().GetMaxRoute());
                return new Route[] { this }.Union(this.Point.List.OrderBy(r => (r.Point.List.Count == 0 ? 0 : r.Point.List.Max(_ => _.Max)) + this.Weight).Last().GetMaxRoute());
            }
    
            public string ToStringMinRoute()
            {
                return "Min:" + this.Min.ToString() + "\t" + string.Join("=>", this.GetMinRoute().Select(_ => _.Point.Id.ToString()));
            }
            public string ToStringMaxRoute()
            {
                return "Max:" + this.Max.ToString() + "\t" + string.Join("=>", this.GetMaxRoute().Select(_ => _.Point.Id.ToString()));
            }
    
        }
    }
    

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク supura 2019年9月17日 7:27
    2019年9月16日 2:32

すべての返信

  • Listや配列に格納すればいいだけです

    namespace ConsoleApp1
    {
        using System;
        using System.Linq;
        using System.Collections.Generic;
    
        class Program
        {
            static void Main(string[] args)
            {
                System.IO.StringReader reader = new System.IO.StringReader(GetTestCSVString());
    
                Route start = GetRoot(reader, "Start", true);
                Console.WriteLine(start.ToStringMinRoute());
                Console.WriteLine(start.ToStringMaxRoute());
                Console.ReadLine();
            }
    
            private static string GetTestCSVString()
            {
                return
                "From,To,Weight" + "\r\n"
                + "Start,1,1" + "\r\n"
                + "1,3,3" + "\r\n"
                + "3,4,4" + "\r\n"
                + "4,5,5" + "\r\n"
                + "5,6,6" + "\r\n"
                + "6,7,7" + "\r\n"
                + "7,8,8" + "\r\n"
                + "8,9,9" + "\r\n"
                + "9,End,0" + "\r\n"
                + "4,10,10" + "\r\n"
                + "10,11,11" + "\r\n"
                + "11,8,8" + "\r\n"
                + "3,12,12" + "\r\n"
                + "12,13,13" + "\r\n"
                + "13,14,14" + "\r\n"
                + "14,11,11" + "\r\n"
                + "1,2,2" + "\r\n"
                + "2,15,15" + "\r\n"
                + "15,12,12" + "\r\n";
            }
    
            static Route GetRoot(System.IO.TextReader tr, string rootID = "Start", bool skipHeader = true)
            {
                List<Record> records = new List<Record>();
                while (tr.Peek() != -1)
                {
                    string line = tr.ReadLine();
                    if (skipHeader)
                    {
                        skipHeader = false;
                        continue;
                    }
                    string[] parts = line.Split(',');
    
                    //CSVの行の値をRecordクラスに格納
                    Record record = new Record();
                    record.From = parts[0];
                    record.To = parts[1];
                    record.Weight = int.Parse(parts[2]);
    
                    records.Add(record);
                }
    
                //FromとToの重複しない位置の一覧を作る
                object[] pointIDs = records.SelectMany(record => new object[] { record.From, record.To }).Distinct().ToArray();
    
                //位置に対応するPointクラスの一覧を作る
                List<Point> points = pointIDs.Select(id => new Point(id)).ToList();
    
                //recordのFromとToに対応するPointを探して設定
                foreach (Record record in records)
                {
                    record.FromPoint = points.FirstOrDefault(p => object.Equals(p.Id, record.From));
                    record.ToPoint = points.FirstOrDefault(p => object.Equals(p.Id, record.To));
                }
    
                //Fromの位置をキーにしてグループ化
                foreach (IGrouping<Point, Record> group in records.GroupBy(record => record.FromPoint))
                {
                    //グループから行き先の一覧を作る
                    IEnumerable<Route> routes = group.Select(record => new Route() { Point = record.ToPoint, Weight = record.Weight });
    
                    //行き先の一覧を格納
                    Point from= group.Key;
                    from.List.AddRange(routes);
                }
    
                //開始位置を探す
                Point start = points.FirstOrDefault(p => object.Equals(p.Id.ToString(), rootID));
    
                //結果として返す
                return new Route() { Point = start, Weight = 0 };
            }
        }
        class Record
        {
            public object From;
            public object To;
            public int Weight;
    
    
            public Point FromPoint;
            public Point ToPoint;
    
    
        }
    
        class Point
        {
            public Point(object id)
            {
                this.Id = id;
                List = new List<Route>();
            }
            public object Id { get; set; }
            public List<Route> List { get; set; }
            public void Add(Point p, int weight)
            {
                List.Add(new Route() { Point = p, Weight = weight });
            }
        }
    
        class Route
        {
            /// <summary>位置</summary>
            public Point Point { get; set; }
    
            /// <summary>位置までの距離</summary>
            public int Weight { get; set; }
    
            /// <summary>ここからEndまでの最短距離</summary>
            public int Min
            {
                get
                {
                    return (Point.List.Count == 0 ? 0 : Point.List.Min(_ => _.Min)) + this.Weight;
                }
            }
    
            /// <summary>ここからEndまでの最長距離</summary>
            public int Max
            {
                get
                {
                    return (Point.List.Count == 0 ? 0 : Point.List.Max(_ => _.Max)) + this.Weight;
                }
            }
    
            /// <summary>最短距離の順路</summary>
            public IEnumerable<Route> GetMinRoute()
            {
                if (this.Point.List.Count == 0)
                {
                    return new Route[] { this };
                }
                //return new Route[] { this }.Union(this.Point.List.OrderBy(r => r.Min).First().GetMinRoute());
                return new Route[] { this }.Union(this.Point.List.OrderBy(r => (r.Point.List.Count == 0 ? 0 : r.Point.List.Min(_ => _.Min)) + this.Weight).First().GetMinRoute());
            }
    
            /// <summary>最長距離の順路</summary>
            public IEnumerable<Route> GetMaxRoute()
            {
                if (this.Point.List.Count == 0)
                {
                    return new Route[] { this };
                }
                //return new Route[] { this }.Union(this.Point.List.OrderBy(r => r.Max).Last().GetMaxRoute());
                return new Route[] { this }.Union(this.Point.List.OrderBy(r => (r.Point.List.Count == 0 ? 0 : r.Point.List.Max(_ => _.Max)) + this.Weight).Last().GetMaxRoute());
            }
    
            public string ToStringMinRoute()
            {
                return "Min:" + this.Min.ToString() + "\t" + string.Join("=>", this.GetMinRoute().Select(_ => _.Point.Id.ToString()));
            }
            public string ToStringMaxRoute()
            {
                return "Max:" + this.Max.ToString() + "\t" + string.Join("=>", this.GetMaxRoute().Select(_ => _.Point.Id.ToString()));
            }
    
        }
    }
    

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク supura 2019年9月17日 7:27
    2019年9月16日 2:32
  • gekkaさん

    サンプルソース有難う御座います。

    今の業務は、Start~Endポイントまで最も距離がある経路を割り出すことですが、
    下記の仕様も含まれます。
     ・Endポイントが複数ある。
     ・Endポイント以降の座標データもCSVの中に存在する。
     ※CSVにEndポイント列があり、Trueの座標までを計算する仕様

    gekkaさんが示してくれたサンプルは
    自分が最初に投稿した仕様を完璧に実現してくれています。
    本当に助かりました。
    ※「Listや配列に格納すればいいだけです」と、さも簡単に記載されていますが
     自分には到底実現出来ませんでした・・・・

    あとは、上記の仕様を組み入れたいと思いますが、
    また躓いたときにアドバイスを頂けると助かります。


    本当に有難う御座いました。
    2019年9月17日 7:27