2010-08-14 63 views
0

我想在C#中開發一個簡單的應用程序來計算列表框中重複的數量。我需要計算所有重複項的數量,並將排名後綴顯示爲最重複的前3個元素。例如,假設列表中有7個稱爲'apple'的元素,6個元素稱爲'pear',4個元素稱爲'桃',3個元素稱爲'orange',處理後應該顯示如下列表:計數列表框中的重複

 
apple (7) 
pear (6) 
peach (4) 
orange 
+0

你有什麼數據源?數據表? – thelost 2010-08-14 07:41:10

+1

你可以發佈你到目前爲止的代碼嗎? – Oded 2010-08-14 07:41:12

回答

0

這是一種使用Linq的替代方法,以定時測試的形式呈現,以查看哪個更快。這些是我用1000次迭代獲得的結果:

Total words: 1324 
Min  Max  Mean  Method 
5305  22889  5739.182 LinkMethodToArray 
5053  11973  5418.355 LinkMethod 
3112  6726  3252.457 HashMethod 

LinkMethod在這種情況下只有大約1.6倍的慢。沒有我測試過的很多Linq代碼那麼糟糕,但它只有1324個字。

編輯#1

這是添加排序之前。用這種方法,你可以看到它與Linq方法是可比較的。當然,將哈希複製到列表中,然後對列表進行排序並不是實現此目的的最有效方式。我們可以改進這一點。有幾個想法,但沒有一個是簡單的,需要編寫大量的自定義代碼。

由於我們想要使用已經可用的代碼,我們希望代碼清晰,所以我必須說Linq實際上是一個非常好的選擇。這改變了我對Linq的一點看法。我看過很多其他的比較,Linq的結果是慢了很多(大約慢了1000倍),讓Linq在任何地方都能使用Linq,但肯定在這個地方它發光的很好。

我想道德是,因爲它一直是,測試,測試,測試。

以下是添加到HashMethod中的排序值。

Total words: 1324 
Min  Max  Mean  Method 
5284  21030  5667.808 LinkMethodToArray 
5081  36339  5425.626 LinkMethod 
5017  27583  5288.602 HashMethod 

編輯#2

幾個簡單的優化(初始化前兩個字典和列表)使HashMethod有點noticably更快。

Total words: 1324 
Min  Max  Mean  Method 
5287  16299  5686.429 LinkMethodToArray 
5081  21813  5440.758 LinkMethod 
4588  8420  4710.659 HashMethod 

編輯#3

有了更大的詞集,他們變得更加均勻。實際上,Linq方法似乎每次都會出現。這是美國憲法(全部七篇文章和簽名)。這可能是由於該聲明包含大量重複詞(「他有......」)。

Total words: 4545 
Min  Max  Mean  Method 
13363  36133  14086.875 LinkMethodToArray 
12917  26532  13668.914 LinkMethod 
13601  19435  13836.955 HashMethod 

代碼:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.IO; 
using System.Linq; 
using System.Threading; 

class Program 
{ 
    static void Main() 
    { 
     Thread.CurrentThread.Priority = ThreadPriority.Highest; 

     // Declaration.txt is a copy of the Declaration of Independence 
     // which can be found here: http://en.wikisource.org/wiki/United_States_Declaration_of_Independence 
     string declaration = File.ReadAllText("Declaration.txt"); 
     string[] items = declaration.ToLower().Split(new char[] { ',', '.', ':', ';', '-', '\r', '\n', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries); 

     // pre-execute outside timing loop 
     LinqMethodToArray(items); 
     LinqMethod(items); 
     HashMethod(items); 

     int iterations = 1000; 
     long min1 = long.MaxValue, max1 = long.MinValue, sum1 = 0; 
     long min2 = long.MaxValue, max2 = long.MinValue, sum2 = 0; 
     long min3 = long.MaxValue, max3 = long.MinValue, sum3 = 0; 

     Console.WriteLine("Iterations: {0}", iterations); 
     Console.WriteLine("Total words: {0}", items.Length); 

     Stopwatch sw = new Stopwatch(); 

     for (int n = 0; n < iterations; n++) 
     { 
      sw.Reset(); 
      sw.Start(); 
      LinqMethodToArray(items); 
      sw.Stop(); 
      sum1 += sw.ElapsedTicks; 
      if (sw.ElapsedTicks < min1) 
       min1 = sw.ElapsedTicks; 
      if (sw.ElapsedTicks > max1) 
       max1 = sw.ElapsedTicks; 

      sw.Reset(); 
      sw.Start(); 
      LinqMethod(items); 
      sw.Stop(); 
      sum2 += sw.ElapsedTicks; 
      if (sw.ElapsedTicks < min2) 
       min2 = sw.ElapsedTicks; 
      if (sw.ElapsedTicks > max2) 
       max2 = sw.ElapsedTicks; 

      sw.Reset(); 
      sw.Start(); 
      HashMethod(items); 
      sw.Stop(); 
      sum3 += sw.ElapsedTicks; 
      if (sw.ElapsedTicks < min3) 
       min3 = sw.ElapsedTicks; 
      if (sw.ElapsedTicks > max3) 
       max3 = sw.ElapsedTicks; 
     } 

     Console.WriteLine("{0,-10} {1,-10} {2,-10} Method", "Min", "Max", "Mean"); 
     Console.WriteLine("{0,-10} {1,-10} {2,-10} LinkMethodToArray", min1, max1, (double)sum1/iterations); 
     Console.WriteLine("{0,-10} {1,-10} {2,-10} LinkMethod", min2, max2, (double)sum2/iterations); 
     Console.WriteLine("{0,-10} {1,-10} {2,-10} HashMethod", min3, max3, (double)sum3/iterations); 
    } 

    static void LinqMethodToArray(string[] items) 
    { 
     var ranking = (from item in items 
         group item by item into r 
         orderby r.Count() descending 
         select new { Name = r.Key, Rank = r.Count() }).ToArray(); 
     for (int n = 0; n < ranking.Length; n++) 
     { 
      var item = ranking[n]; 
      DoSomethingWithItem(item); 
     } 
    } 

    static void LinqMethod(string[] items) 
    { 
     var ranking = (from item in items 
         group item by item into r 
         orderby r.Count() descending 
         select new { Name = r.Key, Rank = r.Count() }); 
     foreach (var item in ranking) 
      DoSomethingWithItem(item); 
    } 

    static void HashMethod(string[] items) 
    { 
     var ranking = new Dictionary<string, int>(items.Length/2); 
     foreach (string item in items) 
     { 
      if (!ranking.ContainsKey(item)) 
       ranking[item] = 1; 
      else 
       ranking[item]++; 
     } 
     var list = new List<KeyValuePair<string, int>>(ranking); 
     list.Sort((a, b) => b.Value.CompareTo(a.Value)); 
     foreach (KeyValuePair<string, int> pair in list) 
      DoSomethingWithItem(pair); 

    } 

    static volatile object hold; 
    static void DoSomethingWithItem(object item) 
    { 
     // This method exists solely to prevent the compiler from 
     // optimizing use of the item away so that this program 
     // can be executed in Release build, outside the debugger. 
     hold = item; 
    } 
} 
+0

我不希望看到挑剔,尤其是因爲我完全同意你在這裏。但是從Linq方法通過排序明確排序結果的角度來看,你的HashMethod沒有提供與LINQ方法相同的結果,在這種方法中,散列方法跳過了這一步。 – 2010-08-14 16:58:15

+0

廢話,你說得對。我完全錯過了那裏的船。我會解決它的排序。這可能是Linq擅長的情況嗎?稍後調整以找出... – Tergiver 2010-08-14 17:20:54

+0

它就在那裏。在這種情況下,Linq工作得很好。 – Tergiver 2010-08-14 18:06:01

2

由於我們不知道您使用的數據源,因此這裏有一個通用的LINQ示例,可以幫助您開始。

string[] items = { "apple", "pear", "peach", "apple", "orange", "peach", "apple" }; 

var ranking = (from item in items 
       group item by item into r 
       orderby r.Count() descending 
       select new { name = r.Key, rank = r.Count() }).Take(3); 

這將返回包含前3項的name and rank的對象集合。

當然,你可以用這個數組替換你使用的每個數據源來填充ListBox,如果這些項目不僅僅是簡單的字符串,而且還有更復雜的項目,你可以適當地調整LINQ查詢。

這是上面的一個例子,它將用你所顯示的表格填充數據列表框。

string[] items = { "apple", "pear", "peach", "apple", "orange", "peach", "apple" }; 

    var ranking = (from item in items 
       group item by item into r 
       orderby r.Count() descending 
       select new { name = r.Key, rank = r.Count() }).ToArray(); 

    for (int i = 0; i < ranking.Length; ++i) 
    { 
    var item = ranking[i]; 
    if (i < 3) 
    { 
     listBox1.Items.Add(string.Format("{0} ({1})", item.name, item.rank)); 
    } 
    else 
    { 
     listBox1.Items.Add(item.name); 
    } 
    } 

這確實相同的第一個例子,但變換的結果到一個數組並填充與第一3項表示有秩的商品的列表框。

+0

+1解決問題。我想知道你的Linq傢伙是什麼,並且當它完全和完全沒有必要的時候,強迫性的需要調用ToArray。你意識到這會分配一個新的內存塊,執行一個列表枚舉,並複製每個值 - 對嗎? Linq的速度不夠慢,爲什麼不必要地使速度更慢? – Tergiver 2010-08-14 13:00:23

+0

@Tergiver,:)非常感謝您給我打電話給Linq的人,因爲我專門針對允許我學習Linq的問題,所以我並不傾向於使用Linq。即使這個簡單的例子,我不得不參考101個樣本,但我學到了更多關於Linq的知識。當然,ToArray正在分配額外的內存塊,沒有爭論,雖然我確實認爲我剛剛決定,對或錯,對於這個例子它不會是一個問題。 – 2010-08-14 13:43:17

+0

對不起,我今天沒有接受我每天的Linq抨擊;) – Tergiver 2010-08-14 15:35:00