4
\$\begingroup\$

I'm trying to merge two nodes. Basically, the idea is to take all children in left and new ones in right. Is there any way to get rid of toAdd variable and do it in a clean way?

I don't want to convert any IEnumerable<T> to List or Array in this merge process.

public IEnumerable<TChild> NodeMerge<TNode, TChild, TKey>(TNode left, TNode right,
Func<TNode, IEnumerable<TChild>> getChildren, Func<TChild, TKey> getKey)
{
    var lChildren = getChildren(left);
    var rChildren = getChildren(right);

    IEnumerable<TChild>[] toAdd = new IEnumerable<TChild>[2];

    // Common Keys between left and right
    toAdd[0] = lChildren.Where(s => rChildren.Select(p => getKey(p)).Contains(getKey(s)));

    // new keys added to right
    toAdd[1] = rChildren.Where(s => lChildren.Select(p => getKey(p)).Contains(getKey(s)) == false);

    return toAdd.SelectMany(s => s);
}
\$\endgroup\$

3 Answers 3

3
\$\begingroup\$
public IEnumerable<TChild> NodeMerge<TNode, TChild, TKey>(TNode left, TNode right,
    Func<TNode, IEnumerable<TChild>> getChildren,
    Func<TChild, TKey> getKey)
{
    var lChildren = getChildren(left);
    var rChildren = getChildren(right);

    return lChildren
        .Intersect(rChildren, new FuncComparer<TChild, TKey>(getKey))
        .Concat(
            rChildren
               .Except(lChildren, new FuncComparer<TChild, TKey>(getKey))
        );
}

With this helper class:

public class FuncComparer<T, TKey> : IEqualityComparer<T> 
{
    readonly Func<T, TKey> getKey;
    readonly EqualityComparer<TKey> comparer;

    public FuncComparer(Func<T, TKey> getKey) 
    { 
        this.getKey = getKey;
        this.comparer = EqualityComparer<TKey>.Default;
    }
    public bool Equals(T x, T y) 
    {
        return comparer.Equals(getKey(x), getKey(y));
    }

    public int GetHashCode(T obj) 
    {
        return comparer.GetHashCode(getKey(obj));
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ This is matching what your code is doing, but not what your question says the code is doing. \$\endgroup\$
    – Bill Barry
    Commented Jul 18, 2012 at 15:54
  • \$\begingroup\$ Is FuncComparer necessary? Why? \$\endgroup\$
    – Ankush
    Commented Jul 19, 2012 at 7:11
  • \$\begingroup\$ FuncComparer allows linq to optimize the comparisons via the included GetHashCode method. When Intersect runs it can build a lookup table and doesn't need to get the key to use Equals for every comparison. \$\endgroup\$
    – Bill Barry
    Commented Jul 20, 2012 at 1:21
3
\$\begingroup\$

Little cleaner:

// Common Keys between left and right
var result = lChildren.Where(s => rChildren.Select(p => getKey(p)).Contains(getKey(s))).ToList();

// new keys added to right
result.AddRange(rChildren.Where(s => lChildren.Select(p => getKey(p)).Contains(getKey(s)) == false));

return result;

Without ToList():

public static IEnumerable<T> Merge<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
    foreach (var item in first)
        yield return item;

    foreach (var item in second)
        yield return item;
}

In your NodeMerge method:

return lChildren.Where(s => rChildren.Select(p => getKey(p)).Contains(getKey(s))).Merge(rChildren.Where(s => lChildren.Select(p => getKey(p)).Contains(getKey(s)) == false));
\$\endgroup\$
5
  • \$\begingroup\$ Thanks. I should have mentioned, but in general I usually try to avoid ToList(), ToArray() operator. Will add this. \$\endgroup\$
    – Ankush
    Commented Jul 18, 2012 at 11:08
  • \$\begingroup\$ Why? Is there a problem with them? \$\endgroup\$ Commented Jul 18, 2012 at 11:11
  • \$\begingroup\$ Well I want whole function do Lazy evaluation. ToList will defeat that purpose. \$\endgroup\$
    – Ankush
    Commented Jul 18, 2012 at 11:16
  • \$\begingroup\$ You can make an extension method "Merge" that gets two enumerables and iterate both of them with yield return and use this method on your where results. \$\endgroup\$ Commented Jul 18, 2012 at 11:25
  • 1
    \$\begingroup\$ There is already an extension method that behaves exactly like your Merge method, called Concat. Neither of them will "join" the lists. \$\endgroup\$
    – KeithS
    Commented Jul 18, 2012 at 16:10
2
\$\begingroup\$

You can do this with vanilla Linq. The question is whether you want all keys on the left plus new keys from the right (a "full join") or common keys between left and right plus all keys on the right (a "right join"):

var lChildren = getChildren(left);
var rChildren = getChildren(right);
var lKeys = lChildren.Select(getKey);
var rKeys = rChildren.Select(getKey);

var fullJoin = lChildren
    .Concat(rChildren.Where(r=>!lKeys.Contains(getKey(r)));

var rightJoin = lChildren.Where(l=>!rKeys.Contains(getKey(l)))
    .Concat(rChildren.Where(r=>!lKeys.Contains(getKey(r)));
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.