C# LINQ select until amount >= 0 - c#

This is example database table
Name | Quantanity
Book I | 1
Book II | 13
Book III | 5
etc...
And I want to select this rows until I will have 100 books usinq LINQ expression.
I was trying
.TakeWhile(x => (amount -= x.Quantanity) > 0);
But it gave me an error
"Expression tree cannot contain assignment operator"

int bookCount = 0;
var query = books
.OrderBy(b => b.Quantity) // to get count 100, otherwise exceed is likely
.AsEnumerable()
.Select(b => {
bookCount += b.Quantanity;
return new { Book = b, RunningCount = bookCount };
})
.TakeWhile(x => x.RunningCount <= 100)
.Select(x => x.Book);

Tim's solution is good, but note about it --- Only the part before the AsEnumerable() is being executed by the data server -- Basically, you are pulling the entire table into memory, and then processes it.
Let's see if we can improve that:
int bookCount = 0;
var query1 = (from b in books
where b.Quantity > 0 && b. Quantity <= 100
orderby b.Quantity
select b).Take(100).AsEnumerable();
var query = query1
.Select(b => {
bookCount += b.Quantity;
return new { Book = b, RunningCount = bookCount };
})
.TakeWhile(x => x.RunningCount <= 100)
.Select(x => x.Book);
This limits us to only 100 records in memory to look thru to get to a count of 100.

Related

Remove rows and adjust number

I am trying to remove objects from a list where a certain property value are identical to the previous/next objects property value. If an object are found I need to update the nested objects value.
Example:
Level | Text
1 | General
2 | Equipment
3 | Field Staff
2 | Scheduling
3 | Scheduling
4 | Deadlines
4 | Windows
1 | Specialities
In the example above I want to remove the second Scheduling and change the Deadlines Level to 3 as well as the Windows to 3.
I tried to look a head and compare with the next object in the list and also keep a counter but it didnt work.
int counter = 0;
for (int i = 0; i < notes.Count(); i++)
{
if (i <= notes.Count() - 1)
{
var currentRow = notes.ElementAt(i);
var nextRow = notes.ElementAt(i + 1);
if (currentRow.Text.Equals(nextRow.Text))
{
notes.Remove(nextRow);
counter++;
}
else
{
notes.ElementAt(i).Level = notes.ElementAt(i).Level - counter;
counter = 0;
}
}
}
Could anyone point me in the correct direction?
You can do it with Linq:
1 - Get distinct lines
var distinctList = notes
.GroupBy(p => p.Text)
.Select(v => v.First());
2 - get deleted level
IEnumerable<int> deletedLevel = notes
.Except(distinctList)
.Select(l => l.Level);
3 - update your distinct list
foreach(int level in deletedLevel)
{
distinctList
.Where(l => l.Level >= level + 1)
.ToList()
.ForEach(item => { item.Level -= 1; });
}
Result :
Level | Text
1 | General
2 | Equipment
3 | Field Staff
2 | Scheduling
3 | Deadlines
3 | Windows
1 | Specialities
i hope that will help you out
Try this:
var query = notesList.GroupBy(x => x.Text)
.Where(g => g.Count() > 1)
.Select(y => y.Key)
.Select(y => new { Element = y, Index = Array.FindIndex<Notes>(notesList.ToArray(), t => t.Text ==y) })
.ToList();
var filteredList = new List<Notes>();
foreach (var duplicate in query)
{
filteredList = notesList.Where((n, index) => index < duplicate.Index + 1).ToList();
var newElems = notesList.Where((n, index) => index > duplicate.Index + 1).Select(t =>
new Notes {Level = t.Level == 1 ? 1 : t.Level - 1, Text = t.Text});
filteredList.AddRange(newElems);
}

Linq with sum and group from two tables

Hi I have some two tables,
Product:
ProductID | IssueDate | Amount
1 2017-06-01 1000
2 2017-06-01 1000
3 2017-06-02 500
and Credit:
ProductID | Amount
1 500
1 500
2 1000
3 500
if I use query like this from SQL Server:
SELECT p.IssueDate, SUM(p.Amount), SUM(p.Total)
FROM (SELECT p.IssueDate, SUM(p.Amount) AS Amount,
(SELECT TOP 1 SUM(c.Amount) FROM Credit c WHERE p.Id = c.ProductId) AS Total from Product p
GROUP BY p.IssueDate, p.Id) p
GROUP BY p.IssueDate
I get this result:
IssueDate | Amount | Total
2017-06-01 2000 2000
2017-06-02 500 500
In C# Linq I can get this data by two queries like this:
var data = from p in Products.Collection
select new
{
Product = p,
Credits = Credit.Collection.Where(c => c.ProductID == p.ID).Sum(c => c.Amount)
};
var result = from d in data
group d by new
{
IssueDate = d.Product.IssueDate
} into gr
select new
{
IssueDate = gr.Key.IssueDate,
Credits = gr.Sum(s => s.Credits),
Total = gr.Sum(s => s.Product.Amount)
};
var test = result.ToList();
Does anyone know a better (simpler) solution to get this result? Maybe in one query?
How about:
Products.Collection.GroupBy(x => x.IssueDate)
.Select(x => new
{
IssueDate = x.Key,
Amount = x.Sum(p => p.Amount),
Total = Credit.Collection.Where(c => x.Any(p => p.ProductID == c.ProductID)).Sum(c => c.Amount)
}).ToList();

LINQ to SQL: Group, Count, Sum. I'm so confused

Good morning all,
I have been stuck on this all morning and feel like I've hit a wall. I'd love any advice that can be given at this point.
My table is basically as follows:
PatientName|LivingSpace
-----------|-----------
Patient 1 | Unit 1
Patient 2 | Unit 1
Patient 3 | Unit 2
Patient 4 | Unit 2
Patient 5 | Unit 3
Patient 6 | Unit 3
Patient 7 | Unit 3
Patient 8 | Unit 3
I need a LINQ to SQL query to illustrate this:
Unit|Count
----|-----
Unit 1 | 2
Unit 2 | 2
Unit 3 | 4
TOTAL | 8
My SQL query works fine, I'm just having issues with converting it to LINQ:
SELECT LivingSpace, COUNT(LivingSpace) AS LivingSpace
FROM PatientTable
WHERE Status = 'Active'
GROUP BY LivingSpace
UNION ALL
SELECT 'SUM' LivingSpace, COUNT(LivingSpace)
FROM PatientTable
var counts = from x in ctx.PatientTable
group x by x.LivingSpace into y
select new { Key = y.Key Count = y.Count() };
var total = new { Key = "Total" , Count = ctx.PatientTable.Count() };
var full = counts.ToList();
full.Add(total);
If you want to do it all in one query the following should work (adjusting for the actual names of your properties of course).
context.PatientTable.GroupBy(a => a.LivingSpace.Name, a => 1)
.Select(a => new
{
a.Key,
Total = a.Sum(q => q)
})
.Union(PatientTable.Select(a => new
{
Key = "Total",
Total = PatientTable.Count()
}))
var report = patients
.GroupBy(p => p.LivingSpace)
.Select(g => new
{
Unit = g.Key,
Count = g.Count()
})
.Union(patients
.Select(p => new
{
Unit = "Total",
Count = patients.Count
}));
Something like this should work and just run one query.
var results = db.PatientTable
.GroupBy(p => p.LivingSpace)
.Select(grp => new
{
Unit = grp.Key,
Count = grp.Count()
})
.Union(db.PatientTable
.GroupBy(p => 1)
.Select(grp => new
{
Unit = "Total",
Count = grp.Count()
}));
I see you got the answer, but for learning purposes, here is side by side conversion.
Your SQL (with some aliases added for better comparison)
SELECT P.LivingSpace, COUNT(P.*) AS Count
FROM PatientTable AS P
WHERE P.Status = 'Active'
GROUP BY P.LivingSpace
UNION ALL
SELECT 'SUM' AS LivingSpace, COUNT(P.*) AS Count
FROM PatientTable AS P
The same single query in LINQ
var query =
(
from p in db.PatientTable
where p.Status = "Active"
group p by p.LivingSpace into g
select new { LivingSpace = g.Key, Count = g.Count() }
)
.Concat
(
from p in db.PatientTable
group p by "SUM" into g
select new { LivingSpace = g.Key, Count = g.Count() }
);

Aggregate LINQ function to take place of this "accumalator" function

So I have this function:
var ordersInLast7Days = lineItems
.Where(x => x.OrderLogged > DateTime.Now.AddDays(-7))
.ToList();
ordersInLast7Days.ForEach(lineItem =>
{
var qty = lineItem.Quantity;
var pack = packs.FirstOrDefault(x => x.Id.Equals(lineItem.PackId));
if (pack != null)
{
orderTotalsInLast7Days += qty * pack.Price;
}
});
How would I make that into an Aggregate LINQ function that collects qty * pack.Price?
To elaborate somewhat, I have 3 of these functions that are all the same, so just guna have one method to apply to all Aggregates.
I agree with dasblinkenlight but to provide a solution using aggregate:
var total = lineItems.Where(x => x.OrderLogged > DateTime.Now.AddDays(-7) && packs.Any(y => y.Id.Equals(x.PackId)))
.Aggregate(0, (res, item) => res += item.Quantity * packs.First(y => y.Id.Equals(item.PackId)).Price);
You can use Sum to accumulate the total of qty * pack.Price, like this:
orderTotalsInLast7Days = ordersInLast7Days
.Select(lineItem => new {
qty = lineItem.Quantity
, pack = packs.FirstOrDefault(x => x.Id.Equals(lineItem.PackId))
})
.Where(p => p.pack != null)
.Sum(p => p.qty * p.pack.Price);
This is a straightforward translation of your iterative code, which uses an anonymous type instead of local variables.
You could do it this way:
orderTotalsInLast7Days =
(
from lineItem in lineItems
where lineItem.OrderLogged > DateTime.Now.AddDays(-7)
let qty = lineItem.Quantity
from pack in packs.Where(x => x.Id.Equals(lineItem.PackId)).Take(1)
select qty * pack.Price
).Sum();

How can I calculate an integral?

How to build an "integral" function from a Linq expression that returns a series of (time|value) pairs?
E.g. my Linq expression results in a list of (date|quantity) pairs:
var quantities = db.Stocks.Find(id).Trades.Select(
x => new
{
date = x.Price.Date.Subtract(unix).TotalMilliseconds,
value = x.Quantity
}).ToList();
What would be an efficient way to get a series of (date | cumulated quantity) where each record has a total of all quantities up to that point, plus the current quantity?
EDIT1 - to make it concrete: what above query returns could be
0: (01.01.2012 | 10)
1: (01.01.2013 | -5)
2: (01.01.2014 | 7)
What I'm looking for is to have the cumulated value at each discrete point of time:
0: (01.01.2012 | 10)
1: (01.01.2013 | 5)
2: (01.01.2014 | 12)
EDIT2 - A solution might be to assemble the result 'manually' and to chain each quantity to its predecessor's quantity:
var cumulatedQuantities = new List<Tuple<double, double>>();
cumulatedQuantities.Add(new Tuple<double, double>(quantities[0].date, quantities[0].value));
for (var i = 1; i <= quantities.Count - 1; i++ )
{
cumulatedQuantities.Add(new Tuple<double, double>(quantities[i].date, quantities[i].value + cumulatedQuantities[i-1].Item2));
}
Somehow, that feels pretty ugly .. but it works.
Group by date, then total the quantity in each group:
var quantities
= db.Stocks.Find(id).Trades
.GroupBy(x => x.Price.Date)
.Select(x => new
{
date = x.Key.Subtract(unix).TotalMilliseconds,
value = x.Sum(y => y.Quantity)
})
.ToList();
You could also store it in a dictionary:
var quantities
= db.Stocks.Find(id).Trades
.GroupBy(x => x.Price.Date)
.ToDictionary(x => x.Key, x => x.Sum(y => y.Quantity));
Given your clarification in your question, this will work:
var totalQty = 0;
var cumulatedQuantities =
db.Stocks.Find(id).Trades
.Select(t => new KeyValuePair<int, int>(t.Id, totalQty += t.Quantity)).ToList();
Looks like a job for Aggregate
db.Stocks.Find(id).Trades
.Aggregate(new List<Tuple<double,double>>(),
(result, next) => {
var total = (result.Any() ? result.Last().Item2 : 0) + next.value;
result.Add(Tuple.Create(next.date, total));
return result;
});
You may want to vary this a little, like using anonymous objects, a specific class, or a Dictionary to store your results, but this is the basic pattern.
If Trades isn't already ordered by date then you should do that too, by adding an .OrderBy(t => t.date) before the Aggregate

Categories