If I have, for example the following List<int>
s
{ 1, 2, 3, 4 } //list1
{ 2, 3, 5, 6 } //list2
...
{ 3, 4, 5 } //listN
What is the best way to retrieve the following corresponding List<int?>
s?
{ 1, 2, 3, 4, null, null } //list1
{ null, 2, 3, null, 5, 6 } //list2
...
{ null, null, 3, 4, 5, null } //listN
I'm posting the solution we discussed in chat. I had an unoptimized version using Linq for all things loopy/filtering:
However, I suspect it won't be too performant because of all the enumerator classes created, and the collections being instantiated/modified along the way.
So I took the time to optimize it into handwritten loops with an administration to keep track of active iterators instead of modifying the iters
collection. Here it is:
See http://ideone.com/FuZIDy for full live demo.
Note I assume the lists are pre-ordered by
DefaultComparer<T>
, since I use Linq'sMin()
extension method without a custom comparer
public static IEnumerable<IEnumerable<T>> AlignSequences<T>(this IEnumerable<IEnumerable<T>> sequences)
{
var iters = sequences
.Select((s, index) => new { active=true, index, enumerator = s.GetEnumerator() })
.ToArray();
var isActive = iters.Select(it => it.enumerator.MoveNext()).ToArray();
var numactive = isActive.Count(flag => flag);
try
{
while (numactive > 0)
{
T min = iters
.Where(it => isActive[it.index])
.Min(it => it.enumerator.Current);
var row = new T[iters.Count()];
for (int j = 0; j < isActive.Length; j++)
{
if (!isActive[j] || !Equals(iters[j].enumerator.Current, min))
continue;
row[j] = min;
if (!iters[j].enumerator.MoveNext())
{
isActive[j] = false;
numactive -= 1;
}
}
yield return row;
}
}
finally
{
foreach (var iter in iters) iter.enumerator.Dispose();
}
}
Use it like this:
public static void Main(string[] args)
{
var list1 = new int?[] { 1, 2, 3, 4, 5 };
var list2 = new int?[] { 3, 4, 5, 6, 7 };
var list3 = new int?[] { 6, 9, 9 };
var lockstep = AlignSequences(new[] { list1, list2, list3 });
foreach (var step in lockstep)
Console.WriteLine(string.Join("\t", step.Select(i => i.HasValue ? i.Value.ToString() : "null").ToArray()));
}
It prints (for demo purposes I print the results sideways):
1 null null
2 null null
3 3 null
4 4 null
5 5 null
null 6 6
null 7 null
null null 9
null null 9
Note: You might like to change the interface to accept arbitrary number of lists, instead of a single sequence of sequences:
public static IEnumerable<IEnumerable<T>> AlignSequences<T>(params IEnumerable<T>[] sequences)
That way you could just call
var lockstep = AlignSequences(list1, list2, list3);
Here's another approach using List.BinarySearch
.
sample data:
var list1 = new List<int>() { 1, 2, 3, 4 };
var list2 = new List<int>() { 2, 3, 5, 6, 7, 8 };
var list3 = new List<int>() { 3, 4, 5 };
var all = new List<List<int>>() { list1, list2, list3 };
calculate min/max and all nullable-lists:
int min = all.Min(l => l.Min());
int max = all.Max(l => l.Max());
// start from smallest number and end with highest, fill all between
int count = max - min + 1;
List<int?> l1Result = new List<int?>(count);
List<int?> l2Result = new List<int?>(count);
List<int?> l3Result = new List<int?>(count);
foreach (int val in Enumerable.Range(min, count))
{
if (list1.BinarySearch(val) >= 0)
l1Result.Add(val);
else
l1Result.Add(new Nullable<int>());
if (list2.BinarySearch(val) >= 0)
l2Result.Add(val);
else
l2Result.Add(new Nullable<int>());
if (list3.BinarySearch(val) >= 0)
l3Result.Add(val);
else
l3Result.Add(new Nullable<int>());
}
output:
Console.WriteLine(string.Join(",", l1Result.Select(i => !i.HasValue ? "NULL" : i.Value.ToString())));
Console.WriteLine(string.Join(",", l2Result.Select(i => !i.HasValue ? "NULL" : i.Value.ToString())));
Console.WriteLine(string.Join(",", l3Result.Select(i => !i.HasValue ? "NULL" : i.Value.ToString())));
1, 2, 3, 4, NULL, NULL, NULL, NULL
NULL, 2, 3, NULL, 5, 6, 7, 8
NULL, NULL, 3, 4, 5, NULL, NULL, NULL
DEMO
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With