如何快速判断列表是否包含列表?

时间:2010-11-16 10:04:30

标签: c# .net algorithm list mahjong

有多个相关问题,但我正在寻找针对我的案例的解决方案。有一个(通常)14个整数的数组,每个整数在1到34的范围内。如何快速判断特定静态列表中的每个int是否在此数组中至少出现一次?

作为参考,我目前正在使用这段代码,它尽可能地编写成与规范类似的代码,所以它当然可以大大改进:

if (array.Count < 13) {
    return;
}

var required = new int[] {
    0*9 + 1,
    0*9 + 9,
    1*9 + 1,
    1*9 + 9,
    2*9 + 1,
    2*9 + 9,                
    3*9 + 1,
    3*9 + 2,
    3*9 + 3,
    3*9 + 4,
    3*9 + 5,
    3*9 + 6,
    3*9 + 7,
};

IsThirteenOrphans = !required.Except (array).Any ();

所需列表不是动态的,即在运行时它始终是相同的。使用Linq是可选的,主要方面是性能。

修改

  • 输入数组未排序。
  • 输入值可能会多次出现。
  • 输入数组将包含至少14个项目,即比所需数组多1个。
  • 只有一个必需的数组,它是静态的。
  • 所需的值是不同的。
  • 您可能认为直方图的制作成本低廉。

更新:我也对排序输入数组的解决方案感兴趣。

7 个答案:

答案 0 :(得分:1)

创意1
如果您需要与几个required列表进行比较,那么您可以对输入列表进行排序,然后通过迭代进行比较。但排序当然不是太快,但也不是太慢。但是,如果您与几个必需的列表进行比较,排序的开销可能会很快摊销。

一旦数组被排序,比较是微不足道的:

for(int i = 0; i < 14; i++)
  if(arr[i] != required[i]) return false;

return true;

创意2
或者,如果14个整数是不同/唯一的,您可以简单地将required设为HashSet并执行

input.Count(i => required.Contains(i)) == 14

但如果没有实际测试它,如果这比排序更快,我不知道。

创意3
计算一个快速哈希值,该哈希值在14个整数的排列下是不变的,并将其与require的已知值进行比较。只有哈希匹配才能进行更昂贵的比较。

//Prepare/precalculated
int[] hashes = new int[34];
Random random = new Random();
for(int i = 0; i < 34; i++)
  hashes[i] = random.NextInt();

//On each list
int HashInts(int[] ints)
{
  int result = 0;
  foreach(int i in ints)
    result += hashes[i - 1];

  return result;
}

明智地选择hashes的值可能会有所改善,但随机值应该没问题。

创意4
创建直方图:

int[] CreateHistogram(int[] ints)
{
  int[] counts = new int[34];
  foreach(int i in ints)
  {
    counts[i - 1]++;
  }

  return counts;
}

如果性能非常重要,您可以通过重用现有阵列来避免创建阵列。

答案 1 :(得分:1)

如果你想要快速的方式,你不应该使用linq,如果给定的列表项全部都在下面35,你可以删除if (lst[i] < 35) 波纹管回答遍历列表最多一次,行为类似于counting sort

public bool FindExactMatch(int[] array, List<int> lst)
{
    bool[] a34 = new bool[35];

    foreach(var item in array)
    {
        a34[item] = true;
    }

    int exact = 0;

    for (int i = 0; i < lst.Count; i++)
    {
        if (a34[lst[i]])
        {
            exact++;
            if (exact == array.Length) return true;

            a34[lst[i]] = false;
        }
    }

    return false;
}

对于排序列表,如果列表大小很大,你可以lst.BinarySearch(array[i])最多需要14 * log(n)* c1,而且我认为它也足够有效,如果你实现它可能会变得更快,我没有使用我自己的实现测试二进制搜索,但是linq中的Min,Max,Sort比你自己的(好的)实现慢(4到10次)。 如果排序列表大小很小,我更喜欢使用上面的算法,因为常量c1在上述算法中很小,在二进制搜索算法中可能更大。

答案 2 :(得分:1)

如果你有一个34位以上的积分类型和C样式位操作,那么你可以从变量列表中计算这样一个整数V(如果列表是v [0],v [1],...那么V是(1

答案 3 :(得分:1)

一种可能性是更改存储数据的方式。由于可能值的范围限制为1-34,而不是存储数字列表,您可以存储每个数字的计数:

int[] counts = new int[34];

如果你的列表有一个1和两个3,那么计数[0] == 1&amp;&amp; count [2] = 2(你可以选择使用基于1的索引,如果这会使事情变得更快(减少更少)。)

现在要评估列表中的每个int至少出现一次,您只需按顺序为每个x索引数组,并验证所有计数[x]&gt; 0.将数据从计数表单转换为列表表单会产生相关的开销,但如果您经常需要查看列表表单中的数据,则这只是一个问题。这种存储格式的另一个优点是添加/删除计数永远不会涉及多个数组元素;在列表表单中,删除列表中间的元素需要多个元素的副本。

答案 4 :(得分:1)

这看起来像是按位运算的一个很好的候选者,因为所需的值是不同的,静态的,在1到34之间。而不是将所需的值保存为数组,将其保存为const ulong。在要检查的数组中,创建一个新的ulong填充左移每个值并按位或。

 <h1>new</h1>

<%= form_for @comment do |form| %>
 <div class="form-group">
 <p>
  <%= form.label :fav_drink %>
  <%= form.text_field :fav_drink %>
  <%= form.hidden_field :subscriber_id %>
 </p>
  <%= form.submit "Send", class: "btn btn-primary" %>
 </div>
 <% end %>

答案 5 :(得分:0)

您可以轻松遍历这些项目并找出是否有任何项目丢失。通过你的例子我明白你只是想知道数组中是否有任何需要的项目。所以你可以写

bool notContains = false;

foreach (var iddd in required)
{
    foreach (var ar in array)
    {
        if (iddd == ar) notContains=true;
    }

    if (notContains == false) break;
}

这比你的方法要快得多。添加计时器,查看以下代码。你的方法花了5毫秒,但新的花了0毫秒

System.Diagnostics.Stopwatch aTimer = new System.Diagnostics.Stopwatch();
aTimer.Start();

var IsThirteenOrphans = !required.Except(array).Any();
aTimer.Stop();

System.Diagnostics.Stopwatch bTimer = new System.Diagnostics.Stopwatch();
bTimer.Start();
bool notContains = false;

foreach (var iddd in required)
{
    foreach (var ar in array)
    {
        if (iddd == ar) notContains=true;            
    }

    if (notContains == false) break;
}

bTimer.Stop();

答案 6 :(得分:0)

修改 所以我理解你的问题,可能有一些很复杂的解决方案。另一个问题是,它的表现有多好。

static void Main(string[] args)
{
    var required = new int[]
                       {
                           0*9 + 1,
                           0*9 + 9,
                           1*9 + 1,
                           1*9 + 9,
                           2*9 + 1,
                           2*9 + 9,
                           3*9 + 1,
                           3*9 + 2,
                           3*9 + 3,
                           3*9 + 4,
                           3*9 + 5,
                           3*9 + 6,
                           3*9 + 7,
                       };

    precomputed = required.Select((x, i) => new { Value = x, Offset = (UInt16)(1 << i) }).ToDictionary(x => x.Value, x => x.Offset);

    for (int i = 0; i < required.Length; i++)
    {
        precomputedResult |= (UInt16)(1 << i);
    }

    int[] array = new int[34]; // your array goes here..
    Console.WriteLine(ContainsList(array));

    Console.ReadKey();
}

// precompute dictionary
private static Dictionary<int, UInt16> precomputed;
// precomputed result
private static UInt16 precomputedResult = 0;

public static bool ContainsList(int[] values)
{
    UInt16 result = 0;
    for (int i = 0; i < values.Length; i++)
    {
        UInt16 v;
        if (precomputed.TryGetValue(values[i], out v))
            result |= v;
    }

    return result == precomputedResult;
}