搜索列表<string>表示字符串.StartsWith()</string>

时间:2012-05-06 18:04:08

标签: c# string performance list

我有一个

List<string>

1500个字符串。我现在使用以下代码仅提取以字符串prefixText开头的字符串。

foreach(string a in <MYLIST>)
{            
    if(a.StartsWith(prefixText, true, null))
    {
        newlist.Add(a);                   
    }            
}

这很快,但我正在寻找谷歌快速。现在我的问题是,如果我按字母顺序排列List,然后比较char by char我可以加快速度吗?或者其他任何有关加快速度的建议?

10 个答案:

答案 0 :(得分:13)

因此1500对于排序列表上的二进制搜索实际上并不是很大。 然而,用于前缀搜索的最有效算法基于名为Trie或Prefix Tree的数据结构。请参阅:http://en.wikipedia.org/wiki/Trie

下图简要介绍了这个想法:  enter image description here

对于c#实现,请参阅例如.NET DATA STRUCTURES FOR PREFIX STRING SEARCH AND SUBSTRING (INFIX) SEARCH TO IMPLEMENT AUTO-COMPLETION AND INTELLI-SENSE

答案 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();