Быстрый и эффективный способ прочитать разделенный пробелами файл чисел в массив? - PullRequest
3 голосов
/ 01 июня 2010

Мне нужен быстрый и эффективный метод, чтобы прочитать разделенный пробелами файл с числами в массив. Файлы отформатированы следующим образом:

4 6
1 2 3 4 5 6
2 5 4 3 21111 101
3 5 6234 1 2 3
4 2 33434 4 5 6

Первая строка - это размерность массива [строки столбцов]. Следующие строки содержат данные массива.

Данные также могут быть отформатированы без каких-либо символов новой строки:

4 6
1 2 3 4 5 6 2 5 4 3 21111 101 3 5 6234 1 2 3 4 2 33434 4 5 6

Я могу прочитать первую строку и инициализировать массив значениями строк и столбцов. Затем мне нужно заполнить массив значениями данных. Моей первой идеей было прочитать файл построчно и использовать функцию split. Но второй перечисленный формат дает мне паузу, потому что весь массив данных будет загружен в память все сразу. Некоторые из этих файлов находятся в 100 МБ. Вторым способом было бы прочитать файл порциями, а затем проанализировать их по частям. Может быть, у кого-то есть лучший способ сделать это?

Ответы [ 7 ]

2 голосов
/ 02 июня 2010

Читайте файл символ за раз. Если это пробел, начните новый номер. Если это цифра, используйте ее.

для чисел с несколькими цифрами сохранить переменную счетчика:

int counter = 0;
while (fileOpen) {
    char ch = readChar(); // use your imagination to define this method.
    if (isDigit(ch)) {
        counter *= 10;
        counter += asciiToDecimal(ch);
    } else if (isWhitespace(ch)) {
        appendToArray(counter);
        counter = 0;
    } else {
        // Error?
    }
}

Отредактировано для уточнения.

2 голосов
/ 02 июня 2010

Какова ваша схема использования данных после их загрузки? Вам обычно нужно прикасаться к каждому элементу массива или вы просто создадите разреженный / произвольный доступ?

Если вам нужно прикоснуться к большинству элементов массива, возможно, лучше всего загрузить его в память.

Если вам нужно просто получить доступ к определенным элементам, вы можете лениво загрузить нужные элементы в память. Одна из стратегий состоит в том, чтобы определить, какой из двух макетов использует файл (с / без новой строки) и создать алгоритм для загрузки конкретного элемента непосредственно с диска по мере необходимости (поиск смещения файла, чтение и анализ). Для эффективного повторного доступа к одному и тому же элементу может иметь смысл сохранить элемент после прочтения в словаре, проиндексированном смещением. Сначала проверьте словарь, прежде чем переходить к файлу для определенного значения.

По общему принципу, я бы выбрал простой маршрут, если ваше тестирование не доказывает, что вам нужно идти по более сложному маршруту ( избегать преждевременной оптимизации ).

1 голос
/ 02 июня 2010

Как насчет:

    static void Main()
    {
        // sample data
        File.WriteAllText("my.data", @"4 6
1 2 3 4 5 6
2 5 4 3 21111 101
3 5 6234 1 2 3
4 2 33434 4 5 6");

        using (Stream s = new BufferedStream(File.OpenRead("my.data")))
        {
            int rows = ReadInt32(s), cols = ReadInt32(s);
            int[,] arr = new int[rows, cols];
            for(int y = 0 ; y < rows ; y++)
                for (int x = 0; x < cols; x++)
                {
                    arr[y, x] = ReadInt32(s);
                }
        }
    }

    private static int ReadInt32(Stream s)
    { // edited to improve handling of multiple spaces etc
        int b;
        // skip any preceeding
        while ((b = s.ReadByte()) >= 0 && (b < '0' || b > '9')) {  }
        if (b < 0) throw new EndOfStreamException();

        int result = b - '0';
        while ((b = s.ReadByte()) >= '0' && b <= '9')
        {
            result = result * 10 + (b - '0');
        }
        return result;
    }

На самом деле, это не очень специфично для разделителей - он будет в значительной степени предполагать, что все, что не является целым числом, является разделителем, и он поддерживает только ASCII (вы используете использовать ридер, если вам нужны другие кодировки) .

0 голосов
/ 02 июня 2010

Вот два метода

IEnumerable<int[]> GetArrays(string filename, bool skipFirstLine)
{
    using (StreamReader reader = new StreamReader(filename))
    {
        if (skipFirstLine && !reader.EndOfStream)
            reader.ReadLine();

        while (!reader.EndOfStream)
        {
            string temp = reader.ReadLine();
            int[] array = temp.Trim().Split().Select(s => int.Parse(s)).ToArray();
            yield return array;
        }
    }
}

int[][] GetAllArrays(string filename, bool skipFirstLine)
{
    int skipNumber = 0;
    if (skipFirstLine )
        skipNumber = 1;
    int[][] array = File.ReadAllLines(filename).Skip(skipNumber).Select(line => line.Trim().Split().Select(s => int.Parse(s)).ToArray()).ToArray();
    return array;
}

Если вы имеете дело с большими файлами, первый, скорее всего, предпочтительнее. Если файлы небольшие, то второй может загрузить всю вещь в неровный массив.

0 голосов
/ 02 июня 2010

вы хотите передать файл в память и анализировать, как вы идете.

private IEnumerable<String> StreamAsSpaceDelimited(this StreamReader reader)
{
    StringBuilder builder = new StringBuilder();
    int v;
    while((v = reader.Read()) != -1)
    {
        char c = (char) v;
        if(Char.IsWhiteSpace(c))
        {
            if(builder.Length >0)
            {
                yield return builder.ToString();
                builder.Clear();
            }
        }
        else
        {
            builder.Append(c);
        }
    }
    yield break;
}

это позволит проанализировать файл в коллекцию строк, разделенных пробелом ( lazily ), а затем вы сможете прочитать их как двойные числа, например:

using(StreamReader sr = new StreamReader("filename"))
{
    var nums = sr.StreamAsSpaceDelimited().Select(s => int.Parse(s));
    var enumerator = nums.GetEnumerator();
    enumerator.MoveNext();
    int numRows = enumerator.Current;
    enumerator.MoveNext();
    int numColumns = enumerator.current;
    int r =0, c = 0;
    int[][] destArray = new int[numRows][numColumns];
    while(enumerator.MoveNext())
    {
        destArray[r][c] = enumerator.Current;
        c++;
        if(c == numColumns)
        {
            c = 0;
            r++;
            if(r == numRows)
               break;//we are done
        }
    }

потому что мы используем итераторы, это никогда не должно читать больше, чем несколько символов за раз. это общий подход, используемый для анализа больших файлов (например, так работает LINQ2CSV ).

0 голосов
/ 02 июня 2010

Предположим, мы прочитали весь файл в строку.
Вы говорите, что первые два - это строки и столбцы, поэтому нам определенно нужно разобрать числа.
После этого мы можем взять первые два, создать нашу структуру данных и заполнить ее соответствующим образом.

var fileData = File.ReadAllText(...).Split(' ');
var convertedToNumbers = fileData.Select(entry => int.Parse(entry));
int rows = convertedToNumbers.First();
int columns = convertedToNumbers.Skip(1).First();
// Now we have the number of rows, number of columns, and the data.
int[,] resultData = new int[rows, columns];
// Skipping over rows and columns values.
var indexableData = convertedToNumbers.Skip(2).ToList();
for(int i=0; i<rows; i++)
    for(int j=0; j<columns; j++)
        resultData[i, j] = inedexableData[i*rows + j];

Альтернативой может быть чтение первых двух из потока, инициализация массива, а затем считывание n значений за раз, что будет сложно. Кроме того, лучше всего сохранять файлы открытыми в кратчайшие сроки.

0 голосов
/ 02 июня 2010

Если компьютер, на котором вы анализируете эти текстовые файлы, не ограничен, файлы размером в несколько сотен МБ по-прежнему должны помещаться в памяти. Я бы посоветовал перейти к первому подходу: читать построчно и использовать сплит.

Если память становится проблемой, ваш второй подход к чтению кусками должен работать нормально.

По сути, я говорю только о том, чтобы реализовать это и измерить, если производительность является проблемой.

...