我需要输入包含日记帐分录行项目的输入文件(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 :(得分: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亲自动手。
一个小小的补充是,这种方法的好处包括列表可以比数组更容易添加和删除,尽管以牺牲一些效率为代价,但你不再需要增加任何全局counter,因为你可以调用list.Items.Count来找出你有多少值!
对于可能偶然发现这一点的任何人的另一个注意事项:使用字典将是有利的,因为它将提供更简单的方法来排序和排列您的数据,以及提高性能和可访问性。
答案 1 :(得分: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()
{
}
}
答案 2 :(得分: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());
现在您可以使用循环将其转储到控制台:
// 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