LINQ to SQL and a running total on ordered results - c#

I want to display a customer's accounting history in a DataGridView and I want to have a column that displays the running total for their balance. The old way I did this was by getting the data, looping through the data, and adding rows to the DataGridView one-by-one and calculating the running total at that time. Lame. I would much rather use LINQ to SQL, or LINQ if not possible with LINQ to SQL, to figure out the running totals so I can just set DataGridView.DataSource to my data.
This is a super-simplified example of what I'm shooting for. Say I have the following class.
class Item
{
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public decimal RunningTotal { get; set; }
}
I would like a L2S, or LINQ, statement that could generate results that look like this:
Date Amount RunningTotal
12-01-2009 5 5
12-02-2009 -5 0
12-02-2009 10 10
12-03-2009 5 15
12-04-2009 -15 0
Notice that there can be multiple items with the same date (12-02-2009). The results should be sorted by date before the running totals are calculated. I'm guessing this means I'll need two statements, one to get the data and sort it and a second to perform the running total calculation.
I was hoping Aggregate would do the trick, but it doesn't work like I was hoping. Or maybe I just couldn't figure it out.
This question seemed to be going after the same thing I wanted, but I don't see how the accepted/only answer solves my problem.
Any ideas on how to pull this off?
Edit
Combing the answers from Alex and DOK, this is what I ended up with:
decimal runningTotal = 0;
var results = FetchDataFromDatabase()
.OrderBy(item => item.Date)
.Select(item => new Item
{
Amount = item.Amount,
Date = item.Date,
RunningTotal = runningTotal += item.Amount
});

Using closures and anonymous method:
List<Item> myList = FetchDataFromDatabase();
decimal currentTotal = 0;
var query = myList
.OrderBy(i => i.Date)
.Select(i =>
{
currentTotal += i.Amount;
return new {
Date = i.Date,
Amount = i.Amount,
RunningTotal = currentTotal
};
}
);
foreach (var item in query)
{
//do with item
}

How about this: (credit goes to this source)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
delegate string CreateGroupingDelegate(int i);
static void Main(string[] args)
{
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 69, 2007};
int running_total = 0;
var result_set =
from x in list
select new
{
num = x,
running_total = (running_total = running_total + x)
};
foreach (var v in result_set)
{
Console.WriteLine( "list element: {0}, total so far: {1}",
v.num,
v.running_total);
}
Console.ReadLine();
}
}
}

In case this hasn't been answered yet, I have a solution that I have been using in my projects. This is pretty similar to an Oracle partitioned group. The key is to have the where clause in the running total match the orig list, then group it by date.
var itemList = GetItemsFromDBYadaYadaYada();
var withRuningTotals = from i in itemList
select new {i.Date, i.Amount,
RunningTotal = itemList.Where( x=> x.Date == i.Date).
GroupBy(x=> x.Date).
Select(DateGroup=> DateGroup.Sum(x=> x.Amount)).Single()};

Aggregate can be used to obtain a running total as well:
var src = new [] { 1, 4, 3, 2 };
var running = src.Aggregate(new List<int>(), (a, i) => {
a.Add(a.Count == 0 ? i : a.Last() + i);
return a;
});

Most of the other answers to this, which properly set the running totals within the objects, rely on a side-effect variable, which is not in the spirit of functional coding and the likes of .Aggregate(). This solution eliminates the side-effect variable.
(NB - This solution will run on the client as with other answers, and so may not be optimal for what you require.)
var results = FetchDataFromDatabase()
.OrderBy(item => item.Date)
.Aggregate(new List<Item>(), (list, i) =>
{
var item = new Item
{
Amount = i.Amount,
Date = i.Date,
RunningTotal = i.Amount + (list.LastOrDefault()?.RunningTotal ?? 0)
};
return list.Append(item).ToList();
// Or, possibly more efficient:
// list.Add(item);
// return list;
});

using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var list = new List<int>{1, 5, 4, 6, 8, 11, 3, 12};
int running_total = 0;
list.ForEach(x=> Console.WriteLine(running_total = x+running_total));
}
}

Related

List of numbers lowest value first, and then by input date

So I have a list of prices from a database. I would like to sort it so that the first entry in a list is the entry with the lowest number. And then all other entry are order by input date.
How can this be done?
This is my code, which is a mess, sorry I'm trying stuff :)
var itemPriceDate = itemPrice.OrderBy(d => d.Invoice.DateInvoice).ToList();
var itemPriceDateLow= itemPriceDate.OrderBy(c => c.qtPrice).ThenBy(d => d.Invoice.DateInvoice);
ViewBag.ItemPrice = itemPriceDateLow; ```
First find out the lowest value from the List(itemPrice).
double lowest_price = itemPrice.Min(c => c.qtPrice);
Next, remove the lowest element from the list.
var itemToRemove = itemPrice.Single(c => c.qtPrice == lowest_price);
itemPrice.Remove(itemToRemove);
Next, sort the remaining list based on input Date.
var newList = itemPrice.OrderByDescending(d => d.Invoice.DateInvoice).ToList();
Finally, add lowest element at first index
newList.Insert(0, lowest_price);
LINQ is great when it works, but it sometimes does unexpected things. Depending on how large your dataset is, you may be better off doing it as a stored procedure that returns the data already ordered.
If the dataset is small or you're cornered into using C# to do it there is the option of using a custom sort function. Without knowing the exact structure of your data, this is more intended as a blanket example that will need tweaking accordingly.
Let's say your list is stored in the itemPrice variable, if you do something along the lines of:
itemPrice.Sort((a, b) => {
int retVal = a.qtPrice < b.qtPrice;
return ret != 0 ? ret : a.Invoice.DateInvoice < b.Invoice.DateInvoice;
});
Will sort by qtPrice and then fall back to the DateInvoice field; you may need to swap the less than to a greater than to get your desired order.
One sort is enough. What I think it should be is:
var itemPriceDateLow= itemPriceDate.OrderBy(c => c.qtPrice).ThenBy(d => d.Invoice.DateInvoice);
This will obviously give you whole collection. You might want to use .First() if you want to get top most element.
One thing to remember - ThenBy, OrderBy are ascending by default.
Take a look at this example:
class Program
{
static void Main(string[] args)
{
List<ItemPrice> items = new List<ItemPrice>();
items.Add(new ItemPrice() { Date = DateTime.Now, QtyPrice = 1});
items.Add(new ItemPrice() { Date = DateTime.Now.AddDays(-1), QtyPrice = 1});
items.Add(new ItemPrice() { Date = DateTime.Now, QtyPrice = 2});
var sortedItem = items.OrderBy(p => p.QtyPrice).ThenBy(p => p.Date).First();
Console.WriteLine($"Default Ascending sort {sortedItem.Date}, {sortedItem.QtyPrice}");
var sortedItemWithReverseDate = items.OrderBy(p => p.QtyPrice).ThenByDescending(p => p.Date).First();
Console.WriteLine($"Descending sort on date {sortedItemWithReverseDate.Date}, {sortedItemWithReverseDate.QtyPrice}");
}
}
class ItemPrice {
public DateTime Date { get; set; }
public decimal QtyPrice { get; set; }
}
It will give you:
Default Ascending sort 16/08/2021 12:47:34, 1
Descending sort on date 17/08/2021 12:47:34, 1
You would need to iterate the collection twice in this case, since you would first need to know the Aggregate Value (Min). Then, you could use a Custom Comparer as the following.
public class CustomComparer : IComparer<Item>
{
private int _minValue;
public CustomComparer(int minValue)
{
_minValue= minValue;
}
public int Compare(Item instanceA, Item instanceB)
{
if(instanceA.Price == _minValue) return -1;
if(instanceB.Price == _minValue) return 1;
return instanceA.InputDate.CompareTo(instanceB.InputDate);
}
}
Now you can fetch the result as
var min = list.Min(x=>x.Price);
var result = list.OrderBy(x=>x,new CustomComparer(min));
Example,
public class Item
{
public int Price{get;set;}
public DateTime InputDate{get;set;}
}
var list = new List<Item>
{
new Item{Price = 2, InputDate=new DateTime(2021,3,1)},
new Item{Price = 12, InputDate=new DateTime(2021,7,1)},
new Item{Price = 12, InputDate=new DateTime(2021,9,1)},
new Item{Price = 42, InputDate=new DateTime(2021,1,1)},
new Item{Price = 32, InputDate=new DateTime(2021,6,1)},
new Item{Price = 22, InputDate=new DateTime(2021,4,1)},
new Item{Price = 2, InputDate=new DateTime(2021,3,2)},
new Item{Price = 12, InputDate=new DateTime(2021,2,1)}
};
var min = list.Min(x=>x.Price);
var result = list.OrderBy(x=>x,new CustomComparer(min));
Output
Thx for all your inputs.
For me the right way to go was.
Order my "itemPrice" list by "OrderByDescending(by date)"
Then find out the lowest value from the List(itemPrice).
double lowest_price = itemPrice.Min(c => c.qtPrice);
Then declare a new List
List<qtInvoice> newItemPrice = new List<qtInvoice>();
First loop that adds all the "lowest_price" to "newItemPrice" list
foreach (var item in itemPriceDate)
{
if (item.qtPrice == lowest_price)
{
newItemPrice.Add(item);
}
}
Then in second loop you add all the rest of the prices to "newItemPrice" list
foreach (var item in itemPriceDate)
{
if (item.qtPrice != lowest_price)
{
newItemPrice.Add(item);
}
}

Accumulate values of a list

I have a list with that each object has two fields:
Date as DateTime
Estimated as double.
I have some values like this:
01/01/2019 2
01/02/2019 3
01/03/2019 4
... and so.
I need to generate another list, same format, but accumulating the Estimated field, date by date. So the result must be:
01/01/2019 2
01/02/2019 5 (2+3)
01/03/2019 9 (5+4) ... and so.
Right now, I'm calculating it in a foreach statement
for (int iI = 0; iI < SData.TotalDays; iI++)
{
DateTime oCurrent = SData.ProjectStart.AddDays(iI);
oRet.Add(new GraphData(oCurrent, GetProperEstimation(oCurrent)));
}
Then, I can execute a Linq Sum for all the dates prior or equal to the current date:
private static double GetProperEstimation(DateTime pDate)
{
return Data.Where(x => x.Date.Date <= pDate.Date).Sum(x => x.Estimated);
}
It works. But the problem is that is ABSLOUTELLY slow, taking more than 1 minute for a 271 element list.
Is there a better way to do this?
Thanks in advance.
You can write a simple LINQ-like extension method that accumulates values. This version is generalized to allow different input and output types:
static class ExtensionMethods
{
public static IEnumerable<TOut> Accumulate<TIn, TOut>(this IEnumerable<TIn> source, Func<TIn,double> getFunction, Func<TIn,double,TOut> createFunction)
{
double accumulator = 0;
foreach (var item in source)
{
accumulator += getFunction(item);
yield return createFunction(item, accumulator);
}
}
}
Example usage:
public static void Main()
{
var list = new List<Foo>
{
new Foo { Date = new DateTime(2018,1,1), Estimated = 1 },
new Foo { Date = new DateTime(2018,1,2), Estimated = 2 },
new Foo { Date = new DateTime(2018,1,3), Estimated = 3 },
new Foo { Date = new DateTime(2018,1,4), Estimated = 4 },
new Foo { Date = new DateTime(2018,1,5), Estimated = 5 }
};
var accumulatedList = list.Accumulate
(
(item) => item.Estimated, //Given an item, get the value to be summed
(item, sum) => new { Item = item, Sum = sum } //Given an item and the sum, create an output element
);
foreach (var item in accumulatedList)
{
Console.WriteLine("{0:yyyy-MM-dd} {1}", item.Item.Date, item.Sum);
}
}
Output:
2018-01-01 1
2018-01-02 3
2018-01-03 6
2018-01-04 10
2018-01-05 15
This approach will only require one iteration over the set so should perform much better than a series of sums.
Link to DotNetFiddle example
This is exactly job of MoreLinq.Scan
var newModels = list.Scan((x, y) => new MyModel(y.Date, x.Estimated + y.Estimated));
New models will have the values you want.
in (x, y), x is the previous item and y is the current item in the enumeration.
Why your query is slow?
because Where will iterate your collection from the beginning every time you call it. so number of operations grow exponentially 1 + 2 + 3 + ... + n = ((n^2)/2 + n/2).
You can try this. Simple yet effective.
var i = 0;
var result = myList.Select(x => new MyObject
{
Date = x.Date,
Estimated = i = i + x.Estimated
}).ToList();
Edit : try in this way
.Select(x => new GraphData(x.Date, i = i + x.Estimated))
I will assume that what you said is real what you need hehehe
Algorithm
Create a list or array of values based in the original values ordered date asc
sumValues=0;
foreach (var x in collection){
sumValues+= x.Estimated; //this will accumulate all the past values and present value
oRet.Add(x.date, sumValues);
}
The first step (order the values) is the most important. For each will be very fast.
see sort

How to count how many times exist each number from int[] inside IEnumerable<int>?

I have array of ints(Call him A) and IEnumarable(Call him B):
B - 1,2,4,8,289
A - 2,2,56,2,4,33,4,1,8,
I need to count how many times exist each number from A inside B and sum the result.
For example:
B - 1,2,4,8,289
A - 2,2,56,2,4,33,4,1,8,
result = 1+3+2+1+0
What is elegant way to implement it?
With LINQ it is easy:
int count = A
.Where(x => B.Contains(x))
.Count();
Counts how many times elements from A are contained in B.
As Yuval Itzchakov points out, this can be simplified like this:
int count = A.Count(x => B.Contains(x));
I need to count how many times exist each number from A inside B and sum the result.
You can get both the count and sum as follows
List<int> b = new List<int>() { 1,2,4,8,289 };
List<int> a = new List<int>() { 2,2,56,2,4,33,4,1,8 };
var subset = a.Where(i => b.Contains(i));
var count = subset.Count(); // 7
var sum = subset.Sum(); // 23
Note that I reuse the same Linq expression to get both the count and the sum.
One might be tempted to use a HashSet<int> in place of a List<int> because the .Contains operation is faster. However, HashSet is a set, meaning if the same number is added multiple times, only one copy of that number will remain in the set.
sweet and simple.. one line solution
why dont you try it..
int sum = 0;
A.ToList().ForEach(a=>sum +=B.Count(b=>b==a));
Console.Write(sum);
you can sweap the A/B it will still work
With Linq you can do like this
var B = new List<int>{ 1, 2, 4, 8, 289 };
var A = new List<int> { 2, 2, 56, 2, 4, 33, 4, 1, 8 };
var repetitionSum = B.Select(b => A.Count(a => a == b)).Sum(); //result = 7
And if you want, you can get the individual repetition list like this
var repetition = B.Select(b => A.Count(a => a == b)).ToList();
// { 1, 3, 2, 1, 0 }
It is not clear if you want to know the occurrences of each number or the final count (your text and your example code differ). Here is the code to get the number of appearances of each number
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
int[] a = new []{1,2,3};
int[] b = new []{1,2,2,3};
Dictionary<int, int> aDictionary = a.ToDictionary(i=>i, i => 0);
foreach(int i in b)
{
if(aDictionary.ContainsKey(i))
{
aDictionary[i]++;
}
}
foreach(KeyValuePair<int, int> kvp in aDictionary)
{
Console.WriteLine(kvp.Key + ":" + kvp.Value);
}
}
}

Finding combinations of list of lists that add up to a target number

I've got an interesting issue I'm trying to solve. My knowledge of Linq is honestly very shallow and I'm pretty certain this is the sort of problem that would be most elegantly solved with a Linq based solution but I've attempted a few things so far with what little knowledge I have to little success.
Here's the skinny: I have a List of decimal Lists and I want to find a combination from the lists adding up to a target decimal using only one element from each list. To clarify:
List<List<decimal>> parentList; // this is the main list I'm drawing from
List<decimal> childList { 1 , 2 , 3 , 4 , 5 }; // each list inside of the main list would look something like this
So if my parentList contains five of the childLists, I need to find a combination that only uses one item each list once. This doesn't mean I can't use the same value twice, if parentList[0] and parentList[1] both contain 3 and I'm adding to 6, {3,3} would be a valid solution. However, if parentList[0] were { 1 , 2 , 3 } and parentList[1] were { 4 }, the only valid solution to add to 6 woudl be {2 , 4}, since the second list doesn't contain 3.
I hope this all makes sense and I'm not asking too much. I don't mind just being oriented in the direction of a solution, a push in the right direction as opposed to the whole answer. Thanks!
As others has already stated, LINQ is not suitable for task like this. Such complex LINQ would not be good both from maintenance and performance perspective.
But I could not stop until I made my inner geek happy! You asked for it...
private static IEnumerable<IEnumerable<decimal>> FindCombinations(IEnumerable<IEnumerable<decimal>> listOfLists, decimal target)
{
return listOfLists.Aggregate(
Enumerable.Repeat(Enumerable.Empty<decimal>(), 1),
(acc, seq) =>
from accseq in acc
from item in seq
select accseq.Concat(new[] {item}))
.Where(x => x.Sum(y => y) == target);
}
And here is console test application:
private static void Main(string[] args)
{
var target = 12;
var listOfLists = new List<List<decimal>>()
{
new List<decimal> { 1, 2, 3 },
new List<decimal> { 3, 4, 5 },
new List<decimal> { 5, 6, 7 },
};
foreach (var combination in FindCombinations(listOfLists, target))
{
Console.WriteLine("{0} = {1}", string.Join(" + ", combination.Select(y => y.ToString(CultureInfo.InvariantCulture))), target);
}
Console.ReadKey();
}
Sounds like something you would solve using recursion and not Linq. Here is an example:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<List<decimal>> listOfLists = new List<List<decimal>>()
{
new List<decimal>() { 1, 2, 3 },
new List<decimal>() { 3, 4, 5 },
new List<decimal>() { 5, 6, 7 },
};
PrintAllCombinationsForTargetValue(listOfLists, 12);
}
private static void PrintAllCombinationsForTargetValue(List<List<decimal>> listOfLists, decimal targetValue)
{
Stack<decimal> currentCombination = new Stack<decimal>();
FindNextElement(listOfLists, targetValue, 0, 0, currentCombination);
}
private static void FindNextElement(List<List<decimal>> listOfLists, decimal targetValue, int listIndex, decimal trackingValue, Stack<decimal> currentCombination)
{
List<decimal> currentList = listOfLists[listIndex];
foreach (decimal currentValue in currentList)
{
decimal currentTrackingValue = trackingValue + currentValue;
currentCombination.Push(currentValue);
if (currentTrackingValue < targetValue && listIndex < listOfLists.Count - 1)
{
// There is still la chance that we can get what we want. Let's go to the next list.
FindNextElement(listOfLists, targetValue, listIndex + 1, currentTrackingValue, currentCombination);
}
else if (currentTrackingValue == targetValue && listIndex == listOfLists.Count - 1)
{
// Found a valid combination!
currentCombination.Reverse().ToList().ForEach(element => Console.Write(element + " "));
Console.WriteLine();
}
currentCombination.Pop();
}
}
}
}
You can achieve this with recursion. This will find one combination that sums up to the target, using one value from each list, or null if none exists.
public static List<decimal> CombinationSumMatches(
this IEnumerable<IEnumerable<decimal>> lists,
decimal target)
{
if (lists.Any())
{
var firstList = lists.First();
if (lists.Skip(1).Any())
{
foreach (var num in firstList)
{
var newTarget = target - num;
var subCombination = lists.Skip(1).CombinationSumMatches(newTarget);
if (subCombination != null)
{
subCombination.Insert(0, num);
return subCombination;
}
}
}
else
{
if (firstList.Contains(target))
{
return new List<decimal> { target };
}
}
}
return null;
}
This will first check if there are any lists. If there are then it looks at the first one and sees if there are more. If there are more it goes through each number of the first list and subtracts that value from the target and does a recursive call on the remaining lists. If there is a non null answer it inserts the number and returns. Now if there is only one list then it just checks the list for the target and returns a list with that target value if it finds it. If there are no lists, or only one without the target, or nothing that matches the sub combinations then it will just return null.

Adding two lists having same structure C#

So here a problem which i am facing -
I have two lists with following structure
public class Payment
{
public int Period { get; set; }
public decimal Balance{ get; set; }
}
I have created following two lists as below
List<Payment> A = new List<Payment>();
List<Payment> B = new List<Payment>();
The list looks like this.
List A List B
Perid Payment Perid Payment
1 10 1 16
2 12 2 13
3 45 3 44
4 23 4 33
5 36 5 34
6 45 6 35
I am trying to add these two Payments from list A,B and create a third list which should have same structure.
List C
Perid Payment
1 10+16
2 12+13
3 45+44
4 23+33
5 36+34
6 45+35
I understand with for looping its possible but is there anyway where Linq OR Lambda expressions can be used in simpler way?
Any help is deeply appreciated.
Try LINQ's Zip method. It helps you to iterate over two collections simultaneously.
Here's an example -
using System;
using System.Linq;
class Program
{
static void Main()
{
// Two source arrays.
var array1 = new int[] { 1, 2, 3, 4, 5 };
var array2 = new int[] { 6, 7, 8, 9, 10 };
// Add elements at each position together.
var zip = array1.Zip(array2, (a, b) => (a + b));
// Look at results.
foreach (var value in zip)
{
Console.WriteLine(value);
}
}
}
I think you shouldn't do it. Write the code in the old-fashioned way is going to be cleared to almost anybody reading the code.
More importantly, the non-LINQ code will allow you to add sanity checks in a reasonable fashion (for example, are you sure all periods in the first list exist in the second? And vice versa?).
If you want to get more modern, I suggest using a generator, something like this:
IEnumerable<Payment> UnitePayments(List<Payment> list1, List<Payment> list2)
{
... Check that list1 and list2 are the same length ...
for(int i=0; i<list1.Length; i++)
{
if(list1.Period!=list2.Period) ... handle this case...
yield return new Payment { Period = list1.Period,
Balance = list1.Balance + list2.Balance };
}
}
Your code readers will thank you.
You have two options as already suggested:-
Using Concat + GroupBy :-
List<Payment> result = A.Concat(B).GroupBy(x => x.Period)
.Select(x => new Payment
{
Period = x.Key,
Balance = x.Sum(z => z.Balance)
}).ToList();
Using Zip :-
List<Payment> result1 = A.Zip(B, (first, second) => new Payment
{
Period = first.Period,
Balance = first.Balance + second.Balance
}).ToList();
You can refer to this Fiddle.
// Try for loop i think it would be good way to handle this situation there is other LINQ queries but i believe this is easier..
List<int> a = new List<int>();
a.Add(1 ) ;
a.Add(2);
List<int> b = new List<int>();
b.Add(5) ;
b.Add(6);
List<int> c = new List<int>();
for (int x = 0; x < a.Count; x++)
{
c.Add(a[x] + b[x]);
Label1.Text += c[x] + "";
}

Categories

Resources