2

I have three IEnumerables of the same type (string) and I want to get unique combinations of values from it. Not quite sure how to implement LINQ in here.

Input data would look like this, one column represents one IEnum

The result should be a list of something like this by adding - as separator

a-p-1
b-q-4
b-r-7
c-s-8
c-s-9
d-t-10
e-u-11

Data itself is in unique connection. All projects in one account are unique, and so does all subprojects for a particular project.

Indigo
  • 2,887
  • 11
  • 52
  • 83

2 Answers2

6

You should not be storing related items in separate sequences; that's an antipattern. However, if this happens to be the way that your data is available, you can first combine the items from the sequences using Zip, then use Distinct to get unique values.

var result = accounts.Zip(projects, (a, p) => a + "-" + p)
                     .Zip(subprojects, (a, s) => a + "-" + s)
                     .Distinct()
                     .ToList();

Edit: In the spirit of Servy's answer, here is an extension method for zipping an arbitrary number of sequences:

public static partial class EnumerableExtensions
{
    public static IEnumerable<TResult> Zip<TSource, TResult>(
        this IEnumerable<IEnumerable<TSource>> sequences,
        Func<IEnumerable<TSource>, TResult> resultSelector)
    {
        var enumerators = sequences.Select(sequence => sequence.GetEnumerator()).ToArray();
        while (enumerators.All(enumerator => enumerator.MoveNext()))
            yield return resultSelector(enumerators.Select(enumerator => enumerator.Current));
    }
}

And this is how it would be consumed:

string[][] sequences = { accounts, projects, subprojects };
var results = sequences.Zip(items => string.Join("-", items))
                        .Distinct()
                        .ToList();
Community
  • 1
  • 1
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • Wow, didn't realize it could be this way as well. Thanks. I was making it too complicated. :-) – Indigo Jan 10 '14 at 17:53
  • I would select it as an answer again and again if I could. (Since I already did select it as an answer) Thanks @Douglas and Servy... – Indigo Jan 13 '14 at 11:40
3

While you could use Zip twice to zip three sequences, creating a new three way zip is easy enough and will be easier to work with:

public static IEnumerable<TResult> TriZip<TSource, TResult>(
    this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    IEnumerable<TSource> third,
    Func<TSource, TSource, TSource, TResult> resultSelector)
{
    using (var firstIterator = first.GetEnumerator())
    using (var secondIterator = second.GetEnumerator())
    using (var thirdIterator = third.GetEnumerator())
        while (firstIterator.MoveNext() &&
            secondIterator.MoveNext() &&
            thirdIterator.MoveNext())
            yield return resultSelector(firstIterator.Current,
                secondIterator.Current,
                thirdIterator.Current);
}

Now your work is pretty much just calling this method, plus a Distinct:

var query = accounts.TriZip(projects, subprojects,
    (account, project, subproject) => 
        new { account, project, subproject })
    .Distinct();
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Pretty sophisticated way of doing this. Thanks a lot. – Indigo Jan 10 '14 at 18:01
  • It is correct to create a TriZip but it then restricts to only three IEnums. I mean does not really generalize things, with zip one can add multiple IEnums. On the other hand, it's really great to use if I am encountering this triple combination multiple times. +1 – Indigo Jan 10 '14 at 18:08