HINWEIS : Ich habe dasselbe gefragt Frage hier, aber da einige Leute es als Duplikat markiert haben, obwohl es einige listige, ordentliche Lösungen gab, musste ich diese zusätzliche (betrogene) Frage erstellen, um es einfacher zu machen für andere, die ähnliche Zweifel haben. Die Frage wurde basierend auf dem Vorschlag anderer Stapelüberlaufmitglieder hinzugefügt.

Was ist der effiziente Weg, um eine große begrenzte Zeichenfolge zu analysieren, sodass ich nur auf ein Element aus der begrenzten Menge zugreifen kann, ohne die anderen beteiligten Teilzeichenfolgen speichern zu müssen?

Ich bin insbesondere nicht daran interessiert, den Rest der Elementwerte wie bei Verwendung der Split () -Methode zu speichern, da all diese Informationen für das vorliegende Problem irrelevant sind. Außerdem möchte ich dabei Speicherplatz sparen.

Problemstellung:
Angesichts der genau begrenzten Position muss ich das in dieser Position enthaltene Element auf die effizienteste Weise in Bezug auf den verbrauchten Speicher und die benötigte Zeit extrahieren.

Einfache Beispielzeichenfolge: "1,2,3,4, ...., 21, 22 , 23,24"
Trennzeichen:
Begrenzte Position: 22
Antwort erwartet: 23

Ein weiterer Beispielstring: 61d2e3f6-bcb7-4cd1-a81e-4f8f497f0da2; 0; 192.100.0.102: 4362; 2014-02-14; 283; 0; 354; 23 ; 0 ;;; 0x8D15A2913C934DE; Donnerstag, 19. Juni 14, 22:58:10 GMT; "
Trennzeichen :;
Begrenzte Position: 7
Erwartete Antwort: 23

2
re3el 19 Jän. 2019 im 00:28

5 Antworten

Beste Antwort

In der Dokumentation zu String.Split, obwohl ich Folgendes geschrieben habe, bevor ich das entdeckt habe.

Eine Möglichkeit besteht darin, ein Trennzeichen mit String.IndexOf -Methode - Sie können den Index angeben, von dem aus die Suche gestartet werden soll, sodass Sie die Elemente überspringen können, ohne jedes Zeichen untersuchen zu müssen. (Die Untersuchung jedes Charakters findet hinter den Kulissen statt, ist aber etwas schneller als selbst.)

Ich habe eine Erweiterungsmethode erstellt, indem ich der Lösung mit dem folgenden Inhalt eine neue Klasse mit dem Namen "ExtensionMethods.cs" hinzugefügt habe:

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;
        }

    }
}

Und ein kleines Programm zum Testen:

using System;
using System.Diagnostics;
using System.Linq;
using ExtensionMethods;

namespace ConsoleApp1
{

    class Program
    {

        static void Main(string[] args)
        {
            // test data...
            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.Split(';')[21];
            }
            sw.Stop();
            Console.WriteLine("Item from split array:  " + sw.ElapsedMilliseconds + "\r\n");


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

            Console.ReadLine();

        }
    }
}

Beispielausgabe:

Artikel direkt ausgewählt: 1016
Gegenstand aus geteiltem Array: 1345

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.


Referenz: Gewusst wie: Implementieren und Aufrufen einer benutzerdefinierten Erweiterungsmethode (C # -Programmierhandbuch)

2
Andrew Morton 18 Jän. 2019 im 21:35

Versuche dies:

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;
}

PS: Die verwendete Bibliothek ist Westwind.Utilities


Benchmark-Code:

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;
    }

}

Ergebnisse:

Item directly selected: 277
Item directly selected by MyExtension: 114
Item from split array:  1297

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

Bearbeiten: Dank @Kalten habe ich die Lösung weiter verbessert. Bei den Benchmark-Ergebnissen wurden erhebliche Unterschiede festgestellt.

2
Derviş Kayımbaşıoğlu 18 Jän. 2019 im 22:46

Wenn Sie den folgenden Regex verwenden: ^([^;]*;){21}(.*?);, müssen Sie damit keine Lochteilungsliste erstellen, um nach Ihrer gewünschten Position zu suchen. Sobald Sie diese erreicht haben, wird es eine Frage sein, ob sie existiert oder nicht.

Erklärung :

^ --> start of a line.

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

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

DEMO

Weitere Informationen zu regulären Ausdrücken in C #: Dokumentation

Im folgenden Beispiel habe ich die beiden Beispiele implementiert, um Ihnen zu zeigen, wie es funktioniert.

Übereinstimmungsmethode : Dokumentation (Grundsätzlich wird nur nach dem ersten Auftreten des Musters gesucht) RegexOptions.Singleline : Behandelt die Eingabe als Signallinie.

C # -Code

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]);

Ausgabe :

Erster Begrenzer:

    Match found : 22ABC

Zweiter Begrenzer:

    Match found : 23
1
lagripe 18 Jän. 2019 im 23:53

Wenn Sie sicher sein möchten, dass der Code die Zeichenfolge in nur einem Durchgang analysiert und nur das, was benötigt wird, analysiert, können Sie die Routine, die die Zeichenfolge durchläuft, selbst schreiben.

Da alle c # -Strings IEnumerable<char> implementieren, ist es ziemlich einfach, eine Methode zu entwickeln, die keine String-Zuweisungen erfordert:

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;
        }
    }
}

Dies gibt das Ergebnis als IEnumerable<char> zurück, aber es ist billig, es in eine Zeichenfolge zu konvertieren. Zu diesem Zeitpunkt wird es sowieso eine viel kürzere Saite sein.

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

Und so kann man es nennen:

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

Ausgabe:

Five

Beispiel auf DotNetFiddle

1
John Wu 19 Jän. 2019 im 09:42

Für "Antwort" zu spät, aber dieser Code gibt mir eine Laufzeit von ca. 0,75 Sekunden, wobei beide Zeichenfolgen 1.000.000 Mal verarbeitet werden. Diesmal besteht der Unterschied darin, dass ich jetzt kein Objekt marshalle, sondern Zeiger verwende.

Und dieses Mal gebe ich einen einzelnen neuen String zurück (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);
    }
}
-1
Clay Ver Valen 19 Jän. 2019 im 02:02