0

LINQ. I have an IEnumerable of type Transaction:

private class Transaction{
    public string Debitor { get; set; }
    public double Debit { get; set; }
    public string Creditor { get; set; } }

Sample IEnumerable<Transaction>:

[Debitor] | [Spend] | [Creditor]
luca       10          alessio 
giulia     12          alessio 
alessio    7           luca
alessio    6           giulia
marco      5           giulia
alessio    3           marco
luca       1           alessio

I would like to group the Transactions where Debitor == Creditor; else in a separate group. For the previous example, I should get:

  Group 1:
        luca       10          alessio 
        alessio    7           luca
        luca       1           alessio
  Group 2:
        giulia     12          alessio 
        alessio    6           giulia
  Group 3:
        marco      5           giulia
  Group 4:
        alessio    3           marco

I have solved it using 2 for loops (one nested) over the same IEnumerable, performing the check and using separate lists for the output, but I wonder if there is a less clunky way of doing this using LINQ.

A similar question might be: LINQ Conditional Group, however in this case the grouping condition is variable dependent on the other elements of the IEnumerable.

alelom
  • 2,130
  • 3
  • 26
  • 38
  • Hmm I think you should add more about your assumptions with groups here. Are you going to have groups with always two entries? Will there be leftover entries? Is there a possibility of duplicates and what should happen in these cases? What happens in case of a three-way group? Etc. – Jack Nov 26 '18 at 00:16
  • @Jack - see updated question. Thanks for spotting that. – alelom Nov 26 '18 at 09:16

2 Answers2

1

For a simple solution, you could group using a key created with Creditor and Debitor. For example.

string CreateKey(params string[] names)=>string.Join(",",names.OrderBy(x => x));
var result = transactionCollection.GroupBy(x=> CreateKey(x.Debitor,x.Creditor)); 

Please note I have used a collection in CreateKey, in case you have more similar grouping factors, but you can write a simpler version for CreateKey if the condition is always involving Creditor and Debitor alone.

Anu Viswan
  • 17,797
  • 2
  • 22
  • 51
  • Thanks, as pointed out by @Jack however I missed to clarify what should happen when there are "leftover" entries. – alelom Nov 26 '18 at 10:32
1

The simplest way would indeed be to create a combined key, such as in Anu's answer. Another way to do that (not necessarily better, but avoids sub collections and string joining), is:

var groups = transactions.GroupBy(t=> t.Debitor.CompareTo(t.Creditor) > 0 ? (t.Debitor,t.Creditor) : (t.Creditor,t.Debitor));

NB The above assumes you can use implicit Tuple creation. If you have a lower C# version and/or don't have the ValueTuple NuGet package installed, you can use: var groups = transactions.GroupBy(t=> t.Debitor.CompareTo(t.Creditor) > 0 ? Tuple.Create(t.Debitor,t.Creditor) : Tuple.Create(t.Creditor,t.Debitor));


Purely for the sake of mentioning it, another way, is to use a custom equality comparer for the group by. This might be overkill depending on your needs, collection size, need for reusabillity, etc, but to show the possibility all the same: first create a class (or implement it in Transaction directly)

class TransactionDebCredComparer : EqualityComparer<Transaction>
{       
    public override bool Equals(Transaction t1, Transaction t2) => (t1.Debitor == t2.Creditor && t2.Debitor == t1.Creditor) || (t1.Debitor == t2.Debitor && t2.Creditor == t1.Creditor);
    public override int GetHashCode(Transaction t) => t.Debitor.GetHashCode() ^ t.Creditor.GetHashCode();
}

Then you can group your enumerable by using

var groups = transactions.GroupBy(t=>t, new TransactionDebCredComparer() );
Me.Name
  • 12,259
  • 3
  • 31
  • 48