(C#)提高自定义getBetweenAll的速度

时间:2016-01-30 17:12:59

标签: c# string performance indexof

我在c#中编写了一个自定义扩展方法,它是对extensionmethod string [] getBetweenAll(string source,string startstring,string endstring)的改进;

最初这个扩展方法找到了两个字符串之间的所有子串,例如:

string source = "<1><2><3><4>";
source.getBetweenAll("<", ">");
//output: string[] {"1", "2", "3", "4"}

但如果你有另一次出现&lt;在一开始它只会介于它和整个字符串之间

string source = "<<1><2><3><4>";
source.getBetweenAll("<", ">");
//output: string[] {"<1><2><3><4"}

所以我重新编写它以更精确地从“&gt;”向后搜索找到第一次出现的“&lt;”

现在我开始工作,但问题是方式太慢因为搜索方法会在每次出现时跳过整个字符串的每个字符。你知道我怎么能提高这个功能的速度吗?或者是不可能的?

到目前为止,这是整个代码http://pastebin.com/JEZmyfSG 我添加了代码需要提高速度的注释

public static List<int> IndexOfAll(this string main, string searchString)
{
    List<int> ret = new List<int>();
    int len = searchString.Length;
    int start = -len;
    while (true)
    {
        start = main.IndexOf(searchString, start + len);
        if (start == -1)
        {
            break;
        }
        else
        {
            ret.Add(start);
        }
    }
    return ret;
}

public static string[] getBetweenAll(this string main, string strstart, string strend, bool preserve = false)
{
    List<string> results = new List<string>();
    List<int> ends = main.IndexOfAll(strend);
    foreach (int end in ends)
    {
        int start = main.previousIndexOf(strstart, end);  //This is where it has to search the whole source string every time
        results.Add(main.Substring(start, end - start) + (preserve ? strend : string.Empty));
    }
    return results.ToArray();
}

//This is the slow function (depends on main.Length)
public static int previousIndexOf(this string main, string find, int offset)
{
    int wtf = main.Length ;
    int x = main.LastIndexOf(find, wtf);
    while (x > offset)
    {
        x = main.LastIndexOf(find, wtf);
        wtf -= 1;
    }
    return x;
}

我想另一种方法是做PreviousIndexOf(string,int searchfrom);会提高速度..比如IndexOf()除了向后和提供的起始偏移

4 个答案:

答案 0 :(得分:1)

作为原始的GetBetweenAll,我们可以使用正则表达式。为了只匹配封闭字符串的最短“内部”外观,我们必须在起始字符串上使用负前瞻,并为内容使用非贪婪的量词。

public static string[] getBetweenAll(this string main, 
    string strstart, string strend, bool preserve = false)
{
    List<string> results = new List<string>();

    string regularExpressionString = string.Format("{0}(((?!{0}).)+?){1}", 
        Regex.Escape(strstart), Regex.Escape(strend));
    Regex regularExpression = new Regex(regularExpressionString, RegexOptions.IgnoreCase);

    var matches = regularExpression.Matches(main);

    foreach (Match match in matches)
    {
        if (preserve)
        {
            results.Add(match.Value);
        }
        else
        {
            results.Add(match.Groups[1].Value);
        }
    }

    return results.ToArray();
}

答案 1 :(得分:0)

我写了一个比你快四倍的简单方法(但到目前为止没有preserve参数):

public static string[] getBetweenAll2(this string main, string strstart, string strend, bool preserve = false)
{
    List<string> results = new List<string>();

    int lenStart = strstart.Length;

    int indexStart = 0;
    while (true)
    {
        indexStart = main.IndexOf(strstart, indexStart);
        if (indexStart < 0)
            break;

        int indexEnd = main.IndexOf(strend, indexStart);

        if (indexEnd < 0)
            break;

        results.Add(main.Substring(indexStart+ lenStart, indexEnd- indexStart- lenStart));
        indexStart = indexEnd;
    }
    return results.ToArray();
}

这会为您提供字符1

中的数字234<1><2><3><4>

这样做你想要的吗?

<强> [编辑]

查找嵌套内容:

public static string[] getBetweenAll2(this string main, string strstart, string strend, bool preserve = false)
{
    List<string> results = new List<string>();

    int lenStart = strstart.Length; 
    int lenEnd = strend.Length;

    int index = 0;

    Stack<int> starPos = new Stack<int>();

    while (true)
    {
        int indexStart = main.IndexOf(strstart, index);
        int indexEnd = main.IndexOf(strend, index);

        if (indexStart != -1 && indexStart < indexEnd)
        {
            index = indexStart + lenStart;
            starPos.Push(index);
        }
        else if (indexEnd != -1 && (indexEnd < indexStart || indexStart == -1))
        {
            if (starPos.Count == 1)
            {
                int startOfInterst = starPos.Pop();
                results.Add(main.Substring(startOfInterst, indexEnd - startOfInterst));
            } else if(starPos.Count>0)
            {
                starPos.Pop();
            }
            index = indexEnd + lenEnd;
        }
        else
        {
            break;
        }
    }
    return results.ToArray();
}

答案 2 :(得分:0)

使用堆栈做。只要看到打开令牌,就会开始向stack添加字符。一旦你看到关闭令牌 - 从你的堆栈弹出一切,它将是你感兴趣的角色。

现在,一旦您实现了基本案例,您可以使用递归来改进它。如果在关闭令牌之前看到另一个打开令牌 - 开始将字符收集到新堆栈,直到您看到关闭令牌。

这将为您提供O(N)的复杂性,因为您只需要传递一次所有内容。

如果你在开放令牌之前看到关闭令牌,你还需要处理这个案例,但是你的问题不清楚那个程序应该做什么。

答案 3 :(得分:0)

我发现这可以做我想要的,但另一种方式!执行PreviousIndexOf(字符串源,字符串标记,int偏移)的函数仍然会非常感谢其他东西!

public static List<string> GetBetweenAll(this string main, string start, string finish, bool preserve = false,  int index = 0)
{
    List<string> matches = new List<string>();
    Match gbMatch = new Regex(Regex.Escape(start) + "(.+?)" + Regex.Escape(finish)).Match(main, index);
    while (gbMatch.Success)
    {
        matches.Add((preserve ? start : string.Empty) + gbMatch.Groups[1].Value + (preserve ? finish : string.Empty));
        gbMatch = gbMatch.NextMatch();
    }
    return matches;
}
public static string[] getBetweenAllBackwards(this string main, string strstart, string strend, bool preserve = false)
{
    List<string> all = Reverse(main).GetBetweenAll(Reverse(strend), Reverse(strstart), preserve);
    for (int i = 0; i < all.Count; i++)
    {
        all[i] = Reverse(all[i]);
    }
    return all.ToArray();
}
public static string Reverse(string s)
{
    char[] charArray = s.ToCharArray();
    Array.Reverse(charArray);
    return new string(charArray);
}