このヘージと関連するページ一覧

  • LINQ - Select
  • LINQ - GroupBy
  • LINQ - Join

  • このページの最終更新日 2020/10/25

    [C#] LINQ - Join

    1. データファイルを準備しよう

     CAE解析においてソルバーが読み書きするファイルがどのような形式になっているか考えてみます。良くある形式として、ソルバーが読み取るメッシュによってモデルを作成したファイルには、メッシュを構成するエレメントIDとそのエレメントIDがどのような形状特性を持っているかを定義されたプロパティIDの組み合わせが書かれています。ソルバーの解析結果のファイルはエレメントと力の関係がかかれていがプロパティは書かれていないという場合が執筆者の経験上は多いように思います。ここで各プロパティにIDついて力の最大値を取得したいというような場合があります。
     しかし、プロパティIDと力の関係は直接は書かれておらず、ソルバーが読み取るファイルにはプロパティIDとエレメントIDの完成、ソルバーが出力するファイルにはエレメントIDと力の関係が記載されています。よって、プロパティIDと力の関係を知るためには、ソルバーが読み取るファイルとソルバーが出力するファイルの両方を組み合わせる必要があります。以下では、ソルバーが読み取るモデルの情報が記載されたファイルを「インプットファイル」、ソルバーが出力する解析結果が書かれたファイルを「アウトプット」ファイルと表現することにします。

     まず、インプットファイルを模擬したデータをエクセルでpic.1のように作成します。PropertyIdとElmentIdの対応表を作成して文字コードUTF8でCSVに出力します。ファイル名は「PropertyElementPair.csv」とします。PropetyIdは1~3、ElementIdは1~10までとします。

    PropetyIdとElementIdの対応表

    - pic.1 -

     次に、アウトプットファイルを模擬したデータをエクエルでpic.2のように作成します。力の部分はエクセルのRAND関数等を使って入力しておきましょう。力の値はpic.2のようになっていなくても問題はありません。エクセルでElementIdとForceの対応表を作成し、文字コードUTF8でCSVに出力します。ファイル名は「ElementForcePair.csv」とします。

    ElementIdとForceの対応表

    - pic.2 -


     最終的に考えたいことは各プロパティID毎の力の最大の値は何であるかという問題ですが、まずは焦らずCSVファイルを読み込めているかどうかを確認してみましょう。code.1を実行して次のようにCSVファイルの中身が、Listの中に取り込まれていることを確認して下さい。

    - code.1 -
    public class PropertyElementPair
    {
        public int ElementId { get; set; }
        public int PropertyId { get; set; }
        public override string ToString()
        {
            return string.Format("PropertyId={0}, ElementId={1}", this.PropertyId, this.ElementId);
        }
    }
    
    public class ElementForcePair
    {
        public int ElementId { get; set; }
        public double Force { get; set; }
        public override string ToString()
        {
            return string.Format("ElementId={0}, Force={1}", this.ElementId, this.Force);
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var propertyElementList = new List<PropertyElementPair>();
                var elementForceList = new List<ElementForcePair>();
                string propertyElementFile = @"C:\Users\*****\PropertyElementPair.csv"; //*****は各自の作業フォルダ
                string elementForceFile = @"C:\Users\*****\ElementForcePair.csv"; //*****は各自の作業フォルダ
    
                //プロパティIDとエレメントIDの対をListに加えていく。
                using (var sr = new System.IO.StreamReader(propertyElementFile, Encoding.UTF8))
                {
                    sr.ReadLine();
                    while (!sr.EndOfStream)
                    {
                        string[] propertyElementOneline = sr.ReadLine().Split(',');
                        propertyElementList.Add(new PropertyElementPair()
                        {
                            PropertyId = int.Parse(propertyElementOneline[0]),
                            ElementId = int.Parse(propertyElementOneline[1])
                        });
                    }
                }
    
                //エレメントIDと力の対をListに加えていく。
                using (var sr = new System.IO.StreamReader(elementForceFile, Encoding.UTF8))
                {
                    sr.ReadLine();
                    while (!sr.EndOfStream)
                    {
                        string[] propertyElementOneline = sr.ReadLine().Split(',');
                        elementForceList.Add(new ElementForcePair()
                        {
                            ElementId = int.Parse(propertyElementOneline[0]),
                            Force = double.Parse(propertyElementOneline[1])
                        });
                    }
                }
    
                propertyElementList.ForEach(m => Console.WriteLine(m));
                Console.WriteLine("***** ***** *****");
                elementForceList.ForEach(m => Console.WriteLine(m));
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
    
            Console.ReadKey();
        }
    }

     62行目~64行目でリスト内の要素を出力しています。「コンソールの出力結果1」のようになっていれば、適切にCSVファイルを読み込むことが出来ています。

    - コンソールの出力結果 -
    PropertyId=1, ElementId=1
    PropertyId=1, ElementId=2
    PropertyId=1, ElementId=3
    PropertyId=2, ElementId=4
    PropertyId=2, ElementId=5
    PropertyId=2, ElementId=6
    PropertyId=3, ElementId=7
    PropertyId=3, ElementId=8
    PropertyId=3, ElementId=9
    PropertyId=3, ElementId=10
    ***** ***** *****
    ElementId=1, Force=0.429844649
    ElementId=2, Force=0.849095933
    ElementId=3, Force=0.833987789
    ElementId=4, Force=0.889480799
    ElementId=5, Force=0.232153408
    ElementId=6, Force=0.739578496
    ElementId=7, Force=0.997620865
    ElementId=8, Force=0.914703127
    ElementId=9, Force=0.327499172
    ElementId=10, Force=0.229693679
    

    2. LINQのJoin句の動作を確認しよう

     プロパティIDと力の関係を知りたいのですが、この2つの関係を繋いでいるものはエレメントIDになります。ここでLINQのJoin句を使って、プロパティIDとエレメントIDと力の値のセットを作成してみましょう。21行目から30行目で、プロパティIDとエレメントIDと力の3つのプロパティを持った新たなクラスを定義しておきます。実際にはここまで細かなクラスを作るのは少し冗長で、無名クラス等を使うなどしてもうコードを短く書いていく方がベターかもしれませんが、ここではJoinの動作を確認するため敢えて細かくクラスを分けています。73行目が今回の解析の要点となる「Join」となります。73行目の「left」は「PropertyElementPair」のオブジェクトになります。「right」は「ElementForcePair」のオブジェクトとなっています。

    - code.2 -
    public class PropertyElementPair
    {
        public int PropertyId { get; set; }
        public int ElementId { get; set; }
        public override string ToString()
        {
            return string.Format("PropertyId={0}, ElementId={1}", this.PropertyId, this.ElementId);
        }
    }
    
    public class ElementForcePair
    {
        public int ElementId { get; set; }
        public double Force { get; set; }
        public override string ToString()
        {
            return string.Format("ElementId={0}, Force={1}", this.ElementId, this.Force);
        }
    }
    
    public class PropertyElementForceSet
    {
        public int PropertyId { get; set; }
        public int ElementId { get; set; }
        public double Force { get; set; }
        public override string ToString()
        {
            return string.Format("PropertyId={0}, ElementId={1}, Force={2}", this.PropertyId, this.ElementId, this.Force);
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var propertyElementList = new List<PropertyElementPair>();
                var elementForceList = new List<ElementForcePair>();
                string propertyElementFile = @"C:\Users\*****\PropertyElementPair.csv"; //*****は各自の作業フォルダ
                string elementForceFile = @"C:\Users\*****\ElementForcePair.csv"; //*****は各自の作業フォルダ
    
                //プロパティIDとエレメントIDの対をListに加えていく。
                using (var sr = new System.IO.StreamReader(propertyElementFile, Encoding.UTF8))
                {
                    sr.ReadLine();
                    while (!sr.EndOfStream)
                    {
                        string[] propertyElementOneline = sr.ReadLine().Split(',');
                        propertyElementList.Add(new PropertyElementPair()
                        {
                            PropertyId = int.Parse(propertyElementOneline[0]),
                            ElementId = int.Parse(propertyElementOneline[1])
                        });
                    }
                }
    
                //エレメントIDと力の対をListに加えていく。
                using (var sr = new System.IO.StreamReader(elementForceFile, Encoding.UTF8))
                {
                    sr.ReadLine();
                    while (!sr.EndOfStream)
                    {
                        string[] propertyElementOneline = sr.ReadLine().Split(',');
                        elementForceList.Add(new ElementForcePair()
                        {
                            ElementId = int.Parse(propertyElementOneline[0]),
                            Force = double.Parse(propertyElementOneline[1])
                        });
                    }
                }
    
                IEnumerable<PropertyElementForceSet> propertyElementForceList = propertyElementList.Join(elementForceList, left => left.ElementId, right => right.ElementId, (left, right) => new PropertyElementForceSet() { PropertyId = left.PropertyId, ElementId = left.ElementId, Force = right.Force });
                propertyElementForceList.ToList().ForEach(m => Console.WriteLine(m));
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
    
            Console.ReadKey();
        }
    }
    - コンソールの出力結果2 -
    PropertyId=1, ElementId=1, Force=0.429844649
    PropertyId=1, ElementId=2, Force=0.849095933
    PropertyId=1, ElementId=3, Force=0.833987789
    PropertyId=2, ElementId=4, Force=0.889480799
    PropertyId=2, ElementId=5, Force=0.232153408
    PropertyId=2, ElementId=6, Force=0.739578496
    PropertyId=3, ElementId=7, Force=0.997620865
    PropertyId=3, ElementId=8, Force=0.914703127
    PropertyId=3, ElementId=9, Force=0.327499172
    PropertyId=3, ElementId=10, Force=0.229693679
    

    3. Join句とGroupBy句を組み合わせる

     ここでは各プロパティ毎の最大値を取得したいので、プロパティIDと力のリストが取得出来たら、GroupByを使って各プロパティ毎の力の最大値を取得してみることにします。code.3の5行分をcode.2の74行目の後に挿入してプログラムを実行してみて下さい。「コンソールの出力結果3」のように表示されれば大丈夫です。

    - code.3 -
    IEnumerable<IGrouping<int, PropertyElementForceSet>> groupedByPropertyList = propertyElementForceList.GroupBy(m => m.PropertyId);
    foreach (IGrouping<int, PropertyElementForceSet> groupedByProperty in groupedByPropertyList)
    {
        Console.WriteLine(string.Format("PropertyId={0}, MaxForce={1}", groupedByProperty.Key, groupedByProperty.Max(m => m.Force)));
    }
    - コンソールの出力結果3 -
    PropertyId=1, ElementId=1, Force=0.429844649
    PropertyId=1, ElementId=2, Force=0.849095933
    PropertyId=1, ElementId=3, Force=0.833987789
    PropertyId=2, ElementId=4, Force=0.889480799
    PropertyId=2, ElementId=5, Force=0.232153408
    PropertyId=2, ElementId=6, Force=0.739578496
    PropertyId=3, ElementId=7, Force=0.997620865
    PropertyId=3, ElementId=8, Force=0.914703127
    PropertyId=3, ElementId=9, Force=0.327499172
    PropertyId=3, ElementId=10, Force=0.229693679
    PropertyId=1, MaxForce=0.849095933
    PropertyId=2, MaxForce=0.889480799
    PropertyId=3, MaxForce=0.997620865