从定界字符串中仅提取一个元素的有效方法

时间:2019-01-18 21:28:40

标签: c# string

注意:我确实在这里问过相同的question,但由于有人将其标记为重复,尽管它具有一些巧妙,整洁的解决方案,所以我不得不创建这个多余的(重复)问题,使面临类似疑问的其他人更容易获得帮助。根据其他堆栈溢出成员的建议添加了问题。

解析大型分隔字符串的有效方法是什么,这样我就可以只访问分隔集中的一个元素而不必存储所涉及的其他子字符串?

我特别不希望像使用Split()方法那样存储其余的元素值,因为所有这些信息都与手头的问题无关。另外,我想通过这样做来节省内存。

问题陈述:
给定确切的定界位置,就内存消耗和占用时间而言,我需要以最有效的方式提取该给定位置中包含的元素。

简单的示例字符串:“ 1,2,3,4,....,21, 22 ,23,24”
发货人:,
分隔位置:22
预期答案:23

另一个示例字符串: ““ 61d2e3f6-bcb7-4cd1-a81e-4f8f497f0da2; 0; 192.100.0.102:4362; 2014-02-14; 283; 0; 354; 23 ; 0 ;;;”“ 0x8D15A2913C934DE”“”; 2014年6月19日星期四22:58:10 GMT;“
定界符:;
定界位置:7
预期答案:23

5 个答案:

答案 0 :(得分:2)

String.Split的文档中有一些与该问题有关的有用注释,尽管我在发现之前写了以下内容。

一种方法是使用String.IndexOf方法找到一个定界符-您可以指定索引来开始搜索,因此可以跳过所有项目而不必检查每个字符。 (对每个角色的检查都是在幕后进行的,但是比自己检查要快一些。)

我通过将一个名为“ ExtensionMethods.cs”的新类添加到具有以下内容的解决方案中来构成扩展方法:

' of '.join([random.choice(cards), random.choice(suites)])

还有一个小程序对其进行测试:

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        /// <summary>
        /// Get the nth item from a delimited string.
        /// </summary>
        /// <param name="s">The string to retrieve a delimited item from.</param>
        /// <param name="delimiter">The character used as the item delimiter.</param>
        /// <param name="n">Zero-based index of item to return.</param>
        /// <returns>The nth item or an empty string.</returns>
        public static string Split(this string s, char delimiter, int n)
        {

            int pos = pos = s.IndexOf(delimiter);

            if (n == 0 || pos < 0)
            { return (pos >= 0) ? s.Substring(0, pos) : s; }

            int nDelims = 1;

            while (nDelims < n && pos >= 0)
            {
                pos = s.IndexOf(delimiter, pos + 1);
                nDelims++;
            }

            string result = "";

            if (pos >= 0)
            {
                int nextDelim = s.IndexOf(delimiter, pos + 1);
                result = (nextDelim < 0) ? s.Substring(pos + 1) : s.Substring(pos + 1, nextDelim - pos - 1);
            }

            return result;
        }

    }
}

示例输出:

  

直接选择的项目:1016
  拆分数组中的项目:1345

     p; A; B ;;;; C; D; E; F; G; H; I; J; K; L; M; N; O; P; Q; R; S; T; U; V; W; X; Y; ​​Z
  S


参考:How to: Implement and Call a Custom Extension Method (C# Programming Guide)

答案 1 :(得分:2)

尝试一下:

View

PS:使用的库为public static string MyExtension(this string s, char delimiter, int n) { var begin = n== 0 ? 0 : Westwind.Utilities.StringUtils.IndexOfNth(s, delimiter, n); if (begin == -1) return null; var end = s.IndexOf(delimiter, begin + (n==0?0:1)); if (end == -1 ) end = s.Length; //var end = Westwind.Utilities.StringUtils.IndexOfNth(s, delimiter, n + 1); var result = s.Substring(begin +1, end - begin -1 ); return result; }


基准代码:

Westwind.Utilities

结果:

void Main()
{

     string s = string.Join(";", Enumerable.Range(65, 26).Select(c => (char)c));
            s = s.Insert(3, ";;;");

            string o = "";

            Stopwatch sw = new Stopwatch();

            sw.Start();
            for (int i = 1; i <= 1000000; i++) {
                o = s.Split(';', 21);
            }
            sw.Stop();
            Console.WriteLine("Item directly selected: " + sw.ElapsedMilliseconds);


            sw.Restart();
            for (int i = 1; i <= 1000000; i++) {
                o = s.MyExtension(';', 21);
            }
            sw.Stop();
            Console.WriteLine("Item directly selected by MyExtension: " + sw.ElapsedMilliseconds);

            sw.Restart();
            for (int i = 1; i <= 1000000; i++) {
                o = s.Split(';')[21];
            }
            sw.Stop();
            Console.WriteLine("Item from split array:  " + sw.ElapsedMilliseconds + "\r\n");


            Console.WriteLine(s);
            Console.WriteLine(o);

}

public static class MyExtensions
{
    /// <summary>
    /// Get the nth item from a delimited string.
    /// </summary>
    /// <param name="s">The string to retrieve a delimited item from.</param>
    /// <param name="delimiter">The character used as the item delimiter.</param>
    /// <param name="n">Zero-based index of item to return.</param>
    /// <returns>The nth item or an empty string.</returns>
    public static string Split(this string s, char delimiter, int n)
    {

        int pos = pos = s.IndexOf(delimiter);

        if (n == 0 || pos < 0)
        { return (pos >= 0) ? s.Substring(0, pos) : s; }

        int nDelims = 1;

        while (nDelims < n && pos >= 0)
        {
            pos = s.IndexOf(delimiter, pos + 1);
            nDelims++;
        }

        string result = "";

        if (pos >= 0)
        {
            int nextDelim = s.IndexOf(delimiter, pos + 1);
            result = (nextDelim < 0) ? s.Substring(pos + 1) : s.Substring(pos + 1, nextDelim - pos - 1);
        }

        return result;
    }

    public static string MyExtension(this string s, char delimiter, int n)
    {
        var begin = n== 0 ? 0 : Westwind.Utilities.StringUtils.IndexOfNth(s, delimiter, n);
        if (begin == -1)
            return null;
        var end = s.IndexOf(delimiter, begin +  (n==0?0:1));
        if (end == -1 ) end = s.Length;
        //var end = Westwind.Utilities.StringUtils.IndexOfNth(s, delimiter, n + 1);
        var result = s.Substring(begin +1, end - begin -1 );

        return result;
    }

}

编辑:感谢@Kalten,我进一步增强了解决方案。在基准测试结果上看到了很大的差异。

答案 2 :(得分:1)

通过使用以下正则表达式:^([^;]*;){21}(.*?);,您不必生成空洞列表即可搜索所需位置,一旦到达该位置,就可以确定是否存在或是否存在该问题。不是。

说明

^ --> start of a line.

([^;]*;){Position - 1} --> notice that the symbol ; here is the delimiter, the expression will loop Pos - 1 times

(.*?) --> Non-Greedy .*

DEMO

有关C#正则表达式的更多信息:documentation

在下面的示例中,我确实实现了两个示例以向您展示其工作原理。

匹配方法documentation(基本上,它仅搜索模式的首次出现) RegexOptions.Singleline :将输入视为标志线。

C#代码

Console.WriteLine("First Delimiter : ");
        int Position = 22;
        char delimiter = ',';
        string pattern = @"^([^" + delimiter + "]*" + delimiter + "){" + (Position - 1) + @"}(.*?)" + delimiter;
        Regex regex = new Regex(pattern, RegexOptions.Singleline);
        // First Example
        string Data = @"AAV,zzz,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22ABC,23,24,24";
        Match Re = regex.Match(Data);
        if (Re.Groups.Count > 0)
            Console.WriteLine("\tMatch found : " + Re.Groups[2]);


        // Second Example
        Console.WriteLine("Second Delimiter : ");
        Position = 8;
        delimiter = ';';
        pattern = @"^([^" + delimiter + "]*" + delimiter + "){" + (Position - 1) + @"}(.*?)" + delimiter;
        Data = @"61d2e3f6-bcb7-4cd1-a81e-4f8f497f0da2;0;192.100.0.102:4362;2014-02-14;283;0;354;23;0;;;""0x8D15A2913C934DE"";Thursday, 19-Jun-14 22:58:10 GMT;";
        regex = new Regex(pattern, RegexOptions.Singleline);
        Re = regex.Match(Data);
        if (Re.Groups.Count > 0)
            Console.WriteLine("\tMatch found : " + Re.Groups[2]);

输出:

  

第一个分隔符:

    Match found : 22ABC
     

第二个分隔符:

    Match found : 23

答案 3 :(得分:1)

如果您想确保代码只解析一次字符串,并且只解析所需内容,则可以编写自己遍历字符串的例程。

由于所有c#字符串都实现IEnumerable<char>,因此设计一种要求零字符串分配的方法非常简单:

static public IEnumerable<char> GetDelimitedField(this IEnumerable<char> source, char delimiter, int index)
{
    foreach (var c in source)
    {
        if (c == delimiter) 
        {
            if (--index < 0) yield break;
        }
        else
        {
            if (index == 0) yield return c;
        }
    }
}

这将以IEnumerable<char>的形式返回结果,但是转换为字符串很便宜。无论如何,这将是一个短得多的字符串。

static public string GetDelimitedString(this string source, char delimiter, int index)
{
    var result = source.GetDelimitedField(delimiter, index);
    return new string(result.ToArray());
}

您可以这样称呼它:

var input ="Zero,One,Two,Three,Four,Five,Six";
var output = input.GetDelimitedString(',',5);
Console.WriteLine(output);

输出:

Five

Example on DotNetFiddle

答案 4 :(得分:-1)

“答案”为时已晚,但是此代码为我提供了大约0.75秒的运行时间,两个字符串都处理了1,000,000次。这次的区别是现在我不是在编组对象,而是在使用指针。

这次,我返回一个新字符串(String.Substring)。

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        string testString1 = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24";
        string testString2 = "61d2e3f6-bcb7-4cd1-a81e-4f8f497f0da2;0;192.100.0.102:4362;2014-02-14;283;0;354;23;0;;;\"0x8D15A2913C934DE\";Thursday, 19-Jun-14 22:58:10 GMT;";

        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 1; i < 1000000; i++)
        {
            Delimit(testString1, ',', 22);
            Delimit(testString2, ';', 6);
        }
        sw.Stop();
        Console.WriteLine($"==>{sw.ElapsedMilliseconds}");
        Console.ReadLine();
    }

    static string Delimit(string stringUnderTest, char delimiter, int skipCount)
    {
        const int SIZE_OF_UNICHAR = 2;

        int i = 0;
        int index = 0;
        char c = Char.MinValue;

        GCHandle handle = GCHandle.Alloc(stringUnderTest, GCHandleType.Pinned);
        try
        {
            IntPtr ptr = handle.AddrOfPinnedObject();
            for (i = 0; i < skipCount; i++)
                while ((char)Marshal.ReadByte(ptr, index += SIZE_OF_UNICHAR) != delimiter) ;
            i = index;
            while ((c = (char)Marshal.ReadByte(ptr, i += SIZE_OF_UNICHAR)) != delimiter) ;
        }
        finally
        {
            if (handle.IsAllocated)
                handle.Free();
        }

        return stringUnderTest.Substring((index + SIZE_OF_UNICHAR) >> 1, (i - index - SIZE_OF_UNICHAR) >> 1);
    }
}