对列表中的每个项目进行操作,并与列表中的其他项目进行排除的操作。

6

我有一个没有重复数据的列表。以本例为例,假设我的列表如下:

List<string> list1 = new List<string>() { "A", "B", "C", "D" };

我希望对列表中的每个项目执行操作,与列表中的其他项目进行比较,但不包括已经执行过操作的项目(A-B和B-A),或者它们是相同的项目(A-A)。
例如:
A against B
A against C
A against D
B against C
B against D
C against D

现在,这个操作非常简单,但是我的列表非常庞大,这个过程可能会非常耗时。另外,根据我拥有的数据,如果操作已经完成或者没有匹配的数据需要运行该操作,则不需要再次运行该操作。
A against A - Skip
A against B - Good
A against C - Good
A against D - Good
B against A - Skip (we already did A against B)
B against B - Skip
B against C - Good
B against D - Good
C against A - Skip

等等以此类推。

我一直在寻找一个简单的方法来完成这个操作,但我不知道是否存在这样的方法,而不是启动两个循环并执行我的操作,并保存结果以供以后比较。

遍历列表的时间复杂度为O(n*n),但由于我不需要比较一半以上的结果,所以这是浪费时间的,因为我知道我只需要检查O(n*(n/2))

我目前正在使用的代码如下:

List<string> list1 = new List<string>() { "A", "B", "C", "D" };
List<string> list2 = new List<string>(list1);
List<string> listResult = new List<string>();

list2.Reverse();

int i = 0;
foreach (var a in list1)
{
    for (int j = 0; j < (list2.Count / 2); j++)
    {
        i++;
        Console.WriteLine("Looped {0} times", i);

        // Don't run against ourself
        if (a == list2[j])
            continue;

        if (listResult.Count(x => (x == a + list2[j]) || (x == list2[j] + a)) == 0)
        {
            listResult.Add(a + list2[j]);

            // Perform some operation here
            // operation(a, list2[j]);
        }
    }
}

上述代码运行良好(我需要调整list2.Count / 2部分以适应奇数个列表)。

有更好的方法吗?我错过了什么LINQ扩展方法吗?我的问题是我真的不知道该搜索什么。

我想知道是否有一种方法可以返回仅包含我想要的项目的列表,然后我会循环遍历并执行我的操作。也许使用.SelectMany()之类的东西。


你的列表中所有项目都是唯一的吗?例如,你的列表是否可能是 A B A C D - BJ Myers
list1 中是否有重复项? - ASh
(顺便说一句 - 上面的代码跳过了对A和B的操作。) - BJ Myers
列表中将永远不会有重复的内容。 - Gareth Hastings
4个回答

12

针对列表中的每一项,将其与列表中所有在它后面的项匹配,因为前面的项已经匹配过了。

List<string> list1 = new List<string>() { "A", "B", "C", "D" };

for( int i = 0; i < list1.Count - 1; i++ )
    for( int j = i + 1; j < list1.Count; j++ )
        Console.WriteLine( "{0} against {1}", list1[i], list1[j] );

编辑:至于您的第二个问题,可以尝试类似以下的解决方案:

public static class Extensions
{
    public static IEnumerable<U> Combinations<T, U>( this IEnumerable<T> list,
                                                     Func<T, T, U> combinator )
    {
        var temp = list.ToArray();
        for( int i = 0; i < temp.Length - 1; i++ )
            for( int j = i + 1; j < temp.Length; j++ )
                yield return combinator( temp[i], temp[j] );
    }
}

然后可以像这样使用:

List<string> list1 = new List<string>() { "A", "B", "C", "D" };
var res = list1.Combinations( ( a, b ) => string.Format( "{0} against {1}", a, b ) );

如果您可以接受它仅支持IList而不是任何IEnumerable,那么您完全可以跳过ToArray调用。

3
我喜欢这种方法,因为它是最干净、最快的,并且不使用额外的内存。这意味着当处理非常大的列表时,它不会出现其他方法可能会遇到的"堆栈溢出"异常。 - Der Kommissar

5
只需将列表中每个元素与其后面的所有元素逐一运行即可。
for (int i = 0; i < list.Count; i++) {
    for (int j = i + 1; j < list.Count; j++) {
        //Run list[i] against list[j]
    }
}

这确保了没有任何元素会被运行两次,也不会对自身或已经运行过的元素再次运行。

1
在这里,我们通过嵌套的foreach循环遍历所有项。只有当项不存在或两者相同时(例如“AA”),才会添加该项。
List<string> list1 = new List<string>() { "A", "B", "C", "D" };
List<string> result = new List<string>();

foreach (string a in list1)
    foreach (string b in list1)
        if (!result.Contains(b + a) && a != b) result.Add(a + b);

4
为什么人们相信 LINQ 是解决任何问题的通用答案? - Ondrej Tucny
@OndrejTucny 这是一把很棒的工具。 - jdphenix
回滚到原始的“LINQ尝试”答案。完全不同的解决方案应该作为单独的答案发布,而不是编辑。 - Ondrej Tucny
答案并不是“完全”不同。我只是注意到.ForEach这样用并没有太多意义,所以我将其改为了普通的foreach语句。请不要再回滚了。 - bytecode77

1

我喜欢@Chris的回答,但如果你需要写出跳过的操作,可以很容易地转化为:

List<string> list1 = new List<string>() { "A", "B", "C", "D" };
List<string> listResult = new List<string>();

for (int i = 0; i < list1.Count; i++)
{
    for (int k = 0; k < list1.Count; k++)
    {
        if (k <= i)
        {
            Console.WriteLine("{0} against {1} - Skip", list1[i], list1[k]);
        }
        else
        {
            Console.WriteLine("{0} against {1} - Good", list1[i], list1[k]);
            listResult.Add(list1[i] + list1[k]);
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接