Есть ли ленивый `String.Split` в C#
Все string.Split
методы, кажется, возвращают массив строк (string[]
).
Мне интересно, есть ли ленивый вариант, который возвращает IEnumerable<string>
такой, что один для больших струн (или бесконечной длины IEnumerable<char>
), когда кого-то интересуют только первые подпоследовательности, он экономит вычислительные усилия, а также память. Это также может быть полезно, если строка создается устройством / программой (сеть, терминал, каналы), и, таким образом, нет необходимости в полной доступности всей строки. Такой, что уже можно обработать первые случаи.
Есть ли такой метод в.NET Framework?
7 ответов
Вы можете легко написать один:
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();
}
Нет такой вещи встроенной. Regex.Matches
лениво, если я правильно интерпретирую декомпилированный код. Может быть, вы можете использовать это.
Или вы просто пишете свою собственную функцию разделения.
На самом деле, вы могли бы изображение наиболее string
функции, обобщенные на произвольные последовательности. Часто даже последовательности T
, не просто char
, BCL не подчеркивает, что при обобщении все. Здесь нет Enumerable.Subsequence
например.
Ничего встроенного, но не стесняйтесь разорвать мой метод 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);
}
}
Я написал этот вариант, который поддерживает также SplitOptions и count. Он ведет себя так же, как строка. Разделить во всех тестовых случаях, которые я пытался. Оператор nameof является отдельным C# 6 и может быть заменен на "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);
}
}
Насколько я знаю, встроенного метода для этого не существует. Но это не значит, что вы не можете написать. Вот пример, чтобы дать вам идею:
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()); }
}
Конечно, это не обрабатывает все случаи и может быть улучшено.
Ленивый раскол без создания временной строки.
Кусок строки, скопированный с помощью системного колла mscorlib String.SubString.
public static IEnumerable<string> LazySplit(this string source, StringSplitOptions stringSplitOptions, params string[] separators)
{
var sourceLen = source.Length;
bool IsSeparator(int index, string separator)
{
var separatorLen = separator.Length;
if (sourceLen < index + separatorLen)
{
return false;
}
for (var i = 0; i < separatorLen; i++)
{
if (source[index + i] != separator[i])
{
return false;
}
}
return true;
}
var indexOfStartChunk = 0;
for (var i = 0; i < source.Length; i++)
{
foreach (var separator in separators)
{
if (IsSeparator(i, separator))
{
if (indexOfStartChunk == i && stringSplitOptions != StringSplitOptions.RemoveEmptyEntries)
{
yield return string.Empty;
}
else
{
yield return source.Substring(indexOfStartChunk, i - indexOfStartChunk);
}
i += separator.Length;
indexOfStartChunk = i--;
break;
}
}
}
if (indexOfStartChunk != 0)
{
yield return source.Substring(indexOfStartChunk, sourceLen - indexOfStartChunk);
}
}
Я хотел функциональность 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()
,