How to extract a specific value from the linq query? - c#

I am building a NET 5 API and am unable to extract and calculate something. I have a table StockTransaction which among other has property Quantity (I skipped some properties for brevity):
public class StockTransaction : BaseEntity
{
public int Id { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public bool Purchase { get; set; }
public int Resolved { get; set; }
}
Suppose I have 7 transactions, all of which are purchase:
List<StockTransaction> list = new List<StockTransaction>
{
new StockTransaction {Id = 1, Purchase = true, Quantity = 10, Resolved = 5, Price = 50},
new StockTransaction {Id = 2, Purchase = true, Quantity = 5, Resolved = 5, Price = 70},
new StockTransaction {Id = 3, Purchase = true, Quantity = 8, Resolved = 8, Price = 25},
new StockTransaction {Id = 4, Purchase = true, Quantity = 7, Resolved = 5, Price = 77},
new StockTransaction {Id = 5, Purchase = true, Quantity = 1, Resolved = 1, Price = 23},
new StockTransaction {Id = 6, Purchase = true, Quantity = 3, Resolved = 0, Price = 14},
new StockTransaction {Id = 7, Purchase = true, Quantity = 2, Resolved = 0, Price = 17},
};
And I would like to get the value of the last 7 quantities, which in this case gives 176 ((2 x 17) + (3 x 14) + (1 x 23) + (1 x 77)). (How) can this be done? Every help and hint is more then appreciated...

You can use
var last3=list.OrderByDescending(x=>x.CreatedDate).Take(3).Select(x=>x.Quantity * x.Price).Sum();
var requiredSum=last3+list.Where(x=>x.id==4).Select(x=>x.Price).FirstOrDefault();

Although you have tagged your question with LINQ, do it with a normal loop:
private static decimal SumOfLastTransaction(IEnumerable<StockTransaction> stockTransactions, int max)
{
decimal result = 0;
int sum = 0;
foreach(var stockTransaction in stockTransactions.OrderByDescending(x => x.Id))
{
if(sum + stockTransaction.Quantity <= max)
{
result += stockTransaction.Quantity * stockTransaction.Price;
sum += stockTransaction.Quantity;
}
else
{
result += (max - sum) * stockTransaction.Price;
return result;
}
}
return result;
}
You loop over the last items in your list. You sum the amount of transactions and check whether it is smaller than your maximum. As long as they are smaller, you can add the amount of transactions. If not, you check how much is missing until your maximum is reached and substract this from your quantity.
Online demo: https://dotnetfiddle.net/9oM4pj

Related

LINQ to Object - How to implement dynamic SELECT projection of sub elements

I have several classes of business logic:
public class Client {
public string Code { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string Account { get; set; } = string.Empty;
public Total Total { get; set; } = new Total();
public List<Month> Months { get; set; } = new List<Month>();
}
public class Month {
public int Number { get; set; } = 0;
public string Name { get; set; } = string.Empty;
public DateTime Start { get; set; } = new DateTime();
public DateTime End { get; set; } = new DateTime();
public Total Summary { get; set; } = new Total();
}
public class Total {
public int Count { get; set; } = 0;
public decimal Sum { get; set; } = 0.0m;
}
which are instanced as follows:
List<Client> clients = new List<Client>() {
new Client {
Code = "7002.70020604",
Status = "Active",
Account = "7002.915940702810005800001093",
Total = new Total {
Count = 9,
Sum = 172536.45m
},
Months = new List<Month>() {
new Month {
Number = 0,
Name = "January",
Start = new DateTime(2021, 1, 1, 0, 0, 0),
End = new DateTime(2021, 1, 31, 23, 59, 59),
Summary = new Total {
Count = 6,
Sum = 17494.50m
}
},
new Month {
Number = 1,
Name = "February",
Start = new DateTime(2021, 2, 1, 0, 0, 0),
End = new DateTime(2021, 2, 28, 23, 59, 59),
Summary = new Total {
Count = 3,
Sum = 155041.95m
}
},
new Month {
Number = 2,
Name = "March",
Start = new DateTime(2021, 3, 1, 0, 0, 0),
End = new DateTime(2021, 3, 31, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
}
}
},
new Client {
Code = "7002.70020604",
Status = "Active",
Account = "7002.800540702810205800001093",
Total = new Total {
Count = 4,
Sum = 16711.21m
},
Months = new List<Month>() {
new Month {
Number = 0,
Name = "January",
Start = new DateTime(2021, 1, 1, 0, 0, 0),
End = new DateTime(2021, 1, 31, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
},
new Month {
Number = 1,
Name = "February",
Start = new DateTime(2021, 2, 1, 0, 0, 0),
End = new DateTime(2021, 2, 28, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
},
new Month {
Number = 2,
Name = "March",
Start = new DateTime(2021, 3, 1, 0, 0, 0),
End = new DateTime(2021, 3, 31, 23, 59, 59),
Summary = new Total {
Count = 4,
Sum = 16711.21m
}
}
}
}
};
I'm trying to arrange aggregate data of a view like this:
+---------------+--------+------------------+-------------------+------------------+-------------------+
| Code | Status | January | February | March | Total |
| | +-------+----------+-------+-----------+-------+----------+-------+-----------+
| | | Count | Sum | Count | Sum | Count | Sum | Count | Sum |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
| 7002.70020604 | Active | 6 | 17494.50 | 3 | 155041.95 | 4 | 16711.21 | 13 | 189247.66 |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
using projection like this:
clients
.GroupBy(x => x.Code)
.Select(y => new {
Code = y.First().Code,
Status = y.First().Status,
Account = y.First().Account,
Total = new {
Count = y.Sum(z => z.Total.Count),
Sum = y.Sum(z => z.Total.Sum)
},
Months = new {
/*
?
*/
}
});
But I can't project the data by month. Assuming the date range (months) can be more than just this example. Please help!
Full interactive code listing at dotnetfiddle
You can use SelectMany to get months out of y and then group by month similarly as you group by code:
//...
Months = y
.SelectMany(client => client.Months)
.GroupBy(month => month.Name, (_, months) => new {
Number = months.First().Number,
Name = months.First().Name,
Start = months.First().Start,
End = months.First().End,
Summary = new {
Count = months.Sum(z => z.Summary.Count),
Sum = months.Sum(z => z.Summary.Sum)
}
}).ToList()
//...
That being said I don't suggest to use y.First() or months.First() more than once in each function because it makes an enumeration each time it is used. The following should in general have better performance:
(_, months) => {
var month = months.First();
return new {
Number = month.Number,
Name = month.Name,
Start = month.Start,
End = month.End,
Summary = new {
Count = months.Sum(z => z.Summary.Count),
Sum = months.Sum(z => z.Summary.Sum)
}
}
}
which is also not ideal because we're still making 3 enumerations here (1 enumeration in .First() and 1 enumeration for every .Sum(...)).
Even better approach would be to use Aggregate function which will do only a single enumeration:
(_, months) => months
.Aggregate((res, nextVal) => new Month {
Number = nextVal.Number,
Name = nextVal.Name,
Start = nextVal.Start,
End = nextVal.End,
Summary = new Total {
Count = res.Summary.Count + nextVal.Summary.Count,
Sum = res.Summary.Sum + nextVal.Summary.Sum
}
})
This LINQ query should prepare data for visualization:
clients
.GroupBy(x => new {x.Code, x.Status})
.Select(g => new
{
Code = g.Key
MonthsSummary = g.SelectMany(x => x.Months)
.OrderBy(x => x.Start)
.GroupBy(x => new {x.Start, x.Name})
.Select(gm => new
{
gm.Key.Name,
Count = gm.Sum(x => x.Summary.Count),
Sum = gm.Sum(x => x.Summary.Sum),
})
.ToList()
});

How to programmatically determine tab index of fields on a form based on their X,Y coordinates?

I need to figure out the proper tabbing order of HTML form fields based on their absolute X, Y coordinates on the page. We use X, Y from the bottom-left corner of the div (page container) in which they are placed.
For example, in the image below, the numbers inside the boxes indicate the final tabIndex order I would expect as the result should the boxes overlap at all on the Y axis; the lowest X axis value would win and the Y axis wouldn't matter at all. If there's no overlap, then the highest Y axis value wins.
Context
Basically when filling out a PDF form, the natural tab index should be left to right even if box #3 is a little higher than box #2; you'd still want to fill it out left to right. However, since box #1 is on a completely different X plane than the other boxes (regardless if it's further right than the rest) it should still logically come before the other boxes when filling out a form. You wouldn't go across and then up.
The fields are in a C# object with X and Y properties. (pseudocode below)
var fields = new List<TestFieldModel>()
{
new TestFieldModel()
{
ExpectedOrderNumberResult = 3,
PageNumber = 1,
X = 7,
Y = 6,
Width = 5,
Height = 2
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 4,
PageNumber = 1,
X = 14,
Y = 4,
Width = 5,
Height = 3
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 1,
PageNumber = 1,
X = 17,
Y = 9,
Width = 5,
Height = 3
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 2,
PageNumber = 1,
X = 2,
Y = 5,
Width = 4,
Height = 2
}
};
My answer is very similar to yours. The main differences are
not using a boolean to break out of two loops.
In the "double break" scenario I prefer the maligned goto. It's subjective, but for me, is clearer because it avoids a check in the outer loop.
replacing the if statements on recalculating group bounds with Math.Min/Max. This shorthand expresses your intent more clearly.
calling Group.Add() for every field; there is no need to initialize the list of fields in a group differently if you have an empty list to being with
Psuedo C#:
foreach (var f in fields)
{
foreach (var g in groups)
{
if (g.VerticallyOverlapsWith(f))
{
g.Add(f);
goto NEXT_FIELD;
}
}
// no overlap detected, so make a new group
var newGroup = new Group();
newGroup.Add(f);
groups.Add(newGroup)
NEXT_FIELD :;
}
class Group
{
void AddField(Field f)
{
_group.Add(f);
_yTop = Max(f.Top, _yTop);
_yBottom = Min(f.Bottom, _yBottom);
}
List<Field> _group = new List<Field>();
int _yTop = int.MinValue();
int _yBottom = int.MaxValue();
}
At this point you have your groups. You now have to sort groups descending then by fields ascending (which you have done).
A couple of design points.
this does not address fields that overlap with fields in multiple groups, causing unnecessarily large groups. If you get into that, your tabbing order could get a bit unexpected (from the user's point of view). If you expect these
weird overlaps you'd be better off using a clustering algorithm or a "tolerance" function rather than a simple "overlap with first overlapping group" function (VerticallyOverlapsWith does the latter).
you can avoid the sort steps at the end by making the adds insert into an ordered collection in each case.
Ok this is how I was able to solve this:
[Fact]
public void GetFieldsOrderTest()
{
var fields = new List<TestFieldModel>()
{
new TestFieldModel()
{
ExpectedOrderNumberResult = 3,
PageNumber = 1,
X = 7,
Y = 6,
Width = 5,
Height = 2
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 4,
PageNumber = 1,
X = 14,
Y = 4,
Width = 5,
Height = 3
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 1,
PageNumber = 1,
X = 17,
Y = 9,
Width = 5,
Height = 3
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 2,
PageNumber = 1,
X = 2,
Y = 5,
Width = 4,
Height = 2
}
};
var pageCount = 1;
for (var i = 1; i <= pageCount; i++)
{
var groups = new List<Group>();
foreach (var field in fields.Where(f => f.PageNumber == i))
{
var needsNewGroup = true;
foreach (var group in groups)
{
var fieldTop = field.Y + field.Height;
var fieldBottom = field.Y;
if ((fieldTop <= group.Top && fieldTop >= group.Bottom) || (fieldBottom >= group.Bottom && fieldBottom <= group.Top))
{
if (fieldTop > group.Top)
{
group.Top = fieldTop;
}
if (fieldBottom < group.Bottom)
{
group.Bottom = fieldBottom;
}
group.GroupFields.Add(field);
needsNewGroup = false;
break;
}
}
if (needsNewGroup)
{
var group = new Group
{
Top = field.Y + field.Height,
Bottom = field.Y,
GroupFields = new List<TestFieldModel>
{
field
}
};
groups.Add(group);
}
}
var groupFields = groups.OrderByDescending(g => g.Top).Select(g => g.GroupFields.OrderBy(gf => gf.X).ToList()).ToList();
fields = groupFields.Aggregate((acc, list) => acc.Concat(list).ToList());
fields.Should().HaveCount(4, "Count is not 4: " + fields.Count);
fields[0].ExpectedOrderNumberResult.Should().Be(1, $"Expected 1. Got {fields[0].ExpectedOrderNumberResult}.");
fields[1].ExpectedOrderNumberResult.Should().Be(2, $"Expected 2. Got {fields[1].ExpectedOrderNumberResult}.");
fields[2].ExpectedOrderNumberResult.Should().Be(3, $"Expected 3. Got {fields[2].ExpectedOrderNumberResult}.");
fields[3].ExpectedOrderNumberResult.Should().Be(4, $"Expected 4. Got {fields[3].ExpectedOrderNumberResult}.");
}
}
public class Group
{
public List<TestFieldModel> GroupFields { get; set; }
public int Top { get; set; }
public int Bottom { get; set; }
}
public class TestFieldModel
{
public int ExpectedOrderNumberResult { get; set; }
public int PageNumber { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int X { get; set; }
public int Y { get; set; }
}

Find the product with the lowest price return with name LINQ

I´m new to LINQ and I´m trying to find the lowest price in a list and return the name of it.
I´ve been searching and haven´t find anything that I can use.
The List is in a class Category but I have to write out the result in main.
It´s a C# in a Microsoft Visual Studio.
The list I have to find the lowest price from is like this:
public static IEnumerable<Product> GetProducts( )
{
List<Product> products = new List<Product>( );
products.Add( new Product { Name = "Milk", Price = 90, CategoryID = 4, ID = 1 } );
products.Add( new Product { Name = "Cheese", Price = 130, CategoryID = 4, ID = 2 } );
products.Add( new Product { Name = "Butter", Price = 110, CategoryID = 4, ID = 3 } );
products.Add( new Product { Name = "Apple juice", Price = 230, CategoryID = 1, ID = 4 } );
products.Add( new Product { Name = "Grape juice", Price = 240, CategoryID = 1, ID = 5 } );
products.Add( new Product { Name = "Beetroot juice", Price = 300, CategoryID = 1, ID = 6 } );
products.Add( new Product { Name = "Carrot juice", Price = 190, CategoryID = 1, ID = 7 } );
products.Add( new Product { Name = "Ginger ale", Price = 990, CategoryID = 1, ID = 8 } );
products.Add( new Product { Name = "Oregano", Price = 500, CategoryID = 2, ID = 9 } );
products.Add( new Product { Name = "Salt", Price = 550, CategoryID = 2, ID = 10 } );
products.Add( new Product { Name = "Pepper", Price = 490, CategoryID = 2, ID = 11 } );
products.Add( new Product { Name = "Carrots", Price = 300, CategoryID = 3, ID = 12 } );
products.Add( new Product { Name = "Spinach", Price = 250, CategoryID = 3, ID = 13 } );
products.Add( new Product { Name = "Onion", Price = 200, CategoryID = 3, ID = 14 } );
products.Add( new Product { Name = "Garlic", Price = 150, CategoryID = 3, ID = 15 } );
products.Add( new Product { Name = "Tomatoes", Price = 100, CategoryID = 3, ID = 16 } );
return products;
}
from p in Products where p.Price == Products.Min(x=>x.Price) select p.Name
The problem with taking the First from an Ordered list is that it doesn't deal with the possibilities of multiple items having the same lowest price.
products.OrderBy(p => p.Price).Select(p => p.Name).First();
or
products.OrderBy(p => p.Price).First().Name;
This returns Milk
string namesOfProductWithLowestPrice = products
.GroupBy(p => p.Price)
.OrderBy(g => g.Key)
.Select(g => string.Join(",", g.Select(p => p.Name)))
.FirstOrDefault();
In case of multiple products with the lowest price it will concatenate the names with comma.
EDIT I've changed the answer after Tim Schmelters comment on Ralph Shillingtons answer, which is very similiar to mine, but in a different syntax.
int lowestPrice = from prod in GetProducts()
select prod.Price).Min();
var lowestPriceProduct = from p in GetProducts()
where lowestPrice == p.Price)
select p;

EF LINQ query where meet all list or array

i have these data:
class MyTableItem
{
public long id { get; set; }
public long listId { get; set; }
public long listFieldValue { get; set; }
public long parentId { get; set; }
}
and:
var myData = new MyTableItem[]
{
new MyTableItem { id = 1, listId = 1, listFieldValue = 100, parentId = 1 },
new MyTableItem { id = 2, listId = 2, listFieldValue = 130, parentId = 1 },
new MyTableItem { id = 3, listId = 3, listFieldValue = 170, parentId = 1 },
new MyTableItem { id = 4, listId = 4, listFieldValue = 170, parentId = 1 },
new MyTableItem { id = 5, listId = 1, listFieldValue = 100, parentId = 2 },
new MyTableItem { id = 6, listId = 2, listFieldValue = 130, parentId = 2 },
new MyTableItem { id = 7, listId = 3, listFieldValue = 170, parentId = 2 },
new MyTableItem { id = 8, listId = 4, listFieldValue = 270, parentId = 2 },
...(continue)
};
var myMatchConditions = new int?[][] //id, rangeTypeId(equal, more, less, between), from, to
{
new int?[] { 1, 1, 100, null },
new int?[] { 2, 2, 125, null },
new int?[] { 3, 3, null, 175 },
new int?[] { 4, 4, 130, 180 }
...(can continue more)
};
now i need to know which myData (groupBy parrentId) are matched by my conditions,
let me explain more:
I want to know which parrent Id has listFieldValue where:
1) (listId == 1)&&(listFieldValue == 100)
and
2) (listId == 2)&&(listFieldValue > 125)
and
3) (listId == 3)&&(listFieldValue < 175)
and
4) ((listId == 4)&&(listFieldValue > 130)&&(listFieldValue < 180))
it must return (1)parrentId.
There you go. Explanations are at the bottom:
IEnumurable<MyTableItem> temp = myData ;
for (int i = 0; i < myMatchConditions.GetLength(0); i++)
{
var conditionType = myMatchConditions[i,1];
if (conditionType == 1)
{
temp = temp.Where(_ => _listFieldValue == myMatchConditions[i,2]);
}
else
{
if (conditionType == 2 || conditionType == 4)
{
temp = temp.Where(_ => _listFieldValue > myMatchConditions[i,2]);
}
if (conditionType == 3 || conditionType == 4)
{
temp = temp.Where(_ => _listFieldValue < myMatchConditions[i,3]);
}
}
}
I'm using IEnumurable<MyTableItem> which means it's Linq and not Linq to entities. I chose that because your myData is not an EF table but a simple array.
I go through all the "rows" with a for, you can do that with a foreach, and I add the Where clauses to filter out more and more each time (The actual filtering will happen only when you use that temp list)
I add a condition based on the type in the second cell, and if the type is 4... I add both the 2 and 3 type rules... which makes a 4 type rule

Using LINQ to count value frequency

I have a table
ID|VALUE
VALUE is an integer field with possible values between 0 and 4. How can I query the count of each value?
Ideally the result should be an array with 6 elements, one for the count of each value and the last one is the total number of rows.
This simple program does just that:
class Record
{
public int Id { get; set; }
public int Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Record> records = new List<Record>()
{
new Record() { Id = 1, Value = 0},
new Record() { Id = 2, Value = 1 },
new Record() { Id = 3, Value = 2 },
new Record() { Id = 4, Value = 3 },
new Record() { Id = 5, Value = 4 },
new Record() { Id = 6, Value = 2 },
new Record() { Id = 7, Value = 3 },
new Record() { Id = 8, Value = 1 },
new Record() { Id = 9, Value = 0 },
new Record() { Id = 10, Value = 4 }
};
var query = from r in records
group r by r.Value into g
select new {Count = g.Count(), Value = g.Key};
foreach (var v in query)
{
Console.WriteLine("Value = {0}, Count = {1}", v.Value, v.Count);
}
}
}
Output:
Value = 0, Count = 2
Value = 1, Count = 2
Value = 2, Count = 2
Value = 3, Count = 2
Value = 4, Count = 2
Slightly modified version to return an Array with only the count of values:
int[] valuesCounted = (from r in records
group r by r.Value
into g
select g.Count()).ToArray();
Adding the rows count in the end:
valuesCounted = valuesCounted.Concat(new[] { records.Count()}).ToArray();
Here is how you would get the number of rows for each value of VALUE, in order:
var counts =
from row in db.Table
group row by row.VALUE into rowsByValue
orderby rowsByValue.Key
select rowsByValue.Count();
To get the total number of rows in the table, you can add all of the counts together. You don't want the original sequence to be iterated twice, though; that would cause the query to be executed twice. Instead, you should make an intermediate list first:
var countsList = counts.ToList();
var countsWithTotal = countsList.Concat(new[] { countsList.Sum() });

Categories