0

I have a list of transaction history with the corresponding amount.

    public class Invoice
    {
        public Invoice(string TransactionReference, double Amount)
        {
            this.TransactionReference = TransactionReference;
            this.Amount = Amount;
        }
    
        public string TransactionReference { get; set; }
        public double Amount { get; set; }
    
    }

    List<Invoice> items = new List<Invoice>() {
        new Invoice("808861", -45.85),
        new Invoice("822634", -144.32),
        new Invoice("822635", -56.63),
        new Invoice("835308", 2970.55),
        new Invoice("835309", 869.36),
        new Invoice("835310", 3050.73),
        new Invoice("835311", 657.74),
        new Invoice("835312", 168.42),
        new Invoice("835348", 2922.69),
        new Invoice("835349", 324.5),
        new Invoice("835350", 906.3),
        new Invoice("835351", 420.21),
        new Invoice("835352", 851.7),
        new Invoice("838880", 158.43),
        new Invoice("838881", 2062.46),
        new Invoice("838882", 567.26),
        new Invoice("838883", 40.96),
        new Invoice("838884", 207.16),
        new Invoice("838889", 2726.13),
        new Invoice("838890", 29.25),
        new Invoice("838891", 1458.28),
        new Invoice("838892", 219.58),
        new Invoice("839930", 2791.99),
        new Invoice("839931", 455.47),
        new Invoice("839932", 514.94),
        new Invoice("839934", 666.78),
        new Invoice("840758", -341.34),
        new Invoice("855741", -113.55),
        new Invoice("855973", -85.46),
        new Invoice("866848", -39.53),
        new Invoice("877471", -58.17),
        new Invoice("877472", -58.17),
        new Invoice("878790", -459.53),
        new Invoice("892869", -6353.36)
};

I want to divide the transaction history into many small groups with the following conditions:

  1. Maximum for each group will contain 26 transaction history.
  2. The total amount in each group must be greater than 0.
  3. The last group contains the remaining histories as long as the condition is greater than 0.

Below I write indiscriminately to calculate the trial total of the transaction, but the final group is still less than 0.

private static void ShowSumOf26Invoice( List<Invoice> list, int from, int to )
{
    if (list.Count > 26)
    {
        Console.WriteLine("Total amount from: " + from + " to " + to + " : " + items.GetRange(0,26).Sum(x => x.Amount));
        items.RemoveRange(from - 1, 26);
        from = from - 1 + 26;
        to = to + 26;
        ShowSumOf26Invoice(items, from, to);
    }
    else
    {
        Console.WriteLine("Total amount from: " + from + " to " + (to - 26 + items.Count) + " : " + items.Sum(x => x.Amount));
    }
}
    
static void Main(string[] args)
{
    Console.WriteLine( "Total amount: " + items.Sum( x => x.Amount ) );
    int from =  1;
    int to   = 26;
    ShowSumOf26Invoice( items, from, to );
    
    Console.WriteLine("Press any key to end the program!");
    Console.ReadKey();
}

This is a model of Invoice.

public class Invoice
{
    public Invoice(string TransactionReference, double Amount)
    {
        this.TransactionReference = TransactionReference;
        this.Amount = Amount;
    }

    public string TransactionReference { get; set; }
    public double Amount { get; set; }

}

Could you please give me some solutions for this problem? Thank you very much!

  • 1
    I am a little uncertain about what you mean by this "*The total amount in each group must be greater than 0.*" What happens when its not? – TheGeneral Aug 10 '20 at 03:05
  • Hi @TheGeneral! Thanks for your comment on my question. If the total amount in each group is less than 0, it will be not accepted. The result for each group should not have a total amount < 0. – Nguyen Anh Duy Aug 10 '20 at 03:11
  • 1
    @NguyenAnhDuy - Rule 2 may not be possible. And Rule 3 is a bit of a tautology - it has to be true as you are asking to "divide the transaction history into many small groups" and Rule 2 says it must be greater than zero anyway. – Enigmativity Aug 10 '20 at 03:55

1 Answers1

0

You can do it in a single Linq expression, like so:

List<String> messages = listOfInvoices
    .Select( ( inv, idx ) => ( inv, idx, batch: idx / 26 ) )
    .GroupBy( t => t.batch )
    .Select( grp => (
        batch: grp.Key,
        from : grp.First().inv.TransactionReference,
        to   : grp.Last() .inv.TransactionReference,
        count: grp.Count(),
        total: grp.Sum( t => t.inv.Amount )
    ))
//  .Where( batch => batch.total >= 0 )
    .Select( b => "Total for batch {0:N0} ({1:N0} invoices from {2} to {3}) is {4:C}".Fmt( b.batch + 1, b.count, b.from, b.to, b.total ) )
    .ToList();

To be succinct, the above Linq expression uses this extension method:

static class StringExtensions
{
    public static String Fmt( this String format, params Object[] args )
    {
        return String.Format( CultureInfo.CurrentCulture, format, args );
    }
}

Also, you should use Decimal for Amount. Do not use double: Why not use Double or Float to represent currency?


Here's the whole program you can copy+paste into DotNetFiddle.net to see it working (choose "Compiler: .NET Core 3.1"):

using System;
using System.Linq;
using System.Globalization;
using System.Collections.Generic;

public class Invoice
{
    public Invoice( String transactionReference, Decimal amount )
    {
        this.TransactionReference = transactionReference;
        this.Amount               = amount;
    }

    public String  TransactionReference { get; }
    public Decimal Amount               { get; }

}

static class StringExtensions
{
    public static String Fmt( this String format, params Object[] args )
    {
        return String.Format( CultureInfo.CurrentCulture, format, args );
    }
}
                    
public class Program
{
    public static void Main()
    {
        List<Invoice> listOfInvoices = new List<Invoice>() {
            new Invoice("808861",   -45.85M), //  0
            new Invoice("822634",  -144.32M), //  1
            new Invoice("822635",   -56.63M), //  2
            new Invoice("835308",  2970.55M), //  3
            new Invoice("835309",   869.36M), //  4
            new Invoice("835310",  3050.73M), //  5
            new Invoice("835311",   657.74M), //  6
            new Invoice("835312",   168.42M), //  7
            new Invoice("835348",  2922.69M), //  8
            new Invoice("835349",    324.5M), //  9
            new Invoice("835350",    906.3M), // 10
            new Invoice("835351",   420.21M), // 11
            new Invoice("835352",    851.7M), // 12
            new Invoice("838880",   158.43M), // 13
            new Invoice("838881",  2062.46M), // 14
            new Invoice("838882",   567.26M), // 15
            new Invoice("838883",    40.96M), // 16
            new Invoice("838884",   207.16M), // 17
            new Invoice("838889",  2726.13M), // 18
            new Invoice("838890",    29.25M), // 19
            new Invoice("838891",  1458.28M), // 20
            new Invoice("838892",   219.58M), // 21
            new Invoice("839930",  2791.99M), // 22
            new Invoice("839931",   455.47M), // 23
            new Invoice("839932",   514.94M), // 24
            new Invoice("839934",   666.78M), // 25
            new Invoice("840758",  -341.34M), // 26
            new Invoice("855741",  -113.55M), // 27
            new Invoice("855973",   -85.46M), // 28
            new Invoice("866848",   -39.53M), // 29
            new Invoice("877471",   -58.17M), // 30
            new Invoice("877472",   -58.17M), // 31
            new Invoice("878790",  -459.53M), // 32
            new Invoice("892869", -6353.36M)  // 33
        };
        
        List<String> messages = listOfInvoices
            .Select( ( inv, idx ) => ( inv, idx, batch: idx / 26 ) )
            .GroupBy( t => t.batch )
            .Select( grp => (
                batch: grp.Key,
                from : grp.First().inv.TransactionReference,
                to   : grp.Last() .inv.TransactionReference,
                count: grp.Count(),
                total: grp.Sum( t => t.inv.Amount )
            ))
//          .Where( batch => batch.total >= 0 )
            .Select( b => "Total for batch {0:N0} ({1:N0} invoices from {2} to {3}) is {4:C}".Fmt( b.batch + 1, b.count, b.from, b.to, b.total ) )
            .ToList();
        
        foreach( String message in messages )
        {
            Console.WriteLine( message );
        }
    }
}
Dai
  • 141,631
  • 28
  • 261
  • 374
  • Hi @Dai! Thanks for your reply to my question! I will post adding a model for Invoice below. – Nguyen Anh Duy Aug 10 '20 at 03:26
  • I have used your code but it has a problem at InvoiceId and Amount. from : grp.First( inv => inv.InvoiceId ), to : grp.Last ( inv => inv.InvoiceId ), count: grp.Count(), total: grp.Sum( inv => inv.Amount ), – Nguyen Anh Duy Aug 10 '20 at 03:29
  • How to call right follow my model Invoice? I tried from: grp.First(inv => inv.inv.TransactionReference), but it is still warning error. – Nguyen Anh Duy Aug 10 '20 at 03:30
  • @NguyenAnhDuy What is the error? Is it a compiler error or a runtime error? – Dai Aug 10 '20 at 03:34
  • It is a complier error. With your code we will get error: Error CS1061 “…Does Not Contain Definition and No Extension Method…accepting a first argument of type ” could be found. I have changed to my code and get error: CS0029 C# Cannot implicitly convert type 'string' to 'bool' and CS1662 C# Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type. – Nguyen Anh Duy Aug 10 '20 at 03:38
  • This solution is throwing away any groups that have a negative total. Is that what the OP wants? It seems like a bug to throw away transactions. – Enigmativity Aug 10 '20 at 03:57
  • @Enigmativity I based that on his part of the OP's post: "The total amount in each group must be greater than 0." – Dai Aug 10 '20 at 04:07
  • @NguyenAnhDuy Do you have `using System.Linq` at the top of your file? I fixed a bug in my answer too. – Dai Aug 10 '20 at 04:08
  • @Dai. I changed the model as you, 2 properties are InvoiceID and Amount. But Amount only is ok. InvoiceID still gets a compiler error. CS0029 C# Cannot implicitly convert type 'int' to 'bool' and CS1662 C# Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type. Could you please tell me how to write model? – Nguyen Anh Duy Aug 10 '20 at 04:18
  • I also have using.System.Linq and amount is ok, not for InvoiceID. – Nguyen Anh Duy Aug 10 '20 at 04:19
  • @Dai! Thanks for your reply and update the code. But the last group (in this case is the second group) has still the total amount is less than 0, that is what I want. But I would like to say many thanks to you cause of you help me to modifiy code. – Nguyen Anh Duy Aug 10 '20 at 07:30