【问题标题】:fastest starts with search algorithm最快从搜索算法开始
【发布时间】:2018-01-12 12:41:45
【问题描述】:

我需要实现一个搜索算法,它只从字符串的开头而不是字符串中的任何地方搜索。

我是算法的新手,但据我所知,它们似乎遍历了字符串并找到了任何出现的地方。

我有一个字符串集合(超过 100 万个),每次用户键入按键时都需要对其进行搜索。

编辑:

这将是一个增量搜索。我目前使用以下代码实现了它,并且我的搜索从超过 100 万个可能的字符串中返回,范围在 300-700 毫秒之间。集合没有排序,但没有理由不能排序。

private ICollection<string> SearchCities(string searchString) {
        return _cityDataSource.AsParallel().Where(x => x.ToLower().StartsWith(searchString)).ToArray();
    }

【问题讨论】:

  • 所以你想找到一个集合中的所有字符串,这样你的输入字符串就是它们的前缀?你应该看看String.startsWith。你也应该自己发布一个尝试,我们会指出错误,从那里指导你。
  • 你将不得不更具体......你能发布一些代码来展示你到目前为止所拥有的东西吗?并更详细地说明您的意思。
  • 如果我怀疑您正在进行增量搜索,那么您可能需要查看Trie 数据结构。这允许非常快速地发现集合中以指定前缀开头的所有字符串。详情请见this Visual Studio Magazine article
  • 有一个String.StartWith函数。你想改进它吗???

标签: c# .net algorithm search


【解决方案1】:

我建议使用 linq。

string x = "searchterm";
List<string> y = new List<string>();
List<string> Matches = y.Where(xo => xo.StartsWith(x)).ToList();

其中 x 是您的击键搜索文本词,y 是您要搜索的字符串集合,Matches 是您集合中的匹配项。

我用前 100 万个素数对此进行了测试,这里是改编自上面的代码:

        Stopwatch SW = new Stopwatch();
        SW.Start();
        string x = "2";
        List<string> y = System.IO.File.ReadAllText("primes1.txt").Split(' ').ToList();
        y.RemoveAll(xo => xo == " " || xo == "" || xo == "\r\r\n");
        List <string> Matches = y.Where(xo => xo.StartsWith(x)).ToList();
        SW.Stop();
        Console.WriteLine("matches: " + Matches.Count);
        Console.WriteLine("time taken: " + SW.Elapsed.TotalSeconds);
        Console.Read();

结果是:

匹配:77025

耗时:0.4240604

当然这是针对数字进行测试,我不知道 linq 是否转换了之前的值,或者数字是否有任何区别。

【讨论】:

  • 鉴于 OP 似乎也需要性能,因此可能值得采用按字符的方法并连续改进一组匹配字符串。
  • @hnefatl 是的,我并不是 100% 支持 StartsWith 的方式,尽管我确实认为从简单性和速度方面考虑,linq 是一个很好的前进方向。如果 OP 可以对此进行测试并使用他们的数据返回结果,它可能会帮助其他人改进它以更快地工作:)
  • @Jaxi 我目前以这种方式实现了它,因为我只是想让一些工作正常,但现在我正在寻找性能改进
  • 刚看到你的代码,你的结果是从数据库加载的还是从文件加载的?
  • @Jaxi 从类似于您的文本文件中加载。我会测试看看是否有改进
【解决方案2】:

一些想法:

首先,需要对您的百万个字符串进行排序,以便您可以“查找”到第一个匹配的字符串并返回字符串,直到您不再有匹配项...按顺序(可能通过 C#List&lt;string&gt;.BinarySearch 查找) .这就是您尽可能少地触摸字符串的方式。

其次,您可能不应该尝试点击字符串列表,直到输入暂停至少 500 毫秒(给予或接受)。

第三,您对广阔空间的查询应该是异步且可取消的,因为下一次击键肯定会取代一次努力。

最后,任何后续查询都应​​首先检查新的搜索字符串是否是最新搜索字符串的追加...以便您可以从上次搜索开始后续搜索(节省大量时间)。

【讨论】:

  • 谢谢,我会看看 seek 和随后的查询追加。其他两个得到照顾。
【解决方案3】:

我已经改编了来自this article from Visual Studio Magazine 的代码,它实现了Trie

以下程序演示了如何使用Trie 进行快速前缀搜索。

为了运行该程序,您需要一个名为“words.txt”的文本文件,其中包含大量单词。 You can download one from Github here.

编译程序后,将“words.txt”文件复制到与可执行文件相同的文件夹中。

运行程序时,输入前缀(如prefix ;))并按return,它将列出所有以该前缀开头的单词。

这应该是一个非常快速的查找 - 有关更多详细信息,请参阅 Visual Studio 杂志文章!

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            var trie = new Trie();
            trie.InsertRange(File.ReadLines("words.txt"));

            Console.WriteLine("Type a prefix and press return.");

            while (true)
            {
                string prefix = Console.ReadLine();

                if (string.IsNullOrEmpty(prefix))
                    continue;

                var node = trie.Prefix(prefix);

                if (node.Depth == prefix.Length)
                {
                    foreach (var suffix in suffixes(node))
                        Console.WriteLine(prefix + suffix);
                }
                else
                {
                    Console.WriteLine("Prefix not found.");
                }

                Console.WriteLine();
            }
        }

        static IEnumerable<string> suffixes(Node parent)
        {
            var sb = new StringBuilder();
            return suffixes(parent, sb).Select(suffix => suffix.TrimEnd('$'));
        }

        static IEnumerable<string> suffixes(Node parent, StringBuilder current)
        {
            if (parent.IsLeaf())
            {
                yield return current.ToString();
            }
            else
            {
                foreach (var child in parent.Children)
                {
                    current.Append(child.Value);

                    foreach (var value in suffixes(child, current))
                        yield return value;

                    --current.Length;
                }
            }
        }
    }

    public class Node
    {
        public char Value { get; set; }
        public List<Node> Children { get; set; }
        public Node Parent { get; set; }
        public int Depth { get; set; }

        public Node(char value, int depth, Node parent)
        {
            Value = value;
            Children = new List<Node>();
            Depth = depth;
            Parent = parent;
        }

        public bool IsLeaf()
        {
            return Children.Count == 0;
        }

        public Node FindChildNode(char c)
        {
            return Children.FirstOrDefault(child => child.Value == c);
        }

        public void DeleteChildNode(char c)
        {
            for (var i = 0; i < Children.Count; i++)
                if (Children[i].Value == c)
                    Children.RemoveAt(i);
        }
    }

    public class Trie
    {
        readonly Node _root;

        public Trie()
        {
            _root = new Node('^', 0, null);
        }

        public Node Prefix(string s)
        {
            var currentNode = _root;
            var result = currentNode;

            foreach (var c in s)
            {
                currentNode = currentNode.FindChildNode(c);

                if (currentNode == null)
                    break;

                result = currentNode;
            }

            return result;
        }

        public bool Search(string s)
        {
            var prefix = Prefix(s);
            return prefix.Depth == s.Length && prefix.FindChildNode('$') != null;
        }

        public void InsertRange(IEnumerable<string> items)
        {
            foreach (string item in items)
                Insert(item);
        }

        public void Insert(string s)
        {
            var commonPrefix = Prefix(s);
            var current = commonPrefix;

            for (var i = current.Depth; i < s.Length; i++)
            {
                var newNode = new Node(s[i], current.Depth + 1, current);
                current.Children.Add(newNode);
                current = newNode;
            }

            current.Children.Add(new Node('$', current.Depth + 1, current));
        }

        public void Delete(string s)
        {
            if (!Search(s))
                return;

            var node = Prefix(s).FindChildNode('$');

            while (node.IsLeaf())
            {
                var parent = node.Parent;
                parent.DeleteChildNode(node.Value);
                node = parent;
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-24
    • 1970-01-01
    • 2018-04-13
    • 2011-03-12
    • 2012-11-17
    • 1970-01-01
    • 2014-11-05
    相关资源
    最近更新 更多