У меня есть файл с форматом, подобным
{1:[...]}{2:[X:11][Y:78][]...}{3:[...]}{4:[...]}{5:
[]
[]
...
[]}$
{1:[...]}{2:[X:43][Y:13][]...}{3:[...]}{4:[...]}{5:
[]
[]
...
[]}$
...
Многоточие указывает на множество повторяющихся структур или повторяющихся строк.
Таким образом, файл состоит из сегментов одинакового формата, разделенных вертикальной чертой.
Каков оптимальный способ извлечь только значения X для каждого сегмента? поэтому мы избегаем загрузки всего файла в память. Оптимален в пространстве и времени. Вероятно, это означает, что нужно избегать загрузки всего файла в память. Возможно, мы могли бы прочитать каждую строку и регулярное выражение, чтобы найти соответствие {2:[X:nn][ и извлечь nn, но это небольшая часть строк.
Но, возможно, есть лучший способ?
Вы можете прочитать каждый символ, сохранив элемент иерархии, в котором вы сейчас находитесь, и извлечение только нужных значений. Преимущество этого заключается в том, что не имеет значения длина каждой строки, и это скажет вам, недействительны ли ваши данные.
@jdweng File.ReadLines возвращает Enumerable и будет иметь тот же эффект, что и StreamReader
Если это большие файлы и скорость вызывает беспокойство, я бы просто проанализировал это с помощью fixed(char* p = yourLine), он будет выполнять регулярное выражение. однако, если скорость не имеет значения, регулярное выражение будет более кратким и управляемым.





Есть много подходов к этому,
Дано
var lines = File.ReadLines(@"D:\Test.txt");
Примечание : File.ReadLines возвращает Enumerbale, поэтому он будет лениво загружать каждую строку
Опция 1 : регулярное выражение с использованием Положительный взгляд назад и шаблона (?<=2:\[X:)\d+
foreach (var line in lines)
{
var match = Regex.Match(line,@"(?<=2:\[X:)\d+");
if (match.Success)
Console.WriteLine(match.Value);
}
Вариант 2 : Просто string.Split
foreach (var line in lines)
{
var results = line.Split(new[] { "2:[X:", "][Y:" }, StringSplitOptions.RemoveEmptyEntries);
if (results.Length>1)
Console.WriteLine(results[1]);
}
Вариант 3 : «Возможно» более эффективный подход с использованием указателиfixed и unsafe
public static unsafe (bool found, int value) ParseLine(string line)
{
const string prefix = "2:[X:";
fixed (char* pLine = line,pPrefix = prefix)
{
var pLen = line.Length + pLine;
var found = false;
var result = 0;
var i = 0;
for (char* p = pLine ,pP = pPrefix; p < pLen; p++)
{
if (!found )
{
if ( *p == *(pP+i)) i++;
if ( i ==prefix.Length) found = true;
continue;
}
if (*p < '0' || *p > '9')
break;
result = result * 10 + *p - '0';
}
return (found, result);
}
}
...
var results = File.ReadLines(@"D:\Test.txt")
.Select(ParseLine)
.Where(result => result.found)
.Select(result => result.value);
foreach (var result in results)
Console.WriteLine(result);
Примечание : речь идет не об избиении регулярных выражений, а о разных подходах.
Я не проверял это, однако я подозреваю, что указатели будет самым быстрым, split будет следующим, а регулярное выражение, возможно, будет самым медленным (даже при использовании скомпилированного), однако это самый читаемый и поддерживаемый, а также надежный подход ( поэтому и ставлю первым)
+----------+------------+-----------+-----------+
| Method | Mean | Error | StdDev |
+----------+------------+-----------+-----------+
| RegEx | 3,358.3 us | 65.169 us | 66.923 us |
| Split | 1,980.9 us | 38.440 us | 48.614 us |
| Pointers | 287.4 us | 4.396 us | 4.112 us |
+----------+------------+-----------+-----------+
Тестовый код
public class Test
{
private Regex _regex;
private string[] data;
[GlobalSetup]
public void Setup()
{
_regex = new Regex(@"(?<=2:\[X:)\d+", RegexOptions.Compiled);
data = File.ReadLines(@"D:\Test3.txt")
.ToArray();
}
[Benchmark]
public List<int> RegEx()
{
return data.Select(line => _regex.Match(line))
.Where(x => x.Success)
.Select(match => int.Parse(match.Value))
.ToList();
}
[Benchmark]
public List<int> Split()
{
return data.Select(line => line.Split(new[] { "2:[X:", "][Y:" }, StringSplitOptions.RemoveEmptyEntries))
.Where(results => results.Length > 1)
.Select(results => int.Parse(results[1]))
.ToList();
}
[Benchmark]
public List<int> Pointers()
{
return data.Select(ParseLine)
.Where(result => result.found)
.Select(result => result.value)
.ToList();
}
public static unsafe (bool found, int value) ParseLine(string line)
{
const string prefix = "2:[X:";
fixed (char* pLine = line,pPrefix = prefix)
{
var pLen = line.Length + pLine;
var found = false;
var result = 0;
var i = 0;
for (char* p = pLine ,pP = pPrefix; p < pLen; p++)
{
if (!found )
{
if ( *p == *(pP+i)) i++;
if ( i ==prefix.Length) found = true;
continue;
}
if (*p < '0' || *p > '9')
break;
result = result * 10 + *p - '0';
}
return (found, result);
}
}
}
Гораздо эффективнее использовать if (line.StartsWith("{")) перед использованием Regex.
@jdweng да, хорошее замечание, то же самое со всеми ними на самом деле
x: может появляться в других типах блоков, кроме 2: , однако меня интересует только X: внутри блока 2:.
Я бы читал файл с помощью StreamReader по одной строке, чтобы не помещать весь файл в память. Похоже, вам просто нужно разобрать строки, начинающиеся с фигурной скобки. Затем извлеките значения X из строки, используя Regex.