Joining multidimensional arrays with LINQ on key indices
I have N multidimensional source data arrays, each with the same number of columns (C=4 in this example), but any number of rows:
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[,] { ... };
I also have an array that specifies which indices in the source arrays are to be used as the join keys:
var keyArray = new int[] { 0, 1 };
I need to join the arrays in such as way that the resulting array will look like:
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 }
};
I am thinking I need to select the distinct keys fro开发者_如何学JAVAm each source array and loop through all of the data and compare each row, etc. to fill the results array. However, there should be a more efficient way to do this using LINQ - can anyone help?
Here is a solution if you use double[][] instead of 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();
I have a solution for you. It may not be as clean as you are looking for, but it will work. It would require that you change your use of arrays from:
var array1 = new double[,] to: var array1 = new double?[][]
as .NET views the first as a single IEnumerable instead of IEnumerable>. Plus to support the nulls you have to use a nullable-double. The following code does expect that all jagged arrays are of the same size.
Next you have to define a class to hold the dynamic key(s) and do the compare:
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;
}
}
Then you have the following logic to do the query and return an double?[][] that you would expect:
// 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();
精彩评论