У меня есть исходный файл, содержащий такие строки, как:

34    964:0.049759 1123:0.0031 2507:0.015979 
32,48 524:0.061167 833:0.030133 1123:0.002549
34,52 534:0.07349 698:0.141667 1123:0.004403 
106   389:0.013396 417:0.016276 534:0.023859

Первая часть строки - это номер класса. Линия может иметь несколько классов.

Для каждого класса я создаю новый файл.

Например, для класса 34 результирующий файл будет:

+1 964:0.049759 1123:0.0031 2507:0.015979 
-1 524:0.061167 833:0.030133 1123:0.002549
+1 534:0.07349 698:0.141667 1123:0.004403 
-1 389:0.013396 417:0.016276 534:0.023859

Для класса 106 результирующий файл будет:

-1 964:0.049759 1123:0.0031 2507:0.015979 
-1 524:0.061167 833:0.030133 1123:0.002549
-1 534:0.07349 698:0.141667 1123:0.004403 
+1 389:0.013396 417:0.016276 534:0.023859

Проблема в том, что у меня есть 13 файлов для записи на 200 классов. Я уже запускал менее оптимизированную версию своего кода, и это заняло несколько часов. С моим приведенным ниже кодом для создания 2600 файлов требуется 1 час.

Есть ли способ выполнить такую ​​замену быстрее? Является ли регулярное выражение приемлемым вариантом?

Ниже представлена ​​моя реализация (работает на LINQPAD с

Выход:

Whole process took 00:00:00.1175102

РЕДАКТИРОВАТЬ: Я также запустил профилировщик, и похоже, что метод разделения - самая горячая точка.

enter image description here

РЕДАКТИРОВАТЬ 2: Простой пример:

2,1 1:0.8 2:0.2
3   1:0.4 3:0.6
12  1:0.02 4:0.88 5:0.1

Ожидаемый результат для класса 2:

+1 1:0.8 2:0.2
-1 1:0.4 3:0.6
-1 1:0.02 4:0.88 5:0.1

Ожидаемый результат для класса 3:

-1 1:0.8 2:0.2
+1 1:0.4 3:0.6
-1 1:0.02 4:0.88 5:0.1

Ожидаемый результат для класса 4:

-1 1:0.8 2:0.2
-1 1:0.4 3:0.6
-1 1:0.02 4:0.88 5:0.1
4
alexandrekow 8 Июл 2014 в 00:28
4
"код ниже занимает 1 час." но Whole process took 00:00:00.1175102? Я в замешательстве.
 – 
tnw
8 Июл 2014 в 00:31
2
Я думаю, что он работает на большем наборе, чем 4 строки :)
 – 
Troels Larsen
8 Июл 2014 в 00:33
1
Попробуйте измерить, сколько из этого времени приходится на ввод-вывод, а сколько — на фактическое время обработки, сгенерировав файл в памяти и записав его только после завершения цикла.
 – 
Troels Larsen
8 Июл 2014 в 00:37
2
Я подумал, что лучше будет писать на диск последовательно. Параллельная запись 200 файлов сводила мой диск с ума.
 – 
alexandrekow
8 Июл 2014 в 00:38
1
Это все, что у меня есть. :) С добавленным комментарием, что я бы использовал Path.Combine() для создания имен каталогов и файлов.
 – 
itsme86
8 Июл 2014 в 00:57

2 ответа

Лучший ответ

Я удалил самые горячие пути из вашего кода, удалив разделение и используя больший буфер в FileStream.

Вместо Split я теперь вызываю ToCharArray а затем проанализируйте первые символы в первом пробеле, и пока я нахожусь на нем, выполняется сопоставление с classValue на основе char. Логическое значение found указывает точное совпадение с чем-либо до первого пробела. В остальном обработка такая же.

var fsw = new FileStream(classFilePath,
    FileMode.Create,
    FileAccess.Write,
    FileShare.None,
    64*1024*1024); // use a large buffer
using (var file = new StreamWriter(fsw)) // use the filestream
{
    foreach(var line in fileLines) // for( int i = 0;i < fileLines.Length;i++)
    {
        char[] chars = line.ToCharArray();
        int matched = 0;
        int parsePos = -1;
        bool takeClass = true;
        bool found = false;
        bool space = false;
        // parse until space
        while (parsePos<chars.Length && !space )
        {
            parsePos++;
            space = chars[parsePos] == ' '; // end
            // tokens
            if (chars[parsePos] == ' ' ||
                chars[parsePos] == ',')
            {
                if (takeClass 
                    && matched == classValue.Length)
                {
                    found = true;
                    takeClass = false;
                }
                else
                {
                    // reset matching
                    takeClass = true;
                    matched = 0;
                }
            }
            else
            {
                if (takeClass 
                    &&  matched < classValue.Length 
                    && chars[parsePos] == classValue[matched])
                {
                    matched++; // on the next iteration, match next
                }
                else
                {
                    takeClass = false; // no match!
                }    
            }
        }

        chars[parsePos - 1] = '1'; // replace 1 in front of space
        var correction = 1;
        if (parsePos > 1)
        {
            // is classValue before the comma (or before space)
            if (found)
            {
                chars[parsePos - 2] = '+';
            }
            else
            {
                chars[parsePos - 2] = '-';
            }
            correction++;
        }
        else
        {
            // is classValue before the comma (or before space)
            if (found)
            {
                // not enough space in the array, write a single char
                file.Write('+');
            }
            else
            {
                file.Write('-');
            }
        }
        file.WriteLine(chars, parsePos - correction, chars.Length - (parsePos - correction));
    }
}
1
rene 9 Июл 2014 в 22:50
Я попробовал твой вариант. Это очень быстро, но не делает то, что ожидалось. Например, он идентифицирует строку с меткой 12 как принадлежащую классу 2. Значение +1 или -1 определяется только первыми элементами строки, разделенными запятыми. Спасибо за ваше время.
 – 
alexandrekow
8 Июл 2014 в 02:08
Я добавил простой пример в конце вопроса
 – 
alexandrekow
8 Июл 2014 в 02:22
Извините, но он все еще не дает ожидаемого результата.
 – 
alexandrekow
8 Июл 2014 в 02:46
Я добавил лучший синтаксический анализатор, который выполняет строгую проверку, сохраняя при этом производительность. Попробуйте.
 – 
rene
9 Июл 2014 в 00:01
Это хорошо работает для простого случая, когда есть только один класс. Но когда в одной строке есть два класса, например 2,1 1: 0,8 2: 0,2, он добавляет +1 только к файлу, сгенерированному для класса 2, но создает неверный файл для класса 1.
 – 
alexandrekow
9 Июл 2014 в 01:01

Вместо того, чтобы повторять непроанализированные строки 200 раз, как насчет синтаксического анализа строк в структуре данных, а затем повторения этих 200 раз? Это должно свести к минимуму количество операций манипулирования строками.

Также используется StreamReader вместо File.ReadLines, поэтому файл целиком не находится в памяти дважды - один раз как строка [], а другой раз как Detail [].

static void Main(string[] args)
{
    var details = ReadDetail("data.txt").ToArray();
    var classValues = Enumerable.Range(0, 10).ToArray();

    foreach (var classValue in classValues)
    {
        // Create file/directory etc

        using (var file = new StreamWriter("out.txt"))
        {
            foreach (var detail in details)
            {
                file.WriteLine("{0} {1}", detail.Classes.Contains(classValue) ? "+1" : "-1", detail.Line);
            }
        }
    }
}

static IEnumerable<Detail> ReadDetail(string filePath)
{
    using (StreamReader reader = new StreamReader(filePath))
    {
        while (!reader.EndOfStream)
        {
            string line = reader.ReadLine();
            int separator = line.IndexOf(' ');

            Detail detail = new Detail
            {
                Classes = line.Substring(0, separator).Split(',').Select(c => Int32.Parse(c)).ToArray(),
                Line = line.Substring(separator + 1)
            };

            yield return detail;
        }
    }
}

public class Detail
{
    public int[] Classes { get; set; }
    public string Line { get; set; }
}
1
Mike Hixson 8 Июл 2014 в 01:29
Мой первый подход был очень похож на ваш код. Это хорошая идея, но после тестирования на большом файле производительность не улучшилась. Ваш код занял 17,15 секунды, а код в этом посте — 17,43 секунды.
 – 
alexandrekow
8 Июл 2014 в 02:25
Вероятно, вы могли бы выжать немного больше скорости, убрав Int32.Parse() для назначения в элементе Classes. Вам нужно будет указать classValues как IEnumerable<string>, как в вашем сообщении.
 – 
Mike Hixson
8 Июл 2014 в 02:36
Ударь это. Я только что попробовал, и это на самом деле медленнее.
 – 
Mike Hixson
8 Июл 2014 в 02:40