Разбить строку по нескольким разделителям без создания новых объектов - PullRequest
0 голосов
/ 04 марта 2020

Как бы я взял это и превратил в метод, который исключает повторение (то есть, не повторяю себя), но также не делает нового распределения в боксе / объекте - или, по крайней мере, настолько мало, насколько это возможно.

private static IEnumerable<string> SeparateLineIntoMultipleDefinitions(string line) {
    string[] splitEntries;
    splitEntries = (from str in line.Split(new[] {", "}, StringSplitOptions.RemoveEmptyEntries)
                    where str.Contains('=')
                    select str).ToArray();
    if (splitEntries.Length > 2) return splitEntries;
    splitEntries = (from str in line.Split(',')
                    where str.Contains('=')
                    select str).ToArray();
    if (splitEntries.Length > 2) return splitEntries;
    splitEntries = (from str in line.Split(' ')
                    where str.Contains('=')
                    select str).ToArray();
    if (splitEntries.Length > 2) return splitEntries;
    return Enumerable.Empty<string>();
}

Первоначально я пытался создать такой метод:

IEnumerable<string> SplitEntries(object splitter) {
    return splitter switch {
        string[] strArray => (from str in line.Split(strArray, StringSplitOptions.RemoveEmptyEntries)
                  where str.Contains('=')
                  select str),
        string s => (from str in line.Split(new[] {s}, StringSplitOptions.RemoveEmptyEntries)
                  where str.Contains('=')
                  select str),
        char charSplitter => (from str in line.Split(charSplitter)
                  where str.Contains('=')
                  select str),
        _ => Enumerable.Empty<string>()
    };
}

Но, увы, вызов этого с помощью char помещает символ в объект.

Для этого конкретного сценария я хочу, чтобы он попытался проанализировать ", ", затем просто ',', затем просто ' '. Принимая во внимание, что если бы я просто позвонил

line.Split(',', ' ')

, я думаю, что он попытался бы разбить что-то вроде this=that,there x=y b=c на this=that, there, x=y, b=c. Но я не хочу эту дополнительную запись there.

Я бы использовал регулярное выражение, но я полагаю, что его использование не обеспечило бы более оптимизированное для памяти решение (возможная ложная предпосылка; я был бы рад быть доказанным неправильно). Несмотря на то, что может считаться чрезмерным проектированием, мне любопытно, каким будет решение, потому что у меня есть ощущение, что я упускаю что-то очень простое.

Ответы [ 3 ]

3 голосов
/ 04 марта 2020

Альтернативный способ разделения с помощью ReadOnlySpan<char>, без использования Regex, string.Split() и System.Linq:


Код (UsingSpan):

public static IEnumerable<string> SeparateLineIntoMultipleDefinitions(ReadOnlySpan<char> line)
{
    List<string> definitions = new List<string>();

    bool captureIsStarted = false;

    int equalSignCount = 0;
    int lastEqualSignPosition = 0;
    int captureStart = 0;
    int captureEnd = 0;

    for (int i = 0; i < line.Length; i++)
    {
        char c = line[i];

        if (c != ',' && !char.IsWhiteSpace(c))
        {
            if (captureIsStarted)
            {
                captureEnd = i;
            }

            else
            {
                captureStart = i;
                captureIsStarted = true;
            }

            if (c == '=')
            {
                equalSignCount++;
                lastEqualSignPosition = i;
            }
        }
        else
        {
            if (equalSignCount == 1 && lastEqualSignPosition > captureStart && lastEqualSignPosition < captureEnd)
            {
                definitions.Add(line[captureStart..(captureEnd + 1)].ToString());
            }

            equalSignCount = 0;
            captureIsStarted = false;
        }
    }

    if (captureIsStarted && equalSignCount == 1 && lastEqualSignPosition > captureStart && lastEqualSignPosition < captureEnd)
    {
        definitions.Add(line[captureStart..(captureEnd + 1)].ToString());
    }  

    return definitions;
}

Код (UsingSpan_ZeroAllocation):

public static (int, (int, int)[]) SeparateLineIntoMultipleDefinitions_ZeroAllocation(ReadOnlySpan<char> line)
{
    int count = 0;
    (int, int)[] ranges = ArrayPool<(int, int)>.Shared.Rent(line.Length);

    bool captureIsStarted = false;

    int equalSignCount = 0;
    int lastEqualSignPosition = 0;
    int captureStart = 0;
    int captureEnd = 0;

    for (int i = 0; i < line.Length; i++)
    {
        char c = line[i];

        if (c != ',' && !char.IsWhiteSpace(c))
        {
            if (captureIsStarted)
            {
                captureEnd = i;
            }

            else
            {
                captureStart = i;
                captureIsStarted = true;
            }

            if (c == '=')
            {
                equalSignCount++;
                lastEqualSignPosition = i;
            }
        }
        else
        {
            if (equalSignCount == 1 && lastEqualSignPosition > captureStart && lastEqualSignPosition < captureEnd)
            {
                ranges[count++] = (captureStart, captureEnd + 1);
            }

            equalSignCount = 0;
            captureIsStarted = false;
        }
    }

    if (captureIsStarted && equalSignCount == 1 && lastEqualSignPosition > captureStart && lastEqualSignPosition < captureEnd)
    {
        ranges[count++] = (captureStart, captureEnd + 1);
    }

    return (count, ranges);
}

Пример использования:

var line = "this=that,there x=y b=c";

var (count, ranges) = SeparateLineIntoMultipleDefinitions_ZeroAllocation(line);

for (int i = 0; i < count; i++)
{
    var (offset, length) = ranges[i];

    Console.WriteLine(line[offset..length]);
}

ArrayPool<(int, int)>.Shared.Return(ranges);

Тест:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.17763.1039 (1809/October2018Update/Redstone5)
Intel Xeon CPU E5-2696 v4 2.20GHz, 2 CPU, 88 logical and 88 physical cores
.NET Core SDK=3.1.101
  [Host]     : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
  DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT


|                   Method |     Mean |   Error |  StdDev |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------------------- |---------:|--------:|--------:|-------:|------:|------:|----------:|
| UsingSpan_ZeroAllocation | 139.9 ns | 0.86 ns | 0.76 ns |      - |     - |     - |         - |
|                UsingSpan | 176.3 ns | 1.66 ns | 1.47 ns | 0.0067 |     - |     - |     192 B |
|               UsingRegEx | 218.2 ns | 2.62 ns | 2.45 ns | 0.0088 |     - |     - |     256 B |
|                UsingLinq | 339.0 ns | 3.86 ns | 3.42 ns | 0.0100 |     - |     - |     288 B |
|            UsingOPMethod | 853.0 ns | 8.80 ns | 8.23 ns | 0.0210 |     - |     - |     624 B |
2 голосов
/ 04 марта 2020

Просто используйте перегрузку Split(String[], StringSplitOptions):

var line = "this=that,there x=y b=c";
var spl = new[] { ", ", ",", " " };
var result = (from str in line.Split(spl, StringSplitOptions.RemoveEmptyEntries)
                  where str.Contains('=')
                  select str);

Выход:

this=that 
x=y 
b=c 
1 голос
/ 04 марта 2020

Можно использовать скомпилированную версию Regex.

Regex.Matches(strings,"[^=, ]*=[^=, ]*",RegexOptions.Compiled)
     .Cast<Match>().Select(x=>x.Value)

Вот результаты тестов Regex против OP Method.

enter image description here

...