3

我需要获取一个包含日记帐分录行项目的输入文件 (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;
      }

   }

}
4

3 回答 3

3

当您要求最有效的方法时,我不能 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 来找出你有多少个值!

给任何可能偶然发现的人的另一个注意事项:使用字典将是有利的,因为它将提供更简单的方法来对数据进行排序和排列,并提高性能和可访问性。

于 2013-10-08T22:21:10.420 回答
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()
    {
    }
}
于 2013-10-08T22:48:03.263 回答
1

您真正想要的是通过使用定义的唯一键{DbName,Date}对这些值进行分组,然后创建从每个键到条目列表的映射。

首先,您应该创建一个表示此唯一键的类,并使其实现IEquatable<T>接口。这将确保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()); 

现在您可以使用循环将其转储到控制台:

// this loop also orders entries by database name and date
foreach (var item in map.OrderBy(m => m.Key.DbName).ThenBy(m => m.Key.Date))
{
    Console.WriteLine("Journal: {0} - {1:dd/MM/yyyy}", 
        item.Key.DbName, 
        item.Key.Date);

    foreach (var entry in item.Value.OrderBy(e => e.LineNumber))
    {
        Console.WriteLine(" - Line {0}, Amount = {1:0.00}",
            entry.LineNumber,
            entry.Amount);
    }
}

对于您的输入文件,此代码应打印:

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
于 2013-10-10T09:43:04.867 回答