2015-01-27 2 views
6

Все методы string.Split, кажется, возвращают массив строк (string[]).Есть ли ленивый `String.Split` в C#

Я интересно, если есть ленивый вариант, который возвращает IEnumerable<string> такое, что один для больших строк (или бесконечную длину IEnumerable<char>), когда один заинтересован только в первом подпоследовательности, один экономит вычислительные затраты, а также памяти , Это также может быть полезно, если строка построена устройством/программой (сетью, терминалом, трубами), и поэтому все строки не обязательно должны быть полностью доступны. Это позволяет уже обрабатывать первые вхождения.

Есть ли такой способ в .NET framework?

+0

C# не имеет стандартной библиотеки. Вы, кажется, ссылаетесь на .NET Framework, который не является специфичным для C#, VB.NET или любого другого конкретного языка. –

+0

@JohnSaunders: modified ... –

ответ

4

Существует не такая вещь встроенная. Regex.Matches ленив, если я правильно интерпретирую декомпилированный код. Может быть, вы сможете это использовать.

Или вы просто пишете свою собственную функцию разделения.

На самом деле, вы можете изобразить большинство string функций, обобщенных на произвольные последовательности. Часто даже последовательности T, а не только char. BCL не подчеркивает, что при обобщении все. Например, нет Enumerable.Subsequence.

+3

Позор 'Regex.Split' явно не ленив. – Rawling

+0

Я хочу, чтобы .NET включил «неизменяемый массив T»; «String» может просто быть сокращенным для «неизменяемого массива символов». Я знаю, что много раз я использовал бы «неизменяемый массив байтов» или «неизменяемый массив Int32», если бы они существовали, и ожидал бы, что обобщение будет полезно и во многих других случаях. – supercat

+0

@supercat: true, вот как Haskell обрабатывает строки. Это позволяет обобщать множество строковых методов на списки ... –

2

Ничего встроенный, но не стесняйтесь копировать мой Tokenize метод:

/// <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

Там нет встроенного метода сделать это, насколько я знаю. Но это не значит, что вы не можете написать его. Вот образец, который даст вам представление:

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

Конечно, это не относится ко всем случаям и может быть улучшено.

+2

Это только разделение на символы, а не строки. – Servy

4

Вы могли бы легко написать один:

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

Очевидно, я не проверял паритета с String.Split, но я считаю, что это должно работать примерно то же самое.

Как отмечает Servy, это не разделение на строки. Это не так просто, но не так эффективно, но это в основном тот же шаблон.

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

Это только разбиение на символы, а не строки. – Servy

1

Я написал этот вариант, который поддерживает также SplitOptions и count. Он ведет себя так же, как string.Split во всех тестовых случаях, которые я пробовал. Имя оператора C# 6 sepcific и может быть заменено на «count».

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

Я хотел функциональность Regex.Split, но в лениво оцениваемой форме. Приведенный ниже код просто проходит через все Matches во входной строке, и производит такие же результаты, как 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); 
} 

Обратите внимание, что с помощью этого с помощью параметра шаблона @"\s" даст вам те же результаты, как string.Split().

Смежные вопросы