Creating a list by adding items when conditions are met - c#

public class Connector
{
public double Id { get; set; }
public string Name { get; set; }
public double Len { get; set; }
public double Height { get; set; }
public double Count { get; set; }
}
I have a list of such facilities:
List<Connector> resutsList = new List<Connector>();
Below is an example of the contents of such a list:
1 | IZO | 1000 | 200 | 2
2 | IZO | 1000 | 200 | 4
3 | IZO | 600 | 200 | 5
4 | IZO | 1000 | 200 | 2
5 | IZO | 600 | 180 | 7
6 | IZO | 600 | 180 | 3
I need such a result: (This is the sum of the Count positions when the Len and Height conditions are met.)
1 | IZO | 1000 | 200 | 8
2 | IZO | 600 | 200 | 5
3 | IZO | 600 | 180 | 10
Is it possible to do any Linq combination?
Or another simple solution?

Here's my effort.
class Program
{
public class Connector
{
public Connector(double id, string name, double len, double height, double count)
{
Id = id;
Name = name;
Len = len;
Height = height;
Count = count;
}
public double Id { get; set; }
public string Name { get; set; }
public double Len { get; set; }
public double Height { get; set; }
public double Count { get; set; }
}
static void Main(string[] args)
{
var l = new List<Connector>
{
new Connector(1, "IZO", 1000, 200, 2),
new Connector(2, "IZO", 1000, 200, 4),
new Connector(3, "IZO", 600, 200, 5),
new Connector(4, "IZO", 1000, 200, 2),
new Connector(5, "IZO", 600, 180, 7),
new Connector(6, "IZO", 600, 180, 3)
};
var sums = from c in l
group c by new { c.Name, c.Len, c.Height } into g
select new { g.First().Id, g.Key.Name, g.Key.Len, g.Key.Height, Count = g.Sum(x => x.Count) };
}
}
```
Please note that the ids are not exactly like in your example. (1,2,3 vs 1,3,5)
I don't believe you can get the index with query expression syntax, but here is another Linq way to do it and get the desired indexes:
var sums = l.GroupBy(c => new { c.Name, c.Len, c.Height })
.Select((g, index) => new{
Id = index+1,
g.Key.Name,
g.Key.Len,
g.Key.Height,
Count = g.Sum(x => x.Count)
});
Please note the index + 1

What you're trying to do here is group your list by Name, Len & Height, which you can do using the LINQ GroupBy method.
Then, you want to project that group to a new object using Select and a Sum aggregation on the Count property. For example:
var result = list
.GroupBy(x => new { x.Name, x.Len, x.Height })
.Select(x => new { x.Key.Name, x.Key.Len, x.Key.Height, Count = x.Sum(y => y.Count) })
.ToList();
As for the ID - well it makes a limited amount of sense in an aggregate operation. You have basically 2 choices
Use an incrementing number as one of the other answers does
.Select( (x,i) => new { ID = i, ....
That the first ID from the group
.Select(x => new { ID = x.First().ID, ....

you can try is
Here we group the elements of resultList by three conditions Name,Len,Height.Then we create a new Connector object from that group by by using the Len,Height,Name,& Id then we Sum all the elements present in that group and assign Count var with the Sum.
var List = from result in resultList
group d by new { d.Name, d.Len , d.Height} into g
select new Connector
(
Id = g.First().ID,
Name = g.Key.Name,
Len = g.Key.Len,
Height = g.Key.Height,
count = g.Sum(s => s.Count)
);
Note:- this will not generate incrementing ID if you want that you may refer #tymtam's answer

Related

LINQ compare values for the same year , different month and doing a count if value has changed

I have a data set that looks like below:
Option | Year | Month | Value
-------+------+-------+------
1 | 2011 | 12 | 0
-------+------+-------+------
1 | 2011 | 11 | 1
-------+------+-------+------
2 | 2012 | 6 | 0
-------+------+-------+------
2 | 2012 | 7 | 0
-------+------+-------+------
1 | 2011 | 6 | 2
The result set I am looking for is below :
Option | Year | ChangedCount
-------+------+-------------
1 | 2011 | 3
-------+------+-------------
2 | 2012 | 0
-------+------+-------------
Changed Count represents , if the value has changed in the same year between different months . so say if the value of 06 month was 2 and then 07 it changed to 1 , then changed count will be 1 . If the value for two months remains the same , then changedCount is 0
Here is what I have written so far
var changes = from ord in resultSet
group ord by new
{
ord.Year,
ord.Month,
ord.Option,
ord.Value,
}
into g
select new
{
Year = g.Key.Year,
changed = g.Count(x => x.Value == 0)
+ g.Count(x => x.Value == 1)
+ g.Count(x => x.Value == 2)
};
How do I run comparison for previous value in column ?
{0,1,2} Map ENUM values
This is what I understand from your explanation:
class Record
{
public int Option { get; set; }
public int Year { get; set; }
public int Month { get; set; }
public int Value { get; set; }
}
var resultSet = new List<Record> {
new Record { Option=1, Year=2011, Month=12, Value=0 },
new Record { Option=1, Year=2011, Month=11, Value=1 },
new Record { Option=2, Year=2012, Month=6, Value=0 },
new Record { Option=2, Year=2012, Month=7, Value=0 },
new Record { Option=1, Year=2011, Month=6, Value=2 },
};
Helper Method to count changes:
public static int changeCount(List<Record> Records)
{
int previous = Records[0].Value;
var result_change = 0;
//i used sorted records by month you can do not this if order is not sensitive
foreach (var rec in Records.OrderBy(x=>x.Month))
{
if (rec.Value != previous)
{
result_change++;
}
previous = rec.Value;
}
return result_change;
}
and the actual code :
var changes = resultSet.GroupBy(x => new { x.Year }).Select(g => new
{
Year = g.Key.Year,
changed =changeCount( g.ToList()),
Option = g.First().Option
}).ToList();
Result :
2011,3,1
2012,0,2
Try:
var changes = from ord in resultSet
group ord by new
{
ord.Option,
ord.Year,
}
into g
select new
{
Option = g.Key.Option,
Year = g.Key.Year,
ChangedCount = g.Select(x => x.Value).Sum()
};
OR
resultSet
.GroupBy(x => new { x.Option, x.Year })
.Select(x => new { x.Key.Option, x.Key.Year, ChangedCount = x.Select(x => x.Value).Sum() });

How to get grouped values from List within List with LINQ

I'm looking to retrieve a list of the sum of property values in a list that is itself a property of another list, grouped by properties in the parent list, using LINQ.
To explain, I have a list of offers in a market with a trading date and hour of the day for a range of products, and a list of price and quantity bands within each offer. My classes are:
public class Offer
{
public DateTime TradingDate { get; set; }
public int HourOfDay { get; set; }
public string ProductName { get; set; }
public List<Band> OfferBands { get; set; }
}
public class Band
{
public decimal Price { get; set; }
public double Quantity { get; set; }
}
And what I'm looking to retrieve is the sum of Quantity for a certain Price for each TradingDate and HourOfDay, for every ProductName.
I haven't come up with a working solution, but as a start I'm trying something like (with a List<Offer> offers containing all offers, to retrieve quantities where the offer price < $10):
List<double> quantities = offers.SelectMany(o => o.Bands).Where(b => b.Price < 10).Select(b => b.Quantity)
But I don't know how to GroupBy the TradingDate and HourOfDay and retrieve the sum of Quantity. There can be multiple Offers with multiple OfferBands for different products, with various combinations of offer Prices, and I just want to get sum of Quantity for all products at a certain price grouped by date and time.
I could achieve this programmatically but I would like a LINQ solution. Thanks for your help.
Edit:
What I forgot to mention is that, where there are no Quantitys at the specified Price for a TradingDate and HourOfDay I would like to retrieve double.NaN (or 0).
With example data List<Offer> offers containing six Offers:
TradingDate | HourOfDay | ProductName | OfferBands
===================================================================
01/01/2017 | 1 | Chocolate | Price = 2, Quantity = 6
| | | Price = 5, Quantity = 10
-------------------------------------------------------------------
01/01/2017 | 2 | Chocolate | Price = 3, Quantity = 6
| | | Price = 5, Quantity = 20
-------------------------------------------------------------------
02/01/2017 | 1 | Chocolate | Price = 3, Quantity = 7
| | | Price = 6, Quantity = 9
-------------------------------------------------------------------
01/01/2017 | 1 | Cake | Price = 5, Quantity = 11
| | | Price = 8, Quantity = 3
-------------------------------------------------------------------
01/01/2017 | 2 | Cake | Price = 2, Quantity = 1
| | | Price = 8, Quantity = 4
-------------------------------------------------------------------
02/01/2017 | 1 | Cake | Price = 3, Quantity = 9
| | | Price = 5, Quantity = 13
-------------------------------------------------------------------
Selecting a sum of quantities for a given price, grouped by date and time, would give a List<double> output:
Where price >= 5
{ 24, 24, 22 }
Where price = 2
{ 6, 1, double.NaN }
Where price = 3
{ double.NaN, 6, 16 }
...where the output is the sum of quantities for all products at the specified prices for 01/01/2017 hour 1, 01/01/2017 hour 2, and 02/01/2017 hour 1.
Hopefully that is clear to follow.
First, you need to filter to get the desired Offers with the matching OfferBands.
You can create/pass-in a filter if you want to make this a function, I will just define it inline:
Func<Band, bool> filter = (Band b) => b.Price == 3;
Since you don't care about ProductName, I used an anonymous type, but you could use Offer instead. At this point, we throw out the empty slots as well:
var filteredOffers = offers.Select(o => new { TradingDate = o.TradingDate, HourOfDay = o.HourOfDay, OfferBands = o.OfferBands.Where(filter).ToList() }).Where(gb => gb.OfferBands.Count > 0);
Now, since you want to include empty slots for TradingDate+HourOfDay that are in the original data but were filtered out, group the filtered data and create a dictionary:
var mapQuantity = filteredOffers.GroupBy(o => new { o.TradingDate, o.HourOfDay })
.Select(og => new { og.Key.TradingDate, og.Key.HourOfDay, QuantitySum = og.Sum(o => o.OfferBands.Sum(ob => ob.Quantity)) })
.ToDictionary(og => new { og.TradingDate, og.HourOfDay }, og => og.QuantitySum);
Then, going back to the original offers group find all the distinct slots (TradingDate+HourOfDday) and match them up to the QuantitySum, filling empty slots with double.NaN and convert to a List:
var ans = offers.Select(o => new { o.TradingDate, o.HourOfDay }).Distinct().OrderBy(g => g.TradingDate).ThenBy(g => g.HourOfDay).Select(g => mapQuantity.TryGetValue(g, out var sumq) ? sumq : double.NaN).ToList();
After re-thinking, I realized you could simplify by preserving the slots that are empty in the filteredOffers and then set their values after grouping:
var filteredOffers = offers.Select(o => new { TradingDate = o.TradingDate, HourOfDay = o.HourOfDay, OfferBands = o.OfferBands.Where(filter).ToList() });
var ans = filteredOffers.GroupBy(o => new { o.TradingDate, o.HourOfDay })
.OrderBy(og => og.Key.TradingDate).ThenBy(og => og.Key.HourOfDay)
.Select(og => (og.Sum(o => o.OfferBands.Count) > 0 ? og.Sum(o => o.OfferBands.Sum(ob => ob.Quantity)) : double.NaN));
By using the IGrouping Key to remember the slots, you can simplify the query:
var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
.OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
.Select(obg => {
var filteredOBs = obg.SelectMany(ob => ob).Where(filter).ToList();
return filteredOBs.Count > 0 ? filteredOBs.Sum(b => b.Quantity) : double.NaN;
});
If you are willing to give up the double.NaN for zero instead, you can make this even simpler:
var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
.OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
.Select(obg => obg.SelectMany(ob => ob).Where(filter).Sum(b => b.Quantity));
Finally, to finish the dead horse off, some special extension methods can preserve the NaN returning property and use the simple query form:
public static class Ext {
static double ValuePreservingAdd(double a, double b) => double.IsNaN(a) ? b : double.IsNaN(b) ? a : a + b;
public static double ValuePreservingSum(this IEnumerable<double> src) => src.Aggregate(double.NaN, (a, b) => ValuePreservingAdd(a, b));
public static double ValuePreservingSum<T>(this IEnumerable<T> src, Func<T, double> select) => src.Select(s => select(s)).Aggregate(double.NaN, (a, b) => ValuePreservingAdd(a, b));
}
var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
.OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
.Select(obg => obg.SelectMany(ob => ob).Where(filter).ValuePreservingSum(b => b.Quantity));
I believe I've been able to manage the groupings you are after, though I haven't done the summation of the (quantity)*(whatever price matches some condition), as hopefully that is something that you can customize however you need to.
To get things grouped, I had to use several nested projections and do each grouping individually (it was actually quite fun to work this out, the big sticking point is that LINQ's IGrouping isn't as straightforward to use as you might expect, so each time I grouped I did a projection with a Select):
var projected = offers.GroupBy(x => x.ProductName)
.Select(x => new
{
ProductName = x.Key,
Dates = x.GroupBy(y => y.TradingDate).ToList()
.Select(y => new
{
TradingDate = y.Key,
Times = y.GroupBy(z => z.HourOfDay).ToList()
.Select(zx => new
{
Time = zx.Key,
Items = zx.ToList()
})
})
}).ToList();
Hopefully, this will give you enough to start on for doing your summation with whatever extra checks you need for 0 items, prices not high enough, and so on.
Note that this query is probably not the most efficient if you're working directly with a database - it probably pulls more information than it really needs to at this point. I don't know enough about what you're working on to begin to optimize it, though.
var offers = new List<Offer>();
// flatten the nested list linq-style
var flat = from x in offers
from y in x.OfferBands
select new {x.TradingDate, x.HourOfDay, x.ProductName, y.Price, y.Quantity};
var grouped = from x in flat
group x by new {x.TradingDate, x.HourOfDay, x.ProductName}
into g
select new
{
g.Key.TradingDate,
g.Key.HourOfDay,
g.Key.ProductName,
OfferBands = (from y in g
group y by new {y.Price}
into q
select new {Price = q.Key, Quantity = q.Sum(_ => _.Quantity)}).ToList()
};
foreach (var item in grouped)
{
Console.WriteLine(
"TradingDate = {0}, HourOfDay = {1}, ProductName = {2}",
item.TradingDate,
item.HourOfDay,
item.ProductName);
foreach (var offer in item.OfferBands)
Console.WriteLine(" Price = {0}, Qty = {1}", offer.Price, offer.Quantity);
}

Take and Group By in EntityFramework [duplicate]

This question already has an answer here:
Take(limit) list inside Groupby in Entity Framework
(1 answer)
Closed 5 years ago.
I need to take (for example, 2), 2 messages from a conversation
example:
id = idConversation
Id | messageId | Message
---|-----------|--------
1 | 1 | "asd"
1 | 2 | "asd2"
1 | 3 | "asd3"
1 | 4 | "asd4"
2 | 5 | "asd5"
3 | 6 | "asd6"
3 | 7 | "asd7"
3 | 8 | "asd8"
3 | 9 | "asd9"
3 | 10 | "asd10"
4 | 11 | "asd11"
4 | 12 | "asd12"
4 | 13 | "asd13"
and i want that
Id messageId Message
---|-----------|--------
1 | 1 | "asd"
1 | 2 | "asd2"
2 | 5 | "asd5"
3 | 6 | "asd6"
3 | 7 | "asd7"
4 | 11 | "asd11"
4 | 12 | "asd12"
i can grouby idConversation, but i cant limit quantity using grouby in a conversation.
var test = unitOfWork.ChatMensagemRepository.GetAll()
.Where(x => x.PessoaCodigoPessoa == codigoRemetente)
.GroupBy(x => x.ChatConversaCodigoChatConversa)
.Select(group => new
{
codigoChat = group.Key,
list = group.Select(mensagem => new
{
// do stuff
})
}).ToList();
this is ok... but dont limit my list, when i do group.take(2).Select.....
give me "Subquery returns more than 1 row"
var test = unitOfWork.ChatMensagemRepository.GetAll()
.Where(x => x.PessoaCodigoPessoa == codigoRemetente)
.GroupBy(x => x.ChatConversaCodigoChatConversa)
.Select(group => new
{
codigoChat = group.Key,
list = group.Take(2).Select(mensagem => new
{
// do stuff
})
}).ToList();
error : Subquery returns more than 1 row
var test = unitOfWork.ChatMensagemRepository.GetAll()
.Where(x => x.PessoaCodigoPessoa == codigoRemetente)
.GroupBy(x => x.ChatConversaCodigoChatConversa)
.Select(group => new
{
codigoChat = group.Key,
list = group.Select(mensagem => new
{
// do stuff
}).take(2)
}).ToList();
error : Subquery returns more than 1 row
Here's an example of what I think you're after. This query will return the top 3 sudtents by GPA in each class:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ef6Test
{
class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string Class{ get; set; }
public decimal GPA { get; set; }
}
class Db : DbContext
{
public DbSet<Student> Students { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
var q = db.Students
.GroupBy(s => s.Class)
.SelectMany(g => g.OrderByDescending(s => s.GPA).Take(3));
Console.WriteLine(q.ToString());
Console.ReadKey();
}
}
}
}
I tried this code and is working fine:
class Conversation
{
public int Id;
public string Message;
public int MessageId;
}
class Program
{
static void Main(string[] args)
{
var inputList = new List<Conversation>
{
new Conversation() {Id = 1, Message = "asd0", MessageId = 1},
new Conversation() {Id = 1, Message = "asd1", MessageId = 2},
new Conversation() {Id = 1, Message = "asd2", MessageId = 3},
new Conversation() {Id = 2, Message = "asd3", MessageId = 4},
new Conversation() {Id = 2, Message = "asd4", MessageId = 5},
new Conversation() {Id = 2, Message = "asd5", MessageId = 6},
new Conversation() {Id = 3, Message = "asd6", MessageId = 7}
};
var outputList = inputList.GroupBy(x => x.Id)
.SelectMany(x => x.OrderBy(y => y.MessageId).Take(2).ToList())
.ToList();
Console.ReadKey();
}
}

Linq Table Group By Get Perecentage in each column

I have a datatable with 2 columns:
GuitarBrand | Status
---------------------
Fender | Sold
Fender | In Stock
Gibson | In Stock
Gibson | In Stock
I want to write a linq query to output
GuitarBrand | PercentSold | Sold/Total
---------------------------------------
Fender | 50% | 1/2
Gibson | 100% | 2/2
Here's what I have so far:
var groupedtable = from b in table.AsEnumerable()
group b by b.Field<"GuitarBrand"> into g
select new ( GuitarBrand = g.Key, Perecent = (float)g.Count()/(float)g.key)
Which I got from another post but it isn't even close to working, I get a Cannot Convert string to float. I've tried looking at other posts but I can't find anything.
Thanks!
You can use the following (hopefully self explanatory) query:
var groupedtable =
from b in table.AsEnumerable()
group b by b.Field<string>("GuitarBrand") into g
let Total = g.Count()
let Sold = g.Count(e => e.Field<string>("Status") == "Sold")
let SoldPercent = (float)Sold / (float)Total
select new
{
GuitarBrand = g.Key,
PercentSold = SoldPercent.ToString("p"),
TotalSold = Sold + "/" + Total
};
maybe something like this!
var groupedtable = from b in table.AsEnumerable()
group b by b.Field<"GuitarBrand"> into g
select new {
GuitarBrand = g.Key,
Perecent = g.Count(x=>x.Status.Eguals("Sold")/(float)g.Count()
}
Something like this should get you started. In your question, your output table is contradicting. I've assumed you want the data as labeled.
public static void Main()
{
var guitars = new List<Guitar>()
{
new Guitar(){ Brand = "Fender", Status = Status.Sold },
new Guitar(){ Brand = "Fender", Status = Status.InStock },
new Guitar(){ Brand = "Gibson", Status = Status.InStock },
new Guitar(){ Brand = "Gibson", Status = Status.InStock }
};
var query = guitars
.GroupBy(guitar => guitar.Brand)
.Select(group => new
{
GuitarBrand = group.Key,
Sold = group.Where(guitar => guitar.Status == Status.Sold).Count(),
Total = group.Count()
})
.Select(_ => new
{
_.GuitarBrand,
PercentSold = ((decimal)_.Sold / (decimal)_.Total) * 100,
SoldAndTotal = string.Format("{0}/{1}", _.Sold, _.Total)
});
}
class Guitar {
public string Brand { get; set; }
public Status Status { get; set; }
}
enum Status {
Sold,
InStock
}

How to group data and store them in a new list by group

How can you store the data in the list by group?
Say,
public class ProductPrice
{
public string Name { get; set }
public decimal Price { get; set; }
// Date and other properties
}
then having a record like this:
+--------+--------+
| Name | Price |
+--------+--------+
| Chair | 11 |
| Table | 15 |
| Table | 30 |
| Window | 24 |
| Chair | 29 |
+--------+--------+
What should be done in order to achieve a list something like this:
{
{
new ProductPrice { Name = "Chair", Price = 11 },
new ProductPrice { Name = "Chair", Price = 29 },
},
{
new ProductPrice { Name = "Table", Price = 15 },
new ProductPrice { Name = "Table", Price = 30 }
},
{
new ProductPrice { Name = "Window", Price = 24 }
}
}
As you can see, they are grouped by their Name and store them in a list per group. It would be great to feed them to a, say, line chart to see their price trends. It's just that I am having hard time creating the list.
In a nutshell, can I create a List that is grouped by Products Name? Also, products can have new record as well?
What you need is a List<List<ProductPrice>>, you can do:
List<List<ProductPrice>> groupedList = list.GroupBy(r => r.Name)
.Select(grp => new
{
List = grp.Select(r => r).ToList()
})
.Select(r => r.List)
.ToList();
This will return you three values in your List one for each group.
You can also project your results to Dictionary<string, List<ProductPrice>>, where your key would be the name of Product and value will contain List<ProductPrice> related to Key. Use Enumerable.ToDictionary like:
Dictionary<string, List<ProductPrice>> groupedList = list.GroupBy(r => r.Name)
.ToDictionary(grp => grp.Key, grp => grp.ToList());

Categories