実際に必要なのは、これらの値をを使用して定義された一意のキー{DbName,Date}
でグループ化し、各キーからエントリのリストへのマッピングを作成することです。
何よりもまず、この一意のキーを表すクラスを作成し、IEquatable<T>
インターフェイスを実装する必要があります。Equals
これにより、同じデータベース名と日付を持つ 2 つの異なるインスタンスでメソッドを呼び出すと、確実にが返さ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