2015-01-27 14 views
6

Alle string.Split Methoden scheint ein Array von Zeichenfolgen (string[]) zurückgeben.Gibt es eine faule "String.Split" in C#

Ich frage mich, ob es eine faule Variante ist, die eine IEnumerable<string>, so dass man für große Strings zurückgibt (oder eine unendliche Länge IEnumerable<char>), wenn man nur in einem ersten Teilfolgen interessiert ist, spart man Rechenaufwand sowie Speicher . Es kann auch nützlich sein, wenn der String von einem Gerät/Programm (Netzwerk, Terminal, Pipes) konstruiert wird und die gesamten Strings somit nicht sofort vollständig verfügbar sind. So dass man bereits die ersten Vorkommen verarbeiten kann.

Gibt es solche Methode im .NET Framework?

+0

C# hat keine Standardbibliothek. Sie scheinen sich auf das .NET Framework zu beziehen, das nicht für C#, VB.NET oder eine andere bestimmte Sprache spezifisch ist. –

+0

@JohnSaunders: modifizierte ... –

Antwort

4

Es ist nicht so etwas eingebaut. Regex.Matches ist faul, wenn ich den dekompilierten Code richtig interpretiere. Vielleicht kannst du das nutzen.

Oder Sie schreiben einfach Ihre eigene Split-Funktion.

Eigentlich könnte man die meisten string Funktionen auf beliebige Sequenzen verallgemeinern. Oft sind sogar Sequenzen von T, nicht nur char. Die BCL betont das bei der Verallgemeinerung nicht alle. Zum Beispiel gibt es keine Enumerable.Subsequence.

+3

Shame 'Regex.Split' ist eindeutig nicht faul. – Rawling

+0

Ich wünschte, dass .NET ein "unveränderliches Array des T" -Typs enthalten hätte; 'String' könnte dann einfach eine Abkürzung für" unveränderliches Array von Char "sein. Ich weiß, dass ich oft "unveränderliches Array von Byte" oder "unveränderliches Array von Int32" verwendet hätte, wenn sie existierten, und würde erwarten, dass Verallgemeinerung auch in vielen anderen Fällen nützlich wäre. – supercat

+0

@supercat: True, so behandelt Haskell Strings. Es ermöglicht die Verallgemeinerung einer Menge von String-Methoden zu Listen ... –

2

Nichts eingebaut, aber ich fühle mich frei, meine Tokenize Methode rippen:

/// <summary> 
/// Splits a string into tokens. 
/// </summary> 
/// <param name="s">The string to split.</param> 
/// <param name="isSeparator"> 
/// A function testing if a code point at a position 
/// in the input string is a separator. 
/// </param> 
/// <returns>A sequence of tokens.</returns> 
IEnumerable<string> Tokenize(string s, Func<string, int, bool> isSeparator = null) 
{ 
    if (isSeparator == null) isSeparator = (str, i) => !char.IsLetterOrDigit(str, i); 

    int startPos = -1; 

    for (int i = 0; i < s.Length; i += char.IsSurrogatePair(s, i) ? 2 : 1) 
    { 
     if (!isSeparator(s, i)) 
     { 
      if (startPos == -1) startPos = i; 
     } 
     else if (startPos != -1) 
     { 
      yield return s.Substring(startPos, i - startPos); 
      startPos = -1; 
     } 
    } 

    if (startPos != -1) 
    { 
     yield return s.Substring(startPos); 
    } 
} 
1

Es gibt keine integrierte Methode ist dies so weit zu tun, wie ich bin weiß. Aber es bedeutet nicht, dass du keinen schreiben kannst. Hier ist ein Beispiel, um Ihnen eine Idee zu geben:

public static IEnumerable<string> SplitLazy(this string str, params char[] separators) 
{ 
    List<char> temp = new List<char>(); 
    foreach (var c in str) 
    { 
     if (separators.Contains(c) && temp.Any()) 
     { 
      yield return new string(temp.ToArray()); 
      temp.Clear(); 
     } 
     else 
     { 
      temp.Add(c); 
     } 
    } 
    if(temp.Any()) { yield return new string(temp.ToArray()); } 
} 

Natürlich behandelt dies nicht alle Fälle und kann verbessert werden.

+2

Dies teilt nur Zeichen, nicht Zeichenfolgen. – Servy

4

könnte man leicht einen schreiben:

public static class StringExtensions 
{ 
    public static IEnumerable<string> Split(this string toSplit, params char[] splits) 
    { 
     if (string.IsNullOrEmpty(toSplit)) 
      yield break; 

     StringBuilder sb = new StringBuilder(); 

     foreach (var c in toSplit) 
     { 
      if (splits.Contains(c)) 
      { 
       yield return sb.ToString(); 
       sb.Clear(); 
      } 
      else 
      { 
       sb.Append(c); 
      } 
     } 

     if (sb.Length > 0) 
      yield return sb.ToString(); 
    } 
} 

Klar, habe ich es für die Parität mit string.split nicht getestet, aber ich glaube, es sollte nur etwa die gleiche Arbeit.

Wie Servy merkt, teilt dies nicht auf Strings. Das ist nicht so einfach und nicht so effizient, aber es ist im Grunde das gleiche Muster.

public static IEnumerable<string> Split(this string toSplit, string[] separators) 
{ 
    if (string.IsNullOrEmpty(toSplit)) 
     yield break; 

    StringBuilder sb = new StringBuilder(); 
    foreach (var c in toSplit) 
    { 
     var s = sb.ToString(); 
     var sep = separators.FirstOrDefault(i => s.Contains(i)); 
     if (sep != null) 
     { 
      yield return s.Replace(sep, string.Empty); 
      sb.Clear(); 
     } 
     else 
     { 
      sb.Append(c); 
     } 
    } 

    if (sb.Length > 0) 
     yield return sb.ToString(); 
} 
+0

Dies teilt nur Zeichen, nicht Zeichenfolgen. – Servy

1

Ich habe diese Variante geschrieben, die auch SplitOptions unterstützt und zählt. Es verhält sich wie string.Split in allen Testfällen, die ich versuchte. Der Name des Bedieners ist C# 6 und kann durch "count" ersetzt werden.

public static class StringExtensions 
{ 
    /// <summary> 
    /// Splits a string into substrings that are based on the characters in an array. 
    /// </summary> 
    /// <param name="value">The string to split.</param> 
    /// <param name="options"><see cref="StringSplitOptions.RemoveEmptyEntries"/> to omit empty array elements from the array returned; or <see cref="StringSplitOptions.None"/> to include empty array elements in the array returned.</param> 
    /// <param name="count">The maximum number of substrings to return.</param> 
    /// <param name="separator">A character array that delimits the substrings in this string, an empty array that contains no delimiters, or null. </param> 
    /// <returns></returns> 
    /// <remarks> 
    /// Delimiter characters are not included in the elements of the returned array. 
    /// If this instance does not contain any of the characters in separator the returned sequence consists of a single element that contains this instance. 
    /// If the separator parameter is null or contains no characters, white-space characters are assumed to be the delimiters. White-space characters are defined by the Unicode standard and return true if they are passed to the <see cref="Char.IsWhiteSpace"/> method. 
    /// </remarks> 
    public static IEnumerable<string> SplitLazy(this string value, int count = int.MaxValue, StringSplitOptions options = StringSplitOptions.None, params char[] separator) 
    { 
     if (count <= 0) 
     { 
      if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be less than zero."); 
      yield break; 
     } 

     Func<char, bool> predicate = char.IsWhiteSpace; 
     if (separator != null && separator.Length != 0) 
      predicate = (c) => separator.Contains(c); 

     if (string.IsNullOrEmpty(value) || count == 1 || !value.Any(predicate)) 
     { 
      yield return value; 
      yield break; 
     } 

     bool removeEmptyEntries = (options & StringSplitOptions.RemoveEmptyEntries) != 0; 
     int ct = 0; 
     var sb = new StringBuilder(); 
     for (int i = 0; i < value.Length; ++i) 
     { 
      char c = value[i]; 
      if (!predicate(c)) 
      { 
       sb.Append(c); 
      } 
      else 
      { 
       if (sb.Length != 0) 
       { 
        yield return sb.ToString(); 
        sb.Clear(); 
       } 
       else 
       { 
        if (removeEmptyEntries) 
         continue; 
        yield return string.Empty; 
       } 

       if (++ct >= count - 1) 
       { 
        if (removeEmptyEntries) 
         while (++i < value.Length && predicate(value[i])); 
        else 
         ++i; 
        if (i < value.Length - 1) 
        { 
         sb.Append(value, i, value.Length - i); 
         yield return sb.ToString(); 
        } 
        yield break; 
       } 
      } 
     } 

     if (sb.Length > 0) 
      yield return sb.ToString(); 
     else if (!removeEmptyEntries && predicate(value[value.Length - 1])) 
      yield return string.Empty; 
    } 

    public static IEnumerable<string> SplitLazy(this string value, params char[] separator) 
    { 
     return value.SplitLazy(int.MaxValue, StringSplitOptions.None, separator); 
    } 

    public static IEnumerable<string> SplitLazy(this string value, StringSplitOptions options, params char[] separator) 
    { 
     return value.SplitLazy(int.MaxValue, options, separator); 
    } 

    public static IEnumerable<string> SplitLazy(this string value, int count, params char[] separator) 
    { 
     return value.SplitLazy(count, StringSplitOptions.None, separator); 
    } 
} 
0

wollte ich die Funktionalität von Regex.Split, aber in einer lazily ausgewertet Form. Der folgende Code nur läuft durch alle Matches in der Eingabezeichenfolge und erzeugt die gleichen Ergebnisse wie Regex.Split:

public static IEnumerable<string> Split(string input, string pattern, RegexOptions options = RegexOptions.None) 
{ 
    // Always compile - we expect many executions 
    var regex = new Regex(pattern, options | RegexOptions.Compiled); 

    int currentSplitStart = 0; 
    var match = regex.Match(input); 

    while (match.Success) 
    { 
     yield return input.Substring(currentSplitStart, match.Index - currentSplitStart); 

     currentSplitStart = match.Index + match.Length; 
     match = match.NextMatch(); 
    } 

    yield return input.Substring(currentSplitStart); 
} 

Beachten Sie, dass dies mit dem Muster Parameter @"\s" geben Ihnen die gleichen Ergebnisse wie string.Split().