2013-10-08 2 views
2

저널 항목 광고 항목이 포함 된 입력 파일 (CSV)을 가져와 업무 일지 항목으로 처리해야합니다. 업무 일지 항목은 데이터베이스와 날짜별로 각기 다른 그룹별로 정의됩니다.구분 된 파일을 읽고 저널 항목별로 그룹화

샘플 CSV 데이터 : 그들은 별개의 데이터베이스에 속하고 별개의 일이 있기 때문에

LineNo,Database,Date,Amount 
1,DB3,03/12/2013,1.00 
2,DB1,10/14/2013,1.00 
3,DB2,08/12/2013,1.00 
4,DB3,03/12/2013,1.00 
5,DB2,08/12/2013,1.00 
6,DB1,10/14/2013,1.00 
7,DB1,08/12/2013,1.00 
8,DB1,08/12/2013,1.00 

그룹의 예는 위의 라인 7, 8 일 것이다. 3 호선과 5 호선과 동일합니다.

CSV의 회선이 특별한 순서로 제공되지 않는다면 주어진 시간에 각 업무 일지 항목을 순환하여 검사하는 가장 효과적이고 효율적인 코드는 무엇입니까? 주어진 업무 일지 항목에 대해 각 필드와 각 레코드를 참조 할 수 있어야합니다.

아래 CSV를 처음 읽으려고 시도했지만, 필자가 각 업무 일지 항목을 읽지 않고 줄 단위로보고 있다는 것을 잘 알고 있습니다. 이는별로 도움이되지 않습니다.

가능한 경우이 문제를 해결하기위한 더 강력하고 개선 된 기술을 배우고 싶습니다.

public static void SeparateJournalEntries() 
{ 
    string UploadFilePath = @"\\server\folder\upload.csv"; 
    var reader = new StreamReader(File.OpenRead(UploadFilePath)); 
    string previousSite = ""; 
    int JEcounter = -1; 
    int lineNumber = 1; 

    while (!reader.EndOfStream) 
    { 
     var line = reader.ReadLine(); 
     string[] fields = line.Split(','); 
     Console.WriteLine(fields[0].ToString() + " " + fields[1].ToString()); 

     JEfields JEinstance = new JEfields 
     { 
     Database = fields[0], 
     Date = fields[1], 
     Amount = fields[2] 
     }; 

     if (JEinstance.Site == previousSite || previousSite == System.String.Empty & lineNumber > 1) 
     { 
     JEcounter += 1; 
     previousSite = JEinstance.Site; 
     } 

    } 

} 
+0

1) 귀하의 구문 분석 코드가 그대로 잘입니다. * .csv 파일을 한 줄씩 읽어야합니다. 2) 데이터를 "집계"하려면 목록을 사용할 수 있습니다 (예 : 각 데이터베이스마다 하나의 목록). IMHO ... – paulsm4

+1

[여기] (http://filehelpers.sourceforge.net/)는 CSV 파일을 다른 형식으로 파싱하는 데 환상적이며 훌륭한 무료 오픈 소스 구성 요소입니다. –

+0

이 파일의 크기는 얼마입니까? 쉽게 메모리에 맞출 수 있습니까? 정렬되지 않은 경우 일치하는 저널을 찾으려면 어느 정도까지 역 추적해야 할 것으로 예상됩니까? 작은 파일 인 경우 사전을 사용하면됩니다. – Groo

답변

1

는, 정의 고유 키하여 이러한 값이 {DbName,Date}를 사용하는 그룹이며, 각 키에서 항목 목록으로 맵핑을 작성하십시오.

다른 것을하기 전에이 고유 키를 나타내는 클래스를 만들어 IEquatable<T> interface을 구현하도록해야합니다. 이렇게하면 Equals 메서드를 데이터베이스 인스턴스와 데이터베이스 이름이 동일한 두 인스턴스에서 호출하면 true이 반환되며 .NET 매핑 구문이 제대로 작동하는 데 필요합니다. 이제

/// <summary> 
/// Represents a unique journal info. 
/// This class implements value-type comparison semantics. 
/// </summary> 
class JournalInfo : IEquatable<JournalInfo> 
{ 
    private readonly string _dbName; 
    /// <summary>Gets the database name.</summary> 
    public string DbName 
    { get { return _dbName; } } 

    private readonly DateTime _date; 
    /// <summary>Gets the date.</summary> 
    public DateTime Date 
    { get { return _date; } } 

    /// <summary>Initializes a new instance of the <see cref="JournalInfo"/> class.</summary> 
    public JournalInfo(string db, DateTime date) 
    { 
     _dbName = db; _date = date; 
    } 

    #region Equals overrides to ensure value-type comparison semantics 

    // a lot of plumbing needs to be done here to solve a simple task, 
    // but it must be done to ensure consistency in all cases 

    /// <summary>Determines whether the specified <see cref="JournalInfo" /> is equal to this instance.</summary> 
    public bool Equals(JournalInfo other) 
    { 
     if (object.ReferenceEquals(other, null)) 
      return false; 
     else 
      return this.DbName == other.DbName && this.Date == other.Date; 
    } 

    /// <summary>Determines whether the specified <see cref="System.Object" /> is equal to this instance.</summary> 
    public override bool Equals(object other) 
    { 
     return this.Equals(other as JournalInfo); 
    } 

    /// <summary>Returns a hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</summary> 
    public override int GetHashCode() 
    { 
     var hash = 17; 
     if (this.DbName != null) hash += this.DbName.GetHashCode(); 
     hash = hash * 31 + this.Date.GetHashCode(); 
     return hash; 
    } 

    public static bool operator ==(JournalInfo a, JournalInfo b) 
    { 
     if (object.ReferenceEquals(a, null)) 
      return object.ReferenceEquals(b, null); 

     return ((JournalInfo)a).Equals(b); 
    } 

    public static bool operator !=(JournalInfo a, JournalInfo b) 
    { 
     if (object.ReferenceEquals(a, null)) 
      return !object.ReferenceEquals(b, null); 

     return !((JournalInfo)a).Equals(b); 
    } 

    #endregion 
} 

이 클래스를 준비, 당신은 JournalEntry 클래스를 생성하는 데 사용할 수 있습니다 :

class JournalEntry 
{ 
    public int LineNumber { get; set; } 
    public JournalInfo Info { get; set; } 
    public Decimal Amount { get; set; } 
} 

장소에를 갖는, 당신은 지금 그룹에이 값을 LINQ를 사용에 매핑 할 수 있습니다 항목의 목록 :이 덤프 루프를 사용할 수 있습니다 지금

var path = "input.txt"; 
var culture = System.Globalization.CultureInfo.InvariantCulture; 

Dictionary<JournalInfo, List<JournalEntry>> map = 
    File.ReadLines(path) // lazy read one line at a time 
     .Skip(1) // skip header 
     .Select(line => line.Split(',')) // split into columns 
     .Select((columns, lineNumber) => new JournalEntry() 
      { // parse each line into a journal entry 
       LineNumber = lineNumber, 
       Info = new JournalInfo(
        columns[1], 
        DateTime.ParseExact(columns[2], "MM/dd/yyyy", culture)), 

       Amount = decimal.Parse(columns[3], culture) 
      }) 
     .GroupBy(entry => entry.Info) // group by unique key 
     .ToDictionary(grouping => grouping.Key, grouping => grouping.ToList()); 

는 콘솔 :

당신의 입력 파일의 경우,이 코드를 인쇄해야합니다 :

Journal: DB1 - 12.08.2013 
- Line 6, Amount = 1,00 
- Line 7, Amount = 1,00 
Journal: DB1 - 14.10.2013 
- Line 1, Amount = 1,00 
- Line 5, Amount = 1,00 
Journal: DB2 - 12.08.2013 
- Line 2, Amount = 1,00 
- Line 4, Amount = 1,00 
Journal: DB3 - 12.03.2013 
- Line 0, Amount = 1,00 
- Line 3, Amount = 1,00 
2

당신은 내가 실제 대답의 100 % 확실하지 않다 가장 효율적인 방법을 요구하고 있지만 이것이 내가 무엇을 할 것이라고이므로 :

List<string[]> listofArraysofStrings = new List<string[]>(); 

foreach (string line in file.Lines) 
{ 
    string[] parts = line.Split(','); 
    listofArraysofStrings.Items.Add(parts); 
} 

은 그럼 당신은 뭔가를 실행할 수 있습니다

if (listofArraysofStrings[0][1] == "DB1") 
{ 
    // Do something 
} 

string.Split() 메서드를 사용하여 날짜를 분할하면 일년 월를 얻을 수도 있습니다. 잠재적으로이를 사용자 정의 클래스의 배열로 변환 한 다음 생성자를 사용하여 모든 요소를 ​​한 번에 초기화합니다.

클래스를 사용하면 코드를 깨끗하게 유지하는 데 도움이됩니다. 최선의 조언은 클래스 배열을 초기화 한 다음 클래스를 평가하고 목록에서 필요없는 것을 삭제하는 것입니다. 나는 그보다 더 나은 방법을 개인적으로 볼 수 없습니다.

class JournalEntry 
{ 
    int _dd, _mm, _yy, _linenumber; 
    string _database; 
    float _amount; 

    public JournalEntry(int dd, int mm, int yy, int linenumber, string database, float amount) 
    { 
     _linenumber = linenumber; 
     _database = database; 
     _dd = dd; 
     _mm = mm; 
     _yy = yy; 
     _amount = amount; 
    } 
} 

그리고 샘플 구현 :이 모든 말이

List<JournalEntry> journalEntryList = new List<JournalEntry>(); 
JournalEntry je; 
foreach (string line in file.Lines) 
{ 
    string[] mls = line.Split(','); // mls is short for MyLineSplit 
    string[] dateinfo = mls[2].Split('/'); 
    je = new JournalEntry(mls[0], mls[1], Convert.ToInt32(dateinfo[0]), Convert.ToInt32(dateinfo[1]), Convert.ToInt32(dateinfo[2]), mls[3]); 
    journalEntryList.Items.Add(je); 
} 

희망, 나는 그것을 컴파일 또는 아무것도하지 않은 점에 유의 여기

은 샘플 클래스입니다. 또한 Convert.ToInt32()에 예외 처리가 없다는 점에 유의하십시오. Int32.TryParse()를 사용하고 싶지만 그 코드의 정확한 레이아웃을 잠깐 기억할 수는 없으며 C# IDE가 없습니다. 손에.

이 방법의 장점은 일부 효율성을 희생하지만 더 이상 증가시킬 필요가 없지만 배열보다 훨씬 쉽게 목록을 추가하고 제거 할 수 있다는 것입니다. 글로벌 카운터. list.Items.Count를 호출하여 보유한 값의 개수를 확인할 수 있습니다.

이 문제에 직면 할 수있는 사람에게주는 또 다른 참고 사항 : 사전을 사용하면 데이터를 정렬하고 정렬하는 더 쉬운 방법을 제공 할뿐만 아니라 성능과 액세스 가능성이 향상되므로 이점이 있습니다.

+0

전체 파일을 메모리에로드 할 것을 제안하는 경우 OP 설명대로 사전을 사용하여 이러한 항목을 그룹화해야합니다. – Groo

+0

@Groo - 이미 언급 한 것처럼 IDE를 사용할 필요가 없으므로 메모리에서이 코드를한데 모아서 결과 만 알 수 있습니다. 의견을 보내 주셔서 감사합니다. 나중에이 내용을 발견 할 수 있도록 제안 사항으로 게시물에 추가하겠습니다. – XtrmJosh

1

저는이 문제를 해결하기 위해 Linq와 객체의 힘을 사용합니다. 단일 Linq 문을 사용하여 파일을 읽고 정렬 할 수 있습니다. 그런 다음 원하는 순서로 Journal 개체를 반복하거나 쉽게 재정렬 할 수 있습니다.

에서 읽고 파일을 정렬하려면 :

private void button4_Click(object sender, EventArgs e) 
    { 
     IEnumerable<Journal> sortedJournals = GetJournals(@"c:\temp\test.txt"); 

     //now you can loop through sortedJournals 

     //or you can create groups of journals 
     var journalByDatabase = sortedJournals.ToLookup(j => j.Database + j.Date); 

     foreach (var group in journalByDatabase) 
     { 
      foreach (var item in group) 
      { 
      } 
     } 
    } 

    public IEnumerable<Journal> GetJournals(string JournalsPath) 
    { 

     var myJournals = 
      from c in 
       (
        from line in File.ReadAllLines(JournalsPath).Skip(1) 
        let aRecord = line.Split(',') 
        select new Journal() 
        { 
         LineNo = Convert.ToInt32(aRecord[0].Trim()), 
         Database = aRecord[1].Trim(), 
         Date = Convert.ToDateTime(aRecord[2].Trim()), 
         Amount = Convert.ToDecimal(aRecord[3].Trim()), 
        } 
       ).OrderBy(x => x.Database) 
      select c; 

     return myJournals; 

    } 

간단한 저널 클래스 : 당신이 실제로 원하는 것은

public class Journal 
{ 
    public int LineNo { get ;set;} 
    public string Database { get; set;} 
    public DateTime Date { get; set; } 
    public Decimal Amount { get; set; } 

    public Journal() 
    { 
    } 
} 
+0

연결 문자열을 해시 키로 사용하면 신중하게 구성하지 않으면 충돌이 발생하는 경우가 많습니다. 'j.Database + j.Date'가 고유 키를 생성합니까? – Groo

+0

그는 고유 키를 요구하지 않았습니다. 이 아이디어는 데이터베이스와 날짜가 일치하는 업무 일지 항목을 그룹화하는 것이 었습니다. 이 두 필드를 연결하면됩니다. – Kevin

+0

'("DB1", "12/3/2013")'및'("DB11", "2/3/2013")'항목을 고려하십시오.여러분의 코드는''DB112/3/2013 ''과 같은 그룹에 같은 키를 생성합니다. 나는 당신이 "그가 유일한 열쇠를 요구하지 않았다"는 의미가 무엇인지 확신하지 못한다. 그러나 나는이 두 항목이 같은 그룹에 속하지 않는다고 확신한다. 이 예에서는 쉽게 수정할 수 있지만 문자열을 키로 사용할 때주의해야하는 이유의 예입니다. 그들은 유혹 적으로 단순하지만 적절한 튜플 구현보다 (약간) 성능이 떨어지는 경우를 제외하고는 종종 미묘한 충돌 오류가 발생합니다. – Groo

관련 문제