我想在C#中開發一個簡單的應用程序來計算列表框中重複的數量。我需要計算所有重複項的數量,並將排名後綴顯示爲最重複的前3個元素。例如,假設列表中有7個稱爲'apple'的元素,6個元素稱爲'pear',4個元素稱爲'桃',3個元素稱爲'orange',處理後應該顯示如下列表:計數列表框中的重複
apple (7) pear (6) peach (4) orange
我想在C#中開發一個簡單的應用程序來計算列表框中重複的數量。我需要計算所有重複項的數量,並將排名後綴顯示爲最重複的前3個元素。例如,假設列表中有7個稱爲'apple'的元素,6個元素稱爲'pear',4個元素稱爲'桃',3個元素稱爲'orange',處理後應該顯示如下列表:計數列表框中的重複
apple (7) pear (6) peach (4) orange
這是一種使用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;
}
}
由於我們不知道您使用的數據源,因此這裏有一個通用的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項表示有秩的商品的列表框。
+1解決問題。我想知道你的Linq傢伙是什麼,並且當它完全和完全沒有必要的時候,強迫性的需要調用ToArray。你意識到這會分配一個新的內存塊,執行一個列表枚舉,並複製每個值 - 對嗎? Linq的速度不夠慢,爲什麼不必要地使速度更慢? – Tergiver 2010-08-14 13:00:23
@Tergiver,:)非常感謝您給我打電話給Linq的人,因爲我專門針對允許我學習Linq的問題,所以我並不傾向於使用Linq。即使這個簡單的例子,我不得不參考101個樣本,但我學到了更多關於Linq的知識。當然,ToArray正在分配額外的內存塊,沒有爭論,雖然我確實認爲我剛剛決定,對或錯,對於這個例子它不會是一個問題。 – 2010-08-14 13:43:17
對不起,我今天沒有接受我每天的Linq抨擊;) – Tergiver 2010-08-14 15:35:00
你有什麼數據源?數據表? – thelost 2010-08-14 07:41:10
你可以發佈你到目前爲止的代碼嗎? – Oded 2010-08-14 07:41:12