如何将列表中的项目与其他所有项目进行比较而不重复?

时间:2011-08-11 08:57:41

标签: c# linq

我有一组对象(让我们称之为MyItem),每个MyItem都有一个名为IsCompatibleWith的方法,它返回一个布尔值,表示它是否与另一个MyItem兼容

public class MyItem
{
    ...
    public bool IsCompatibleWith(MyItem other) { ... }
    ...
}

A.IsCompatibleWith(B)将始终与B.IsCompatibleWith(A)相同。例如,如果我有一个包含其中4个的集合,我试图找到一个LINQ查询,该查询将在同一集合中的每个不同的项目对上运行该方法。因此,如果我的集合包含A,B,C和D,我希望做相同的:

A.IsCompatibleWith(B); // A & B
A.IsCompatibleWith(C); // A & C
A.IsCompatibleWith(D); // A & D
B.IsCompatibleWith(C); // B & C
B.IsCompatibleWith(D); // B & D
C.IsCompatibleWith(D); // C & D

最初使用的代码是:

var result = from item in myItems
             from other in myItems
             where item != other && 
                   item.IsCompatibleWith(other)
             select item;

但当然这仍然会同时执行A & BB & A(这不是必需的且效率不高)。此外,值得注意的是,实际上这些列表将比4个项目大很多,因此需要最佳解决方案。

希望这有意义......任何想法?

编辑: 一个可能的查询 -

MyItem[] items = myItems.ToArray();
bool compatible = (from item in items
                   from other in items
                   where
                       Array.IndexOf(items, item) < Array.IndexOf(items, other) &&
                       !item.IsCompatibleWith(other)
                   select item).FirstOrDefault() == null;

Edit2:最后切换到使用LukeH的自定义解决方案,因为它对更大的列表更有效。

public bool AreAllCompatible()
{
    using (var e = myItems.GetEnumerator())
    {
        var buffer = new List<MyItem>();
        while (e.MoveNext())
        {
            if (buffer.Any(item => !item.IsCompatibleWith(e.Current)))
                return false;
            buffer.Add(e.Current);
        }
    }
    return true;
}

5 个答案:

答案 0 :(得分:3)

修改...

根据您的问题中添加的“最终查询”判断,您需要一种方法来确定集合中的所有项目是否彼此兼容。以下是如何合理有效地完成这项工作:

bool compatible = myItems.AreAllItemsCompatible();

// ...

public static bool AreAllItemsCompatible(this IEnumerable<MyItem> source)
{
    using (var e = source.GetEnumerator())
    {
        var buffer = new List<MyItem>();

        while (e.MoveNext())
        {
            foreach (MyItem item in buffer)
            {
                if (!item.IsCompatibleWith(e.Current))
                    return false;
            }
            buffer.Add(e.Current);
        }
    }
    return true;
}

原始答案......

我认为只使用内置的LINQ方法有效的方法。

虽然很容易建立自己的。这是您需要的代码类型的示例。我不确定完全你想要返回什么结果,所以我只是在控制台上为每个兼容的对写一条消息。它应该很容易改变它以产生你需要的结果。

using (var e = myItems.GetEnumerator())
{
    var buffer = new List<MyItem>();

    while (e.MoveNext())
    {
        foreach (MyItem item in buffer)
        {
            if (item.IsCompatibleWith(e.Current))
            {
                Console.WriteLine(item + " is compatible with " + e.Current);
            }
        }
        buffer.Add(e.Current);
    }
}

(请注意,虽然这是相当有效的,但它保留集合的原始顺序。这是您的情况中的问题吗?)

答案 1 :(得分:1)

这应该这样做:

var result = from item in myItems
         from other in myItems
         where item != other && 
               myItems.indexOf(item) < myItems.indexOf(other) &&
               item.IsCompatibleWith(other)
         select item;

但我不知道它是否会使它更快,因为在查询中必须检查每行的行索引。

编辑: 如果myItem中有索引,则应使用该索引而不是indexOf。你可以从where子句中删除“item!= other”,现在有点多余

答案 2 :(得分:0)

这是一个想法:

实施IComparable以便MyItem可以排序,然后运行此linq查询:

var result = from item in myItems
             from other in myItems
             where item.CompareTo(other) < 0 && 
                   item.IsCompatibleWith(other)
             select item;

答案 3 :(得分:0)

如果你的MyItem集合足够小,你可以将item.IsCompatibleWith(otherItem)的结果存储在一个布尔数组中:

var itemCount = myItems.Count();
var compatibilityTable = new bool[itemCount, itemCount];

var itemsToCompare = new List<MyItem>();
var i = 0;
var j = 0;
foreach (var item in  myItems)
{
    j = 0;
    foreach (var other in itemsToCompare)
    {
        compatibilityTable[i,j] = item.IsCompatibleWith(other);
        compatibilityTable[j,i] = compatibilityTable[i,j];
        j++;
    }
    itemsToCompare.Add(item);
    i++;
}

var result = myItems.Where((item, i) =>
                     {
                         var compatible = true;
                         var j = 0;
                         while (compatible && j < itemCount)
                         {
                             compatible = compatibilityTable[i,j];
                         }
                         j++;
                         return compatible;
                     }

答案 4 :(得分:0)

所以,我们有

IEnumerable<MyItem> MyItems;

要获得所有组合,我们可以使用这样的函数。

//returns all the k sized combinations from a list
public static IEnumerable<IEnumerable<T>> Combinations<T>(IEnumerable<T> list,
                                                          int k)
{
    if (k == 0) return new[] {new T[0]};

    return list.SelectMany((l, i) =>
        Combinations(list.Skip(i + 1), k - 1).Select(c => (new[] {l}).Concat(c))
    );
}

然后我们可以将此功能应用于我们的问题。

var combinations = Combinations(MyItems, 2).Select(c => c.ToList<MyItem>());
var result = combinations.Where(c => c[0].IsCompatibleWith(c[1]))

这将对所有组合执行IsCompatableWith而不重复。

您当然可以在Combinations函数中执行检查。对于进一步的工作,您可以将Combinations函数放入一个扩展中,该扩展使一个具有可变数量参数的委托用于多个k长度。

编辑:正如我上面所建议的,如果你写了这些扩展方法

public static class Extenesions
{
    IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> list, int k)
    {
        if (k == 0) return new[] { new T[0] };

        return list.SelectMany((l, i) =>
            list.Skip(i + 1).Combinations<T>(k - 1)
                .Select(c => (new[] { l }).Concat(c)));
    }

    IEnumerable<Tuple<T, T>> Combinations<T> (this IEnumerable<T> list, 
                                                 Func<T, T, bool> filter)
    {
        return list.Combinations(2).Where(c =>
            filter(c.First(), c.Last())).Select(c => 
                Tuple.Create<T, T>(c.First(), c.Last()));

    }
}

然后在你的代码中你可以做更优雅(IMO)

var compatibleTuples = myItems.Combinations(a, b) => a.IsCompatibleWith(b)))

然后使用

获取兼容的项目
foreach(var t in compatibleTuples)
{
    t.Item1 // or T.item2
}