Разбор текстового файла в формате CSV - PullRequest
7 голосов
/ 20 июня 2010

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

1,Smith, 249.24, 6/10/2010
2,Johnson, 1332.23, 6/11/2010
3,Woods, 2214.22, 6/11/2010
1,Smith, 219.24, 6/11/2010

Мне нужно найти баланс для клиента на определенную дату.

Мне интересно, должен ли я:

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

или

B. Используйте RegEx, чтобы найти совпадение и отобразить его.

У меня нет большого опыта работы с RegEx, но я изучу его, если в такой ситуации нет ничего проще.

Ответы [ 7 ]

6 голосов
/ 20 июня 2010

Я бы рекомендовал использовать проект с открытым исходным кодом FileHelpers: http://www.filehelpers.net/

Кусок торта:

Определите свой класс:

[DelimitedRecord(",")]
public class Customer
{
    public int CustId;

    public string Name;

    public decimal Balance;

    [FieldConverter(ConverterKind.Date, "dd-MM-yyyy")]
    public DateTime AddedDate;

}   

Используйте его:

var engine = new FileHelperAsyncEngine<Customer>();

// Read
using(engine.BeginReadFile("TestIn.txt"))
{
   // The engine is IEnumerable 
   foreach(Customer cust in engine)
   {
      // your code here
      Console.WriteLine(cust.Name);

      // your condition >> add balance
   }
}
2 голосов
/ 20 июня 2010

Это похоже на довольно стандартную компоновку типа CSV, которая достаточно проста для обработки. На самом деле вы можете сделать это с ADO.Net и провайдером Jet, но я думаю, что в долгосрочной перспективе, вероятно, будет проще обработать его самостоятельно.

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

StreamReader reader = new StreamReader("C:\Path\To\file.txt")
while(true)
{
    var line = reader.ReadLine();
    if(string.IsNullOrEmpty(line))
        break;
    // Process Line
}

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

public class MyData
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Balance { get; set; }
    public DateTime Date { get; set; }
}

И вы можете обработать данные строки следующим способом:

public MyData GetRecord(string line)
{
    var fields = line.Split(',');
    return new MyData()
    {
        Id = int.Parse(fields[0]),
        Name = fields[1],
        Balance = decimal.Parse(fields[2]),
        Date = DateTime.Parse(fields[3])
    };
}

Теперь это самый простой пример, и он не учитывает случаи, когда поля могут быть пустыми, и в этом случае вам нужно будет либо поддерживать NULL для этих полей (используя обнуляемые типы int ?, decimal? И DateTime? ) или определите какое-либо значение по умолчанию, которое будет присвоено этим значениям.

Так что, получив такую ​​возможность, вы можете сохранить коллекцию объектов MyData в списке и легко выполнять вычисления на основе этого. Итак, учитывая ваш пример определения баланса на определенную дату, вы можете сделать что-то вроде:

var data = customerDataList.First(d => d.Name == customerNameImLookingFor 
                                    && d.Date == dateImLookingFor);

Где customerDataList - это коллекция MyData объектов, считанных из файла, customerNameImLookingFor - это переменная, содержащая имя клиента, а customerDateImLookingFor - это переменная, содержащая дату.

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

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

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

class Record
{
  public int Id { get; protected set; }
  public string Name { get; protected set; }
  public decimal Balance { get; protected set; }
  public DateTime Date { get; protected set; }

  public Record (int id, string name, decimal balance, DateTime date)
  {
    Id = id;
    Name = name;
    Balance = balance;
    Date = date;
  }
}

…

Record[] records = from line in File.ReadAllLines(filename)
                   let fields = line.Split(',')
                   select new Record(
                     int.Parse(fields[0]),
                     fields[1],
                     decimal.Parse(fields[2]),
                     DateTime.Parse(fields[3])
                   ).ToArray();

Record wantedRecord = records.Single
                      (r => r.Name = clientName && r.Date = givenDate);
1 голос
/ 20 июня 2010

эй, эй, эй !!!почему бы не сделать это с этим замечательным проектом на codeproject Linq to CSV , круто!горная порода

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

Если это все CSV с хорошим форматированием, как этот, то я бы использовал что-то вроде класса Microsoft.VisualBasic.TextFieldParser или класса Fast CSV над проектом кода, чтобы прочитать все это.

Тип данных немного сложен, потому что я думаю, что не у каждого клиента есть записи на каждый день. Это означает, что вы не можете просто иметь вложенный словарь для поиска. Вместо этого вы хотите сначала «индексировать» по имени, а затем по дате, но форма записи даты немного отличается. Я думаю, что я бы пошел на что-то вроде этого, как я прочитал в каждой записи:

Dictionary<string, SortedList<DateTime, double>>
1 голос
/ 20 июня 2010

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

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

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

Обратите внимание, что обе ваши опции будут сканировать файл.Это хорошо, если вы хотите искать в файле только 1 элемент.

Если вам нужно найти несколько комбинаций клиент / дата в одном и том же файле, вы можете сначала разобрать файл на Dictionary<string, Dictionary <date, decimal>>.

Прямой ответ: для разовых,RegEx, вероятно, будет быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...