linqとの対称的な違いを見つける
-
04-10-2019 - |
質問
私は2つのコレクションを持っています a
と b
. 。どちらかのアイテムのセットを計算したい a
また b
, 、しかし、両方ではありません(論理的排他的または)。 Linqを使用すると、これを思いつくことができます:
IEnumerable<T> Delta<T>(IEnumerable<T> a, IEnumerable<T> b)
{
return a.Except (b).Union (b.Except (a));
}
2つのコレクションの違いを生み出す他のより効率的またはよりコンパクトな方法があるのだろうか。
編集1:Jon Skeetは、に依存してアイテムの順序を保持しない最初のソリューションを投稿しました HashSet
. 。の順序を維持する他のアプローチがあるのだろうか a
と b
出力内。
解決
使用する HashSet<T>
直接 - SymmetricExceptWith
方法:
HashSet<T> data = new HashSet<T>(a);
data.SymmetricExceptWith(b);
編集:注文を維持したい場合は、代替手段を次に示します。
HashSet<T> data = new HashSet<T>(a);
data.IntersectWith(b);
foreach (T t in a.Concat(b))
{
if (!data.Contains(t))
{
yield return t;
}
}
これには、次の重要な違いがあります。
- 両方
a
とb
2回繰り返されます。場合によっては非常に悪いことかもしれません - あなたは電話することができますToList
それらのそれぞれで、バッファーを保持するために始めます。 どちらにも複製がある場合
a
またb
, 、それらは複数回投げかけられます。これを避けたい場合は、既にイールドの値のセットを保持できます。この時点で、それは次のものと同等です。a.Concat(b).Except(a.Intersect(b))
それはまだだけです 2 ただし、元のコードの3つではなく操作を設定します。
他のヒント
a.except(b)とb.except(a)がばらばらであると、使用できます concat
それ以外の union
, 、セットオペレーターの保存(および concat
より効率的です)。
return a.Except (b).Concat (b.Except (a));
これは引き続き各リストを2回実行します。
私の会社でも同様のプロジェクトが必要だったので、この拡張機能を書きました。
public class EnumerablePair<T> : IReadOnlyCollection<T>
{
private IReadOnlyCollection<T> _Left;
private IReadOnlyCollection<T> _Right;
private IEnumerable<T> _Union;
private int _Count;
public EnumerablePair(IEnumerable<T> left, IEnumerable<T> right)
{
_Left = left?.ToList() ?? Enumerable.Empty<T>().ToList();
_Right = right?.ToList() ?? Enumerable.Empty<T>().ToList();
_Count = Left.Count + Right.Count;
_Union = Left.Union(Right);
}
public int Count => _Count;
public IReadOnlyCollection<T> Left { get => _Left; }
public IReadOnlyCollection<T> Right { get => _Right; }
public IEnumerator<T> GetEnumerator()
{
return _Union.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _Union.GetEnumerator();
}
}
public static class EnumerableExtension
{
public static EnumerablePair<T> ExclusiveDisjunction<T>(this IEnumerable<T> leftOperand, IEnumerable<T> rightOperand, IEqualityComparer<T> comparer = null)
{
if (leftOperand == null)
throw new ArgumentNullException(nameof(leftOperand), $"{nameof(leftOperand)} is null.");
if (rightOperand == null)
throw new ArgumentNullException(nameof(rightOperand), $"{nameof(rightOperand)} is null.");
// TODO : Can be optimized if one of the IEnumerable parameters is empty.
bool leftIsBigger = leftOperand.Count() > rightOperand.Count();
var biggestOperand = leftIsBigger ? leftOperand.ToList() : rightOperand.ToList();
var smallestOperand = leftIsBigger ? rightOperand.ToList() : leftOperand.ToList();
var except1 = biggestOperand.ToList();
var except2 = Enumerable.Empty<T>().ToList();
Func<T, T, bool> areEquals;
if (comparer != null)
areEquals = (one, theOther) => comparer.Equals(one, theOther);
else
areEquals = (one, theOther) => one?.Equals(theOther) ?? theOther == null;
foreach (T t in smallestOperand)
if (except1.RemoveAll(item => areEquals(item, t)) == 0)
except2.Add(t);
if (leftIsBigger)
return new EnumerablePair<T>(except1, except2);
return new EnumerablePair<T>(except2, except1);
}
}
2つのコレクションの要素を比較します(ANを使用します IEqualityComparer
または、あなたが選んだとき)。
- 返されたオブジェクト、an
EnumerablePair<T>
, 、入っているオブジェクトが含まれていますleftOperand
またrightOperand
, 、両方ではありません(xor)。 EnumerablePair<T>.Left
入っているオブジェクトが含まれていますleftOperand
しかし、ではありませんrightOperand
.EnumerablePair<T>.Right
入っているオブジェクトが含まれていますrightOperand
しかし、ではありませんleftOperand
.
次のような拡張機能を使用できます。
var xorList = list1.ExclusiveDisjunction(list2);
var leftXor = xorList.Left;
var rightXor = xorList.Right;
xorList
, leftXor
と rightXor
それは IEnumerable<T>
.