使用LINQ按键索引连接多维数组

3

我有N个多维源数据数组,每个数组都有相同数量的列(在此示例中为C=4),但行数可以是任意数量:

var array1 = new double[,]
  {
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 },
    { 9, 10, 11, 12 }
  };

var array2 = new double[,]
  {
    { 1, 2, 5, 6 },
    { 7, 8, 9, 10 },
    { 9, 10, 11, 12 }
  };

var array3 = new double[,]
  {
    { 1, 2, 7, 8 },
    { 13, 14, 15, 16 }
  };

...
var arrayN = new double[,] { ... };

我还有一个数组,指定在源数组中哪些索引将用作连接键:
var keyArray = new int[] { 0, 1 };

我需要将数组连接起来,使得结果数组看起来像这样:
var result = new double[,]
{
  // The length of each element in this array will be (C x N),
  // the first C elements will be from array1, the next C from 
  // array2, and so on, and nulls used for arrays elements that 
  // are not included in the join (keys don't match).
  //
  // The number of rows in this array will be the number of distinct key combinations.
  { 1, 2, 3, 4, 1, 2, 5, 6, 1, 2, 7, 8 },
  { 5, 6, 7, 8, null, null, null, null, null, null, null, null },
  { 9, 10, 11, 12, 9, 10, 11, 12, null, null, null, null },
  { null, null, null, null, 7, 8, 9, 10, null, null, null, null },
  { null, null, null, null, null, null, null, null, 13, 14, 15, 16 }
};

我认为我需要从每个源数组中选择不同的键,并循环遍历所有数据并比较每一行等,以填充结果数组。然而,使用LINQ应该有更有效的方法来完成这个任务 - 有人可以帮忙吗?


3
你的结果毫无意义。为什么四个双精度数中的第一个和第三个被合并成8,而第二个数却得到了一行新的内容? - Theun Arbeider
你所说的匹配键,是指双重数组中的第一个数字吗? - Theun Arbeider
1
很遗憾,这并不简单 - 我的意思是源数组中包含在keyArray数组中的每个索引。因此,在这个例子中(0和1)- 它基于双重数组中的第一个和第二个数字进行连接。但它也可以基于任何或所有的索引进行连接... - Kevin
所有的键必须匹配才能进行连接(任何具有元素的数组,如果所有键都匹配,则合并到一个结果行中)。对于我难以充分解释这一点,我深感抱歉... - Kevin
我已经更新了问题,希望更加清晰,并在示例中包含了一个额外的数组。我也可以使用double[][]来更改源数组。 - Kevin
显示剩余3条评论
2个回答

3
如果您使用double[][]而不是double[,],这里有一个解决方案。
var array1 = new double[][]
            {
              new double[] {1,2,3,4},
              new double[] {5,6,7,8},
              new double[] {9,10,11,12}
            };

var array2 = new double[][]
            {
              new double[] {1,2,5,6},
              new double[] {7,8,9,10},
              new double[] {9,10,11,12}
            };

var key = new int[] { 0, 1 };

 double?[][] result = (from a in array1
                       from b in array2.Where(bi => key.Select(k => bi[k] == a[k])
                                                       .Aggregate((k1, k2) => k1 && k2))
                                       .DefaultIfEmpty()
                       select a.Select(an => (double?)an)
                               .Concat(b == null ?
                                       a.Select(an => (double?)null) :
                                       b.Select(bn => (double?)bn))
                               .ToArray()
                       ).Union
                       (from b in array2
                        from a in array1.Where(ai => key.Select(k => ai[k] == b[k])
                                                        .Aggregate((k1, k2) => k1 && k2))
                                        .DefaultIfEmpty()
                        where a == null
                        select b.Select(bn => (double?)null)
                                .Concat(b.Select(bn =>(double?)bn))
                                .ToArray()
                        ).ToArray();

+1:无论如何,我认为double[][]在这种情况下更加“正确”。请参见http://www.vbforums.com/showthread.php?t=517717#edit3195930。 - StriplingWarrior

0

我有一个解决方案给你。它可能不像你期望的那样简洁,但它能够工作。这需要你将数组的使用方式从:

var array1 = new double[,] 更改为: var array1 = new double?[][]

因为.NET将第一个视为单个IEnumerable而不是IEnumerable>。此外,为了支持空值,你必须使用可为空的double类型。以下代码假设所有的交错数组大小相同。

接下来,你需要定义一个类来保存动态键并进行比较:

class Keys : IEquatable<Keys>
{
    private IEnumerable<double?> _keys = Enumerable.Empty<double?>();

    public override int GetHashCode()
    {
        int hash = 23;
        foreach (var element in _keys)
        {
            hash = hash * 37 + element.GetValueOrDefault().GetHashCode();
        }

        return hash;
    }

    public bool Equals(Keys other)
    {
        if (other == null)
            return false;

        if (_keys.Count() != other._keys.Count())
            return false;

        for (int index = 0; index < _keys.Count(); index++)
        {
            if (_keys.ElementAt(index) != other._keys.ElementAt(index))
                return false;
        }

        return true;
    }

    public Keys(double?[] data, int[] indexes)
    {
        var keys = new List<double?>();
        foreach (var index in indexes)
        {
            keys.Add(data[index]);
        }
        _keys = keys;
    }
}

然后你需要以下逻辑来执行查询并返回一个你期望的 double?[][] 类型的结果:
// Create full join of selection
var fullJoin = (from l in array1
                join r in array2 on new Keys(l, keyArray) equals new Keys(r, keyArray) into g
                from r in g.DefaultIfEmpty()
                select new { l, r})
               .Concat
               (from r in array2
                join l in array1 on new Keys(r, keyArray) equals new Keys(l, keyArray) into g
                from l in g.DefaultIfEmpty()
                where l == null
                select new {l, r});

// Create the final result set
var results = fullJoin.Select(i => 
{ 
    var list = new List<double?>(); 
    if (i.l != null)
    {
        list.AddRange(i.l); 
    }
    else
    {
        list.AddRange(Enumerable.Repeat((double?)null, i.r.Length));
    }
    if (i.r != null)
    {
        list.AddRange(i.r); 
    }
    else
    {
        list.AddRange(Enumerable.Repeat((double?)null, i.l.Length));
    }
    return list.ToArray();
}).ToArray();

嘿,Adam,非常感谢你的帮助!看起来很好,但不幸的是它仅依赖于只有两个源数组,而可能有任何数量。我不确定是否可以修改fullJoin LINQ查询,以便能够使用动态源数组集。 - Kevin
@Kevin - 我想我错过了那个要求。我需要再考虑一下。让那部分变得动态确实使它变得有点棘手。 - Adam Gritt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接