Linq Outer加入对象数组

时间:2012-03-22 21:58:57

标签: c# linq outer-join

考虑一组6个[StringKey,Value]数组,伪代码:

object[,2][6] KeyValueArrays; //illustr as array, could also be List<> or Dict<>

我想将其转换为单个表格:

object[,7] KeyWithUpTo6Values  //outer join of the 6 orig key-value sets

如果给定的键出现在6个原始[Key,Value]数组中的(例如)3中,那么该键的连接行将包含3个值和3个空值。

问题:我是否可以使用简单的容器(如数组和通用列表&amp;)来使用LINQ。字典?

2 个答案:

答案 0 :(得分:2)

我想我可能会遗漏一些东西,但事实上你的问题提到了通用列表&amp;字典,我认为当你说数组时,你指的是矢量数据结构的一些王者。

因此,可以使用Dictionary<T1,T2>存储键值对。例如,假设您键为string,值类MyValueClass具有整数类型的单个属性。您的数据声明将如下所示:

class Program
{
    class MyValueClass
    {
        public int Value {get;set;}
    }

    // Other elements elided for clarity

    private Dictionary<string, MyValueClass> data = new Dictionary<string, MyValueClass>();

}

现在,您声明您有N个这样的结构,您想要进行外部联接。例如,

private Dictionary<string, MyValueClass>[] data = new Dictionary<string, MyValueClass>[6]();

这里的问题是,连接结构的类型中的“列”数量取决于此N,​​但除非您使用其他类型的数据结构(即List)表示您的,您将无法动态执行此操作,即对于任何N,因为C#中的数据是静态声明的。

为了说明,请检查下面的查询,其中我假设数组的维度为4:

var query = from d0 in _data[0]
            join d1 in _data[1] on d0.Key equals d1.Key into d1joined
            from d1 in d1joined.DefaultIfEmpty()
            join d2 in _data[2] on d1.Key equals d2.Key into d2joined
            from d2 in d2joined.DefaultIfEmpty()
            join d3 in _data[3] on d2.Key equals d3.Key into d3joined
            from d3 in d3joined.DefaultIfEmpty()
            select new
                     {
                         d0.Key,
                         D0 = d0.Value,
                         D1 = d1.Value,
                         D2 = d2.Value,
                         D3 = d3.Value,
                      };

不要关注联接,我稍后会解释,但请检查select new运算符。看看当Linq汇编这个匿名类型时,必须知道属性的确切数量 - 我们的列 - 因为它是语法的一部分。

所以如果你愿意的话,你可以写一个查询来做你所要求的,但它只能用于知道N的值。如果恰好是一个充分的解决方案,它实际上很简单,尽管例子我写道可能有点过于复杂。回到上面的查询,您将看到来自/ join /来自DefaultIfEmpty 的的重复模式。这个模式是从here复制的,它实际上很简单:它通过一些键将两个结构连接到另一个结果结构(上面的into dnjoined)。 Linq将处理左侧列表中的所有记录,对于每个记录,它将处理右侧列表的每个记录(笛卡尔计划N1 x N2),如下所示:

foreach (var d0 in _data[0])
{
    foreach (var d1 in _data[1])
    {
        if (d0.Key == d1.Key) 
        {
            // Produces an anonymous structure of { d0.Key, d0.Value, d1.Value }
            // and returns it.
        }
    }
}

因此,内部连接操作与组合每一行然后选择键匹配的操作相同。 外部联接的不同之处在于,即使密钥不匹配也会生成一行,因此在我们的伪代码中,它将类似于:

foreach (var d0 in _data[0])
{
    foreach (var d1 in _data[1])
    {
        if (d0.Key == d1.Key) 
        {
            // Produces an anonymous structure of { d0.Key, d0.Value, d1.Value }
            // and returns it.
        }
        else
        {
            // Produce a anonymous structure of {d0.Key, d0.Value, null}
        }     
    }
}

通过添加第二个where子句,在LINQ代码中实现else块,即使没有匹配也要求行,这是一个空列表,可以在调用DefaultIfEmpty时返回数据。 (再次,请参阅上面的链接以获取更多信息)

我将在下面复制一个完整的示例,该示例使用上面提到的数据结构和linq查询。希望它是自我解释的:

using System;
using System.Collections.Generic;
using System.Linq;

namespace TestZone
{
    class Example
    {
        #region Types
        class MyValue
        {
            public int Value { get; set; }

            public override string ToString()
            {
                return string.Format("MyValue(Value = {0})", Value);
            }
        }
        #endregion // Types

        #region Constants
        /// <summary>
        /// Our N
        /// </summary>
        private const int NumberOfArrays = 4;

        /// <summary>
        /// How many rows per dictionary
        /// </summary>
        private const int NumberOfRows = 10; 
        #endregion // Constants

        #region Fields
        private Dictionary<string, MyValue>[] _data = new Dictionary<string, MyValue>[NumberOfArrays]; 
        #endregion // Fields

        #region Constructor
        public Example()
        {
            for (var index = 0; index < _data.Length; index++)
            {
                _data[index] = new Dictionary<string, MyValue>(NumberOfRows);
            }
        } 
        #endregion // Constructor

        public void GenerateRandomData()
        {
            var rand = new Random(DateTime.Now.Millisecond);

            foreach (var dict in _data)
            {
                // Add a number of rows
                for (var i = 0; i < NumberOfRows; i++)
                {
                    var integer = rand.Next(10);    // We use a value of 10 so we have many collisions.
                    dict["ValueOf" + integer] = new MyValue { Value = integer };
                }
            }
        }

        public void OuterJoin()
        {
            // To get the outer join, we have to know the expected N before hand, as this example will show.
            // Do multiple joins
            var query = from d0 in _data[0]
                        join d1 in _data[1] on d0.Key equals d1.Key into d1joined
                        from d1 in d1joined.DefaultIfEmpty()
                        join d2 in _data[2] on d1.Key equals d2.Key into d2joined
                        from d2 in d2joined.DefaultIfEmpty()
                        join d3 in _data[3] on d2.Key equals d3.Key into d3joined
                        from d3 in d3joined.DefaultIfEmpty()
                        select new
                                   {
                                       d0.Key,
                                       D0 = d0.Value,
                                       D1 = d1.Value,
                                       D2 = d2.Value,
                                       D3 = d3.Value,
                                   };

            foreach (var q in query)
            {
                Console.WriteLine(q);
            }
        }
    }

    class Program
    {

        public static void Main()
        {
            var m = new Example();
            m.GenerateRandomData();
            m.OuterJoin();

        }
    }
}

答案 1 :(得分:0)

多维数组不实现IEnumerable<T>,因此您将无法使用LINQ。另一方面,锯齿状数组可以由LINQ操纵。