I am trying to figure out the best way to calculate a running sum partition with a self joined collection using LINQ.
The query below is a somewhat simple example of what I am after. The output is the RowNumber, the RowType and the sum of all preceding RowValues within the current row's RowType.
DECLARE #T TABLE (RowNumber INT, RowType INT, RowValue INT)
INSERT #T VALUES (1,1,1),(2,1,1),(3,1,1),(4,1,1),(5,1,1),(6,2,1),(7,2,1),(8,2,1),(9,2,1),(10,2,1)
;WITH Data AS(SELECT RowNumber, RowType,RowValue FROM #T)
SELECT
This.RowNumber,
This.RowType,
RunningValue = COALESCE(This.RowValue + SUM(Prior.RowValue),This.RowValue)
FROM
Data This
LEFT OUTER JOIN Data Prior ON Prior.RowNumber < This.RowNumber AND Prior.RowType = This.RowType
GROUP BY
This.RowNumber,
This.RowType,
This.RowValue
/* OR
SELECT
This.RowNumber,
This.RowType,
RunningValue = SUM(RowValue) OVER (PARTITION BY RowType ORDER BY RowNUmber)
FROM
Data This
*/
Now, my not working attempt.
var joinedWithPreviousSums = allRows.Join(
allRows,
previousRows => new {previousRows.RowNumber, previousRows.RowType, previousRows.RowValue},
row=> new { row.RowNumber, row.RowType, row.RowValue},
(previousRows, row) => new { row.RowNumber, row.RowType, row.RowValue })
.Where(previousRows.RowType == row.RowType && previousRows.RowNumber < row.RowNumber)
.Select(row.RowNumber, row.RowType,RunningValue = Sum(previousRows.Value) + row.RowValue)).ToList()
Of course, the last two lines above are garbage and attempt to exemplify my desired projection while hinting at my lack of knowledge on performant complex LINQ projections.
I have read where some variation of the statement below could work and may be workable, however, is there a way to achieve similar results without yielding?
int s = 0;
var subgroup = people.OrderBy(x => x.Amount)
.TakeWhile(x => (s += x.Amount) < 1000)
.ToList();
EDIT : I have been able to get the snippet below to work, however, I cant seem to partition or project over RowType.
namespace ConsoleApplication1
{
class Program
{
delegate string CreateGroupingDelegate(int i);
static void Main(string[] args)
{
List <TestClass> list = new List<TestClass>()
{
new TestClass(1, 1, 1),
new TestClass(2, 2, 5),
new TestClass(3, 1, 1 ),
new TestClass(4, 2, 5),
new TestClass(5, 1, 1),
new TestClass(6, 2, 5)
};
int running_total = 0;
var result_set = list.Select(x => new { x.RowNumber, x.RowType, running_total = (running_total = running_total + x.RowValue) }).ToList();
foreach (var v in result_set)
{
Console.WriteLine("list element: {0}, total so far: {1}",
v.RowNumber,
v.running_total);
}
Console.ReadLine();
}
}
public class TestClass
{
public TestClass(int rowNumber, int rowType, int rowValue)
{
RowNumber = rowNumber;
RowType = rowType;
RowValue = rowValue;
}
public int RowNumber { get; set; }
public int RowType { get; set; }
public int RowValue { get; set; }
}
}
Your answer can be simplified greatly, but does scale poorly even then, as it must go through the Where for each row to compute each row, so O(list.Count^2).
Here is the simpler version, which preserves the original order:
var result = list.Select(item => new {
RowType = item.RowType,
RowValue = list.Where(prior => prior.RowNumber <= item.RowNumber && prior.RowType == item.RowType).Sum(prior => prior.RowValue)
});
You can go through list once if are willing to sort. (If you know the order is correct, or can use a simpler sort, you can remove or replace the OrderBy/ThenBy.)
var ans = list.OrderBy(x => x.RowType)
.ThenBy(x => x.RowNumber)
.Scan(first => new { first.RowType, first.RowValue },
(res, cur) => res.RowType == cur.RowType ? new { res.RowType, RowValue = res.RowValue + cur.RowValue }
: new { cur.RowType, cur.RowValue }
);
This answer uses an extension method that is like Aggregate, but returns the intermediate results, based on the APL scan operator:
// TRes seedFn(T FirstValue)
// TRes combineFn(TRes PrevResult, T CurValue)
public static IEnumerable<TRes> Scan<T, TRes>(this IEnumerable<T> src, Func<T, TRes> seedFn, Func<TRes, T, TRes> combineFn) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var prev = seedFn(srce.Current);
while (srce.MoveNext()) {
yield return prev;
prev = combineFn(prev, srce.Current);
}
yield return prev;
}
}
}
My eyes were glazed over after seeing this. The answer to my long winded question after 6 hours of skulldrugery seems to be as simple as this. Thanks to #NetMage for pointing out the SelectMany that I was missing.
var result = list.SelectMany(item => list.Where(x => x.RowNumber <= item.RowNumber && x.RowType == item.RowType)
.GroupBy(g => g.RowType)
.Select(p => new
{
RowType = p.Max(s => s.RowType),
RowValue = p.Sum(s => s.RowValue)
}));
I have a class
public class ReceiptDisplayInfo
{
public string ReceiptItemFor{get;set;}
public string ReceiptItemCategory{get;set;}
public string ReceiptItemReference{get;set;}
public string ReceiptRowCategory{get;set;}
public string ReceiptAmount{get;set;}
}
I have a list
List<List<ReceiptDisplayInfo>> dataSourceToBind ;
My requirement : For every List , if ReceiptRowCategory="Payment" , I have to set the value of ReceiptItemForm,ReceiptItemCategory to blank or null in dataSourceToBind .
I am doing using for loop but this is not the most appreciated approach.
Please assist me in doing using LINQ/Lambda Expression.
dataSourceToBind.ForEach(x =>
{
var innerList = x;
innerList.ForEach(y =>
{
if (y.ReceiptRowCategory == "Payment")
{
y.ReceiptItemFor = null;
y.ReceiptItemCategory = null;
}
});
});
I guess just 2 ForEach calls would suffice, no need to use LINQ here. However, since the transformation logic is quite complicated, I think you should extract it as a method:
private void SomeMethod(ReceiptDisplayInfo info) { // please name this appropriately
if (info.ReceiptRowCategory == "Payment") {
info.ReceiptItemForm = null;
info.ReceiptItemCategory = null;
}
}
And then,
dataSourceToBind.ForEach(x => x.ForEach(SomeMethod));
You can use below code to achieve this-
((from l in list
where l.ReceiptItemCategory == "payment"
select new ReceiptDisplayInfo()
{
ReceiptItemFor = null,
ReceiptItemCategory = null,
ReceiptItemReference = l.ReceiptItemReference,
ReceiptRowCategory = l.ReceiptRowCategory,
ReceiptAmount = l.ReceiptAmount
}).Union(from l in list
where l.ReceiptItemCategory != "payment"
select l)).ToList();
I have the following query that pulls the data I need perfectly fine.
var subFuncName = from a in m_dcSQL_ConnectionProdTest.DC3_SubFunctions
where a.VersionIndex == versionIndex && stepDistinct.Select(b => b.Step).Contains(a.FunctionNumber) && stepDistinct.Select(c => c.LogID).Contains(a.SubFunctionNumber)
select new
{
a.FunctionNumber,
a.SubFunctionNumber,
a.SubFunctionName,
};
Then I want to add some data to a list.
foreach (var item in stepDistinct)
{
lstPareto.Add(new clsPareto(Convert.ToInt32(item.Step), Convert.ToInt32(item.LogID),
stepLogID.Where(p => p.Step.Equals(item.Step) && p.LogID.Equals(item.LogID)).Count(),
subFuncName.Where(x => x.FunctionNumber.Equals(item.Step) && x.SubFunctionNumber.Equals(item.LogID)).Select(x => x.SubFunctionName).ToString())); --THIS LINE IS THE PROBLEM--
}
My clsPareto class:
public class clsPareto
{
public int intStep { get; set; }
public int intLogID { get; set; }
public int iCount { get; set; }
public string strFuncName { get; set; }
public clsPareto(int ParetoStep, int ParetoLogID, int Count, string FuncName)
{
intStep = ParetoStep;
intLogID = ParetoLogID;
iCount = Count;
strFuncName = FuncName;
}
}
What I am trying to do, is to pull each SubFunctionName from subFuncName where FunctionNumber = Step and SubFunctionNumber = LogID. However, when I bind it to my datagrid, the column meant to show the names just shows the SQL Query String instead and doesn't actually take the elements I want. I thought my .Select(x => x.SubFunctionName) would do the trick, but apparently it doesn't. Still pretty new to using LINQ and C#, so how would I go about doing this?
The linq for the problem line is still an expression - Select() returns an IEnumerable not the value - and then you are doing a ToString() on it. This is why you just get SQL back.
You need to resolve the expression and get an actual object out of it. Adding Single() to get the FuncName should do it. You may also not need to convert to string if FuncName already is:
subFuncName.Where(x => x.FunctionNumber.Equals(item.Step) && x.SubFunctionNumber.Equals(item.LogID))
.Select(x => x.SubFunctionName).Single().ToString()));
This solution will throw an exception if there is no matching element in the collection.
subFuncName.Where(x => x.FunctionNumber.Equals(item.Step) && x.SubFunctionNumber.Equals(item.LogID))
.Select(x => x.SubFunctionName).Single().ToString()));
Could use;
var subFuncName = subFuncName.Where(x => x.FunctionNumber.Equals(item.Step) && x.SubFunctionNumber.Equals(item.LogID))
.Select(x => x.SubFunctionName).FirstOrDefault());
if(subFuncName != null)
// Add it
else
subFuncName == "UNDEFINED";
and handle the case if subFuncName is null yourself.
I have made a method in my Models class to call set of data from data base by below codes:
public IQueryable result(string username, string exam)
{
return (from result in idb.User_Exam_Question
where (result.User_Tbl_email == username && result.Exam_Tbl_ID == Convert.ToInt32(exam))
group result by result.category_tbl_ID into cat
select cat);
}
but I need to count the rows of each category . how should I change the above code to get my desirable output .
many thanks
If you want to return the count, just add .Count():
public int result(string username, string exam)
{
return (from result in idb.User_Exam_Question
where (result.User_Tbl_email == username && result.Exam_Tbl_ID == Convert.ToInt32(exam))
group result by result.category_tbl_ID into cat
select cat).Count();
}
Or, if you want each sub-result as a count, you can do this:
public IQueryable result(string username, string exam)
{
return from result in idb.User_Exam_Question
where (result.User_Tbl_email == username && result.Exam_Tbl_ID == Convert.ToInt32(exam))
group result by result.category_tbl_ID into cat
select new
{
CatCount = cat.Count()
}
}
You can then do something like this:
result("username", "exam")[0].CatCount;
// returns the count of the first result of the query.
You can get the count directly from your cat !
public IQueryable result(string username, string exam)
{
return (from result in idb.User_Exam_Question
where (result.User_Tbl_email == username && result.Exam_Tbl_ID == Convert.ToInt32(exam))
group result by result.category_tbl_ID into cat
select new { Group = cat , Count = cat.Count() } );
}
Try to find the solution but i cant.
So problem is next one. I have the EDM model of database. I have a class with functions to get data from DB.
Like this:
public IQueryable<photos> FindUserPhotos(string userlogin)
{
return from m in db.photos
where m.userlogin == userlogin
select m;
}
How to get the Random 10 lines from DB?
I always use this method for get custom entity OrderBy(x => Guid.NewGuid())
public photos Find10RandomUserPhotos(string userlogin)
{
return db.photos.Where(x => x.userlogin == userlogin).OrderBy(x => Guid.NewGuid()).Take(10).ToList();
}
Following Random row from Linq to Sql
public photos FindRandomUserPhoto(string userlogin)
{
var qry = FindUserPhotos(userlogin);
int count = qry.Count();
int index = new Random().Next(count);
return qry.Skip(index).FirstOrDefault();
}
public Array<photos> Find10RandomUserPhotos(string userlogin)
{
var result = New Array<photos>;
for (i = 0; i < 10; i++) {
result.add(FindRandomUserPhoto(userlogin));
}
return result
}