【问题标题】:Linq Outer Join on object array对象数组上的 Linq 外连接
【发布时间】:2012-04-07 12:58:48
【问题描述】:

考虑一组 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 个空值。

问题:我可以仅使用数组和通用列表和字典等简单容器来使用 LINQ 吗?

【问题讨论】:

  • 请提供样本输入和预期输出
  • 像这样使用对象数组来存储键/值对确实是错失了使用几乎任何其他数据结构的机会。
  • 为什么 in pseudo code 你的标签是 c# ?
  • 通过“伪代码”我的意思是松散地解释数组 - 我从下面关于 IEnumerable 的评论中得到它。例如如果这是 LINQ 想要的,数据可以排列到 Dictionary - 我的问题实际上是关于让 sn-p 对一组键值列表进行连接
  • @tpascale 得到一个 sn-p,你应该展示一些东西。否则我们将继续在概念层面进行讨论。

标签: c# linq outer-join


【解决方案1】:

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

因此,可以使用Dictionary&lt;T1,T2&gt; 存储键值对。例如,假设您的键是 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]();

这里的问题是,连接结构的type中“列”的数量取决于这个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 的已知值。如果这恰好是一个足够的解决方案,它实际上很简单,尽管示例 I写的可能有点过于复杂。回到上面的查询,您会看到 from / join / from 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}
        }     
    }
}

else块是在之前的LINQ代码中通过添加第二个where子句来实现的,即使没有匹配也要求行,这是一个空列表,可以在调用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();

        }
    }
}

【讨论】:

  • 这很有帮助......我现在可以接受已知的 N 解决方案
【解决方案2】:

多维数组不实现IEnumerable&lt;T&gt;,因此您将无法使用 LINQ。另一方面,锯齿状数组可以由 LINQ 操作。

【讨论】:

  • 这不是真的......(它实现了IEnumerable,但不是类型化的IEnumerable&lt;T&gt;
  • ...并且可以通过IEnumerable&lt;T&gt;.Cast&lt;T&gt; 轻松投射
  • 你会失去键值关系。
  • @KirkWoll:IEnumerable 最多会给你GetEnumerator()。您必须Cast&lt;T&gt; 才能获得任何支持 LINQ 的东西。基于 OP,IEnumerable&lt;T&gt; 也不会削减它。他真的在寻找更多Dictionary&lt;T&gt;
猜你喜欢
  • 1970-01-01
  • 2015-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多