I found myself in the need of a function to pairwise an array 2D so I created one:
public static IEnumerable<TOut> Pairwise<TIn, TOut>(this TIn[,] source ,Func<TIn, TIn, TOut> selector)
{
Point[] deltas =
{
new Point(-1, -1), new Point(0, -1), new Point(1, -1),
new Point(-1, 0), new Point(1, 0),
new Point(-1, 1), new Point(0, 1), new Point(1, 1)
};
int width = source.GetLength(0);
int height = source.GetLength(1);
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Point[] neighbors =
deltas
.Select(point => new Point(point.X + x, point.Y + y))
.Where(point => !(point.X < 0 || point.X >= width || point.Y < 0 || point.Y >= height))
.ToArray();
TIn current = source[x, y];
foreach (var element in neighbors.Select(point => selector(current, source[point.X, point.Y])))
{
yield return element;
}
}
}
}
But, as you can see, this function returns duplicates and its performance is not so good. So, I tried a new approach. Instead of getting the neighbors of each element and returning them, it occurred to me that if I pairwise the rows, columns, and diagonals. The columns and rows were easy, but getting the diagonals was a challenge.
This is what I came up with:
public static IEnumerable<IEnumerable<T>> Diagonals<T>(this T[,] source, bool inverse = false)
{
int width = source.GetLength(0);
int height = source.GetLength(1);
//this code I found returns the diagonals but not the inverse them so I couldnt use it
//for (int slice = 0; slice < width + height - 1; ++slice)
//{
// var curSlice = new List<T>();
// int z1 = slice < height ? 0 : slice - height + 1;
// int z2 = slice < width ? 0 : slice - width + 1;
// for (int j = slice - z2; j >= z1; --j)
// {
// curSlice.Add(source[j, slice - j]);
// }
// yield return curSlice;
//}
List<Tuple<Point, T>> curSlice = new List<Tuple<Point, T>>();
curSlice.Add(!inverse
? Tuple.Create(new Point(0, 0), source[0, 0])
: Tuple.Create(new Point(width - 1, 0), source[width - 1, 0]));
while (curSlice.Count > 0)
{
List<Tuple<Point, T>> prevSlice = curSlice;
curSlice = new List<Tuple<Point, T>>();
bool isFirst = true;
foreach (Point ele in prevSlice.Select(tuple => tuple.Item1))
{
int? newY = ele.Y + 1 >= height ? (int?) null : ele.Y + 1 ;
int? newX = !inverse
? (ele.X + 1 >= width ? (int?) null : ele.X + 1)
: (ele.X - 1 < 0 ? (int?) null : ele.X - 1);
if (isFirst)
{
if (newX != null)
curSlice.Add(Tuple.Create(new Point(newX.Value, ele.Y), source[newX.Value, ele.Y]));
isFirst = false;
}
if (newY != null)
curSlice.Add(Tuple.Create(new Point(ele.X, newY.Value), source[ele.X, newY.Value]));
}
yield return curSlice.Select(tuple => tuple.Item2);
}
}
Finally, this was my solution. Is this the simplest solution or did I over-complicate things?
public static IEnumerable<TOut> Pairwise2<TIn, TOut>(this TIn[,] source, Func<TIn, TIn, TOut> selector)
{
int width = source.GetLength(0);
int height = source.GetLength(1);
var horizontalSeq = Enumerable.Range(0, width).Pairwise(Tuple.Create).ToArray();
var verticalSeq = Enumerable.Range(0, height).Pairwise(Tuple.Create).ToArray();
for (int y = 0; y < height; y++)
{
int y1 = y;
foreach (var e in horizontalSeq.Select(tuple => selector(source[tuple.Item1, y1], source[tuple.Item2, y1])))
yield return e;
}
for (int x = 0; x < width; x++)
{
int x1 = x;
foreach (var e in verticalSeq.Select(tuple => selector(source[x1, tuple.Item1], source[x1, tuple.Item2])))
yield return e;
}
foreach (var e in source.Diagonals().SelectMany(ins => ins.Pairwise(selector)))
{
yield return e;
}
foreach (var e in source.Diagonals(inverse: true).SelectMany(ins => ins.Pairwise(selector)))
{
yield return e;
}
}
Example for this array:
00 03 06 09 01 04 07 10 02 05 08 11
The pairs would be:
(0, 3) (3, 6) (6, 9) (1, 4) (4, 7) (7, 10) (2, 5) (5, 8) (8, 11) (0, 1) (1, 2) (3, 4) (4, 5) (6, 7) (7, 8) (9, 10) (10, 11) (3, 1) (6, 4) (4, 2) (9, 7) (7, 5) (10, 8) (6, 10) (3, 7) (7, 11) (0, 4) (4, 8) (1, 5)
x
in the 2D-array, and for each neighboury
ofx
, you want the result off(x, y)
(and you're assuming thatf(x, y) = f(y, x)
, since you say thatPairwise
returns duplicates)? – mjolka Jul 25 '14 at 5:32