序列序列的2组合的交集的并集

时间:2015-05-08 08:05:52

标签: c# linq

如何在序列序列中找到2个或更多序列中出现的项目集?

换句话说,我想要在传递的序列中至少有2个出现不同的值。

注意: 这不是所有序列的交叉,而是所有序列对的交叉的并集。

注2: 不包括序列与自身的对或2组合。那太傻了。

我自己做了一次尝试,

public static IEnumerable<T> UnionOfIntersects<T>(
                                  this IEnumerable<IEnumerable<T>> source)
{
    var pairs =
            from s1 in source
            from s2 in source
            select new { s1 , s2 };

    var intersects = pairs
        .Where(p => p.s1 != p.s2)
        .Select(p => p.s1.Intersect(p.s2));

    return intersects.SelectMany(i => i).Distinct();
}

但是我担心这可能是次优的,我认为它包括A,B和B对A的交叉点,这似乎是低效的。我还认为在迭代时可能会有更有效的方法来复合集合。

我在下面添加了一些示例输入和输出:

{ { 1, 1, 2, 3, 4, 5, 7 }, { 5, 6, 7 }, { 2, 6, 7, 9 } , { 4 } }

返回

{ 2, 4, 5, 6, 7 }

{ { 1, 2, 3} } or { {} } or { }

返回

{ }

我正在寻找可读性和潜在性能的最佳组合。

修改

我已经对当前答案my code is here进行了一些初步测试。输出如下。

Original valid:True
DoomerOneLine valid:True
DoomerSqlLike valid:True
Svinja valid:True
Adricadar valid:True
Schmelter valid:True
Original 100000 iterations in 82ms
DoomerOneLine 100000 iterations in 58ms
DoomerSqlLike 100000 iterations in 82ms
Svinja 100000 iterations in 1039ms
Adricadar 100000 iterations in 879ms
Schmelter 100000 iterations in 9ms

目前看起来好像Tim Schmelter's answer表现得更好至少一个数量级。

5 个答案:

答案 0 :(得分:4)

var result = sequences
    .SelectMany(e => e.Distinct())
    .GroupBy(e => e)
    .Where(e => e.Count() > 1)
    .Select(e => e.Key);

// result is { 2 4 5 7 6 }

单行方式:

var result = (
          from e in sequences.SelectMany(e => e.Distinct())
          group e by e into g
          where g.Count() > 1
          orderby g.Key
          select g.Key);

// result is { 2 4 5 6 7 }

类似Sql的方式(有订购):

var dic = new Dictionary<int, int>();
var subHash = new HashSet<int>();
int length = array.Length;
for (int i = 0; i < length; i++)
{
    subHash.Clear();
    int subLength = array[i].Length;
    for (int j = 0; j < subLength; j++)
    {
        int n = array[i][j];
        if (!subHash.Contains(n))
        {
            int counter;
            if (dic.TryGetValue(n, out counter))
            {
                // duplicate
                dic[n] = counter + 1;
            }
            else
            {
                // first occurance
                dic[n] = 1;
            }
        }
        else
        {
            // exclude duplucate in sub array
            subHash.Add(n);
        }
    }
}

可能是最快的代码(但不可读),复杂度为O(N):

SELECT USER();

答案 1 :(得分:1)

你可以跳过已经集成的序列,这种方式会快一点。

public static IEnumerable<T> UnionOfIntersects<T>(this IEnumerable<IEnumerable<T>> source)
{
    var result = new List<T>();
    var sequences = source.ToList();
    for (int sequenceIdx = 0; sequenceIdx < sequences.Count(); sequenceIdx++)
    {
        var sequence = sequences[sequenceIdx];

        for (int targetSequenceIdx = sequenceIdx + 1; targetSequenceIdx < sequences.Count; targetSequenceIdx++)
        {
            var targetSequence = sequences[targetSequenceIdx];
            var intersections = sequence.Intersect(targetSequence);
            result.AddRange(intersections);
        }
    }

    return result.Distinct();
}

工作原理?

Input: {/*0*/ { 1, 2, 3, 4, 5, 7 } ,/*1*/ { 5, 6, 7 },/*2*/ { 2, 6, 7, 9 } , /*3*/{ 4 } }

步骤0:将0与1..3

相交

第1步:将1与2..3相交(0与1已经相交)

第2步:将2与3相交(0与2和1与2已经相交)

返回:不同的元素。

Result: { 2, 4, 5, 6, 7 }

您可以使用以下代码

进行测试
var lists = new List<List<int>>
{
    new List<int> {1, 2, 3, 4, 5, 7},
    new List<int> {5, 6, 7},
    new List<int> {2, 6, 7, 9},
    new List<int> {4 }
};

var result = lists.UnionOfIntersects();

答案 2 :(得分:1)

这应该非常接近最佳 - 如何&#34;可读&#34;这取决于你的口味。在我看来,它也是最易读的解决方案。

        var seenElements = new HashSet<T>();
        var repeatedElements = new HashSet<T>();

        foreach (var list in source)
        {
            foreach (var element in list.Distinct())
            {
                if (seenElements.Contains(element))
                {
                    repeatedElements.Add(element);
                }
                else
                {
                    seenElements.Add(element);
                }
            }
        }

        return repeatedElements;

答案 3 :(得分:0)

应该指出:

int[][] test = { new int[] { 1, 2, 3, 4, 5, 7 }, new int[] { 5, 6, 7 }, new int[] { 2, 6, 7, 9 }, new int[] { 4 } };
var result = test.SelectMany(a => a.Distinct()).GroupBy(x => x).Where(g => g.Count() > 1).Select(y => y.Key).ToList();

首先确保每个序列中没有重复项。然后将所有序列连接到单个序列,并查找重复序列,例如, here

答案 4 :(得分:0)

您可以尝试这种方法,它可能更有效,并且还允许指定最小交叉计数和使用的比较器:

public static IEnumerable<T> UnionOfIntersects<T>(this IEnumerable<IEnumerable<T>> source 
    , int minIntersectionCount
    , IEqualityComparer<T> comparer = null)
{
    if (comparer == null) comparer = EqualityComparer<T>.Default;
    foreach (T item in source.SelectMany(s => s).Distinct(comparer))
    {
        int containedInHowManySequences = 0;
        foreach (IEnumerable<T> seq in source)
        {
            bool contained = seq.Contains(item, comparer);
            if (contained) containedInHowManySequences++;
            if (containedInHowManySequences == minIntersectionCount)
            {
                yield return item;
                break;
            }
        }
    }
}

一些解释词:

  • 它列举了所有序列中的所有唯一项。由于Distinct正在使用集合,因此这应该非常有效。如果所有序列中有许多重复,这可以帮助加快速度。
  • 如果包含唯一项目,内部循环只会查看每个序列。它使用Enumerable.Contains一旦找到一个项目就停止执行(因此重复没有问题)。
  • 如果交叉点达到最小交叉点计数,则会产生此项目并检查下一个(唯一)项目。