JacobIRR
JacobIRR

Reputation: 8966

"inflate" either end of array to meet size requirements

I have a complex set of banking transaction objects which are being distilled into an array of (if needed, nullable) decimals. On the front end, I require a certain number of "transaction sums" in order to fully populate a data table. However, in some cases, there are not enough transactions to fill up the array size, which breaks the UI.

Here is part of the C# ViewModel code that constructs the array:

var minDate = this.Periods[0].FirstDayOfMonth(); // 11/1/2016
var maxDate = this.Periods[this.NumberOfPeriods - 1].LastDayOfMonth(); // 11/20/2017

this.NetCashReceipts = this.NetCashReceiptsCredits
                .Where(bt => bt.Date.Between(minDate, maxDate, true))
                .GroupBy(bt => bt.Date.FirstDayOfMonth())
                .ToDictionary(group => group.Key, group => group.Sum(bt => bt.Amount))
                .OrderBy(kvp => kvp.Key)
                .Select(kvp => kvp.Value)
                .ToArray();

Before the Select statement, the ordered dictionary data looks like this:

{System.Collections.Generic.KeyValuePair<System.DateTime, decimal>[11]}
    [0]: {[1/1/2017 12:00:00 AM, 27172.13]}
    [1]: {[2/1/2017 12:00:00 AM, 5080.18]}
    [2]: {[3/1/2017 12:00:00 AM, 63934.53]}
    [3]: {[4/1/2017 12:00:00 AM, 30006.93]}
    [4]: {[5/1/2017 12:00:00 AM, 64821.43]}
    [5]: {[6/1/2017 12:00:00 AM, 19161.06]}
    [6]: {[7/1/2017 12:00:00 AM, 130019.11]}
    [7]: {[8/1/2017 12:00:00 AM, 132176.08]}
    [8]: {[9/1/2017 12:00:00 AM, 72991.46]}
    [9]: {[10/1/2017 12:00:00 AM, 14440.88]}
    [10]: {[11/1/2017 12:00:00 AM, 18057.63]}

Notice that the earliest transaction is 1/1/2017, which is two months after 11/1/2016. All future code that touches this data will fail by skewing my tables, not to mention IndexOutOfRangeExceptions.

I was thinking of just checking the first date and last date of the Dictionary in a while loop and adding items to the start or end of the array (or a new array if needed) until my array is of the correct size. Is this the only way to proceed? Or is there some LINQ magic that can help handle this? Or something more efficient?

The expected result from working code would include null in places where there are no transactions, like this:

{System.Collections.Generic.KeyValuePair<System.DateTime, decimal>[11]}
    [0]: {[11/1/2016 12:00:00 AM, null]}
    [1]: {[12/1/2016 12:00:00 AM, null]}
    [2]: {[1/1/2017 12:00:00 AM, 27172.13]}
    [3]: {[2/1/2017 12:00:00 AM, 5080.18]}
    [4]: {[3/1/2017 12:00:00 AM, 63934.53]}
    [5]: {[4/1/2017 12:00:00 AM, 30006.93]}
    [6]: {[5/1/2017 12:00:00 AM, 64821.43]}
    [7]: {[6/1/2017 12:00:00 AM, 19161.06]}
    [8]: {[7/1/2017 12:00:00 AM, 130019.11]}
    [9]: {[8/1/2017 12:00:00 AM, 132176.08]}
    [10]: {[9/1/2017 12:00:00 AM, 72991.46]}
    [11]: {[10/1/2017 12:00:00 AM, 14440.88]}
    [12]: {[11/1/2017 12:00:00 AM, 18057.63]}

*The null values would then allow me to populate my front end data table with N/a instead of 0.

Upvotes: 1

Views: 74

Answers (2)

InBetween
InBetween

Reputation: 32780

There is no straightforward way to do this with Linq, but Linq isn't magic, someone wrote all those extension methods. If you can't find one that fits your needs then simply implement one that does!

In this case what you need is a custom Take that pads the output. Padding the end is always easier, so you can do the following:

public static IEnumerable<TSource> TakeAndPadEnd<TSource>(
    this IEnumerable<TSource>source, 
    int count,
    Func<TSource, TSource> paddedValueSelector)
{
    var yielded = 0;
    var previous = default(TSource);

    using (var e = source.GetEnumerator())
    {
        while (e.MoveNext())
        {
            yield return e.Current;
            previous = e.Current;
            yielded += 1;

            if (yielded == count)
                yield break;
        }
    }

    while (yielded < count)
    {
        var newValue = paddedValueSelector(previous);
        yield return newValue;
        previous = newValue;
        yielded += 1;
    }
}

And you'd use it:

this.NetCashReceipts = TakeAndPadd(
    12,
    this.NetCashReceiptsCredits
        .Where(bt => bt.Date.Between(minDate, maxDate, true))
        .GroupBy(bt => bt.Date.FirstDayOfMonth())
        .ToDictionary(
            group => group.Key, 
            group => group.Sum(bt => bt.Amount))
        .OrderBy(kvp => kvp.Key)
        .Select(kvp => kvp.Value),
    v => default(decimal?)).ToArray();

UPDATE: Removed the tuple solution because its redundant, the solution shown above is enough; TSource can already be anything.

Upvotes: 2

huB1erTi2
huB1erTi2

Reputation: 23

You should just do:

List<decimal?> LNetCashReceipts = NetCashReceipts.ToList<decimal?>();

and then work on that.

Upvotes: 0

Related Questions