3
\$\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
\$\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\$
  • \$\begingroup\$ This is matching what your code is doing, but not what your question says the code is doing. \$\endgroup\$ – Bill Barry Jul 18 '12 at 15:54
  • \$\begingroup\$ Is FuncComparer necessary? Why? \$\endgroup\$ – Ankush Jul 19 '12 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 Jul 20 '12 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\$
  • \$\begingroup\$ Thanks. I should have mentioned, but in general I usually try to avoid ToList(), ToArray() operator. Will add this. \$\endgroup\$ – Ankush Jul 18 '12 at 11:08
  • \$\begingroup\$ Why? Is there a problem with them? \$\endgroup\$ – Amiram Korach Jul 18 '12 at 11:11
  • \$\begingroup\$ Well I want whole function do Lazy evaluation. ToList will defeat that purpose. \$\endgroup\$ – Ankush Jul 18 '12 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\$ – Amiram Korach Jul 18 '12 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 Jul 18 '12 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, privacy policy and cookie policy

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