我有一个
List<string>
1500个字符串。我现在使用以下代码仅提取以字符串prefixText开头的字符串。
foreach(string a in <MYLIST>)
{
if(a.StartsWith(prefixText, true, null))
{
newlist.Add(a);
}
}
这很快,但我正在寻找谷歌快速。现在我的问题是,如果我按字母顺序排列List,然后比较char by char我可以加快速度吗?或者其他任何有关加快速度的建议?
答案 0 :(得分:13)
因此1500对于排序列表上的二进制搜索实际上并不是很大。 然而,用于前缀搜索的最有效算法基于名为Trie或Prefix Tree的数据结构。请参阅:http://en.wikipedia.org/wiki/Trie
下图简要介绍了这个想法:
答案 1 :(得分:4)
如果您的列表按照字母顺序排列,则可以使用binary search的变体来加快速度。
作为一个起点,这将返回与前缀匹配的其中一个字符串的索引,因此您可以在列表中向前和向后查找其余内容:
public static int BinarySearchStartsWith(List<string> words, string prefix, int min, int max) {
while (max >= min) {
int mid = (min + max) / 2;
int comp = String.Compare(words[mid].Substring(0, prefix.Length), prefix);
if (comp < 0) {
min = mid + 1;
} else if (comp > 0) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}
int index = BinarySearchStartsWith(theList, "pre", 0, theList.Count - 1);
if (index == -1) {
// not found
} else{
// found
}
注意:如果你使用比任何比较的字符串都长的前缀,它会中断,所以你可能需要弄清楚你想要如何处理它。
答案 2 :(得分:4)
分析了许多方法,以达到最低数据容量和高性能。第一个地方是:所有前缀都存储在字典中:key - prefix,values - 适合前缀的项目。
这里简单实现了这个算法:
public class Trie<TItem>
{
#region Constructors
public Trie(
IEnumerable<TItem> items,
Func<TItem, string> keySelector,
IComparer<TItem> comparer)
{
this.KeySelector = keySelector;
this.Comparer = comparer;
this.Items = (from item in items
from i in Enumerable.Range(1, this.KeySelector(item).Length)
let key = this.KeySelector(item).Substring(0, i)
group item by key)
.ToDictionary( group => group.Key, group => group.ToList());
}
#endregion
#region Properties
protected Dictionary<string, List<TItem>> Items { get; set; }
protected Func<TItem, string> KeySelector { get; set; }
protected IComparer<TItem> Comparer { get; set; }
#endregion
#region Methods
public List<TItem> Retrieve(string prefix)
{
return this.Items.ContainsKey(prefix)
? this.Items[prefix]
: new List<TItem>();
}
public void Add(TItem item)
{
var keys = (from i in Enumerable.Range(1, this.KeySelector(item).Length)
let key = this.KeySelector(item).Substring(0, i)
select key).ToList();
keys.ForEach(key =>
{
if (!this.Items.ContainsKey(key))
{
this.Items.Add(key, new List<TItem> { item });
}
else if (this.Items[key].All(x => this.Comparer.Compare(x, item) != 0))
{
this.Items[key].Add(item);
}
});
}
public void Remove(TItem item)
{
this.Items.Keys.ToList().ForEach(key =>
{
if (this.Items[key].Any(x => this.Comparer.Compare(x, item) == 0))
{
this.Items[key].RemoveAll(x => this.Comparer.Compare(x, item) == 0);
if (this.Items[key].Count == 0)
{
this.Items.Remove(key);
}
}
});
}
#endregion
}
答案 3 :(得分:3)
您可以使用PLINQ (Parallel LINQ)来加快执行速度:
var newList = list.AsParallel().Where(x => x.StartsWith(prefixText)).ToList()
答案 4 :(得分:1)
我认为真正最快的方法是生成一个包含1500个字符串中所有可能前缀的字典,有效地预先计算将返回非空的所有可能搜索的结果。然后,您的搜索将只是在O(1)时间内完成的字典查找。这是交易记忆(和初始化时间)的一种情况。
private IDictionary<string, string[]> prefixedStrings;
public void Construct(IEnumerable<string> strings)
{
this.prefixedStrings =
(
from s in strings
from i in Enumerable.Range(1, s.Length)
let p = s.Substring(0, i)
group s by p
).ToDictionary(
g => g.Key,
g => g.ToArray());
}
public string[] Search(string prefix)
{
string[] result;
if (this.prefixedStrings.TryGetValue(prefix, out result))
return result;
return new string[0];
}
答案 5 :(得分:1)
通过在调用StartsWith:
之前比较第一个字符,可以加速一点char first = prefixText[0];
foreach(string a in <MYLIST>)
{
if (a[0]==first)
{
if(a.StartsWith(prefixText, true, null))
{
newlist.Add(a);
}
}
}
答案 6 :(得分:1)
1500通常太少了:
你可以通过简单的划分和征服来解决问题。将列表的每一半搜索成两个(或分成三个,四个,......,部分)不同的作业/线程。
或者将字符串存储在(非二进制)树中。将是O(log n)。
按字母顺序排序,您可以进行二元搜索(与前一个搜索相同)
答案 7 :(得分:0)
您是否尝试过实施字典并比较结果?或者,如果您按字母顺序排列条目,请尝试二进制搜索。
答案 8 :(得分:0)
对我而言,问题是你是否需要这样做一次或多次。
如果您只找到一次StartsWithPrefix列表,则无法更快,然后按原样保留原始列表并执行myList.Where(s => s.StartsWith(prefix))
。这会查看每个字符串一次,因此它是O(n)
如果您需要多次找到StartsWithPrefix列表,或者您可能想要在原始列表中添加或删除字符串并更新StartsWithPrefix列表,那么您应该对原始列表进行排序并使用二进制搜索。但这将是sort time + search time = O(n log n) + 2 * O(log n)
如果您使用二进制搜索方法,您将找到第一次出现前缀的索引以及最后一次出现的索引。然后执行mySortedList.Skip(n).Take(m-n)
,其中n是第一个索引,m是最后一个索引。
等一下,我们正在使用错误的工具来完成工作。使用Trie!如果你将所有的字符串放入Trie而不是列表中,那么你所要做的就是沿着你的前缀走下trie并抓住那个节点下面的所有单词。
答案 9 :(得分:-2)
我会选择使用Linq:
var query = list.Where(w => w.StartsWith("prefixText")).Select(s => s).ToList();