有效地查找出现次数最多的相同数组

时间:2019-03-22 09:36:05

标签: c# linq .net-core

假设我有以下嵌套数组:

[
    [1, 2, 3],
    [4, 7, 9, 13],
    [1, 2],
    [2, 3]
    [12, 15, 16]
]

我只需要出现最多的相同数字的数组。在上面的示例中,将是:

[
    [1, 2, 3],
    [4, 7, 9, 13],
    [12, 15, 16]
]

如何使用C#有效地做到这一点?

编辑 确实,我的问题确实令人困惑。我想问的是:如果某个较大的子数组已经包含了较小子数组的所有元素,该如何消除子数组。

我当前对该问题的实现如下:

var allItems = new List<List<int>>{
            new List<int>{1, 2, 3},
            new List<int>{4, 7, 9, 13},
            new List<int>{1, 2},
            new List<int>{2, 3},
            new List<int>{12, 15, 16}
        };

var itemsToEliminate = new List<List<int>>();

for(var i = 0; i < allItems.ToList().Count; i++){
    var current = allItems[i];
    var itemsToVerify = allItems.Where(item => item != current).ToList();
    foreach(var item in itemsToVerify){
        bool containsSameNumbers = item.Intersect(current).Any();
        if(containsSameNumbers && item.Count > current.Count){
            itemsToEliminate.Add(current);          
        }
    }
}
allItems.RemoveAll(item => itemsToEliminate.Contains(item));
foreach(var item in allItems){
    Console.WriteLine(string.Join(", ", item));
}

这确实有效,但是嵌套循环for(var i = 0; i < allItems.ToList().Count; i++)foreach(var item in itemsToVerify)的性能很差。特别是如果您知道allItems数组可以包含大约10000000行。

1 个答案:

答案 0 :(得分:2)

我会记住列表中已经存在的项目。
首先通过减少长度对列表进行排序,然后检查每一项是否已经存在。 根据您的算法,即使在已知整数列表中甚至没有一个整数也不会添加数组。

因此,我将使用以下算法:

List<List<int>> allItems = new List<List<int>>{
    new List<int>{1, 2, 3},
    new List<int>{4, 7, 9, 13},
    new List<int>{1, 2},
    new List<int>{2, 3},
    new List<int>{12, 15, 16}
};

allItems = allItems.OrderByDescending(x => x.Count()).ToList(); // order by length, decreasing order

List<List<int>> result = new List<List<int>>();
SortedSet<int> knownItems = new SortedSet<int>(); // keep track of numbers, so you don't have to loop arrays
// https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.sortedset-1?view=netframework-4.7.2

foreach (List<int> l in allItems)
{
    // bool allUnique = true;
    foreach (int elem in l)
    {
        if (knownItems.Contains(elem))
        {
            // allUnique = false;
            break;
        }
        else
        {
            // OK, because duplicates not allowed in single list
            // and because how the data is constrained (I still have my doubts about how the data is allowed to look and what special cases may pop up that ruin this, so use with care)
            // this WILL cause problems if a list starts with any number which has not yet been provided appears before the first match that would cause the list to be discarded.
            knownItems.Add(elem);
        }
    }
    // see comment above near knownItems.Add()
    /*
    if (allUnique)
    {
        result.Add(l);
        foreach (int elem in l)
        {
            knownItems.Add(elem);
        }
    }
    */
}

// output
foreach(List<int> item in result){
    Console.WriteLine(string.Join(", ", item));
}

您不必一次嵌套遍历原始数组两次(O(n ^ 2)),而只需一次遍历(O(n ^ 2))并进行已知数的搜索(二进制搜索树查找:O(n * log2) (n))。 无需从阵列中删除,而是添加到新阵列。这将为新阵列使用更多内存。完成重新排序的原因是,任何后续数组都更有可能包含已处理的数字。但是,对大量列表进行排序可能比如果有许多小列表时获得的收益要慢。如果您有几个长的,那可能会有所回报。

按长度排序列表列表是有效的,因为

  

如果列表中有不同列表中的项目,该怎么办?说不是新列表{2,3},而是新列表{2,4}?


  

这种意外行为。您可以将整数视为一个人的ID。每一组int组成一个家庭。如果算法创建了[2,4],那么我们正在创建例如婚外关系。这是不可取的。

由此我收集到的数组将最多包含一个其他数组的子集,或者是唯一的。因此,订单无关紧要。 这也假设至少一个这样的数组将包含这些子集的所有元素(因此是最长的数组,排在最前面)。
如果不是这样,则可以将其删除,如果有疑问,应该将其删除。

例如:

{1, 2, 3, 4, 5} - contains all elements that future arrays will have subsets of
{1, 4, 5} - must contain no element that {1,2,3,4,5} does not contain
{1, 2, 6} - illegal in this case
{7, 8 ,9} - OK
{8, 9} - OK (will be ignored)
{7, 9} - OK (will be ignored, is only subset in {7,8,9})
{1, 7} - - illegal, but would be legal if {1,2,3,4,5,7,8,9} was in this list. because it is longer it would've been earlier, making this valid to ignore.