C# List<> GroupBy functionality [closed] - c#

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last month.
Improve this question
I have a list like the below.
var list = new List<Currency>()
{
new Currency() { Id = 1, CurrencyCode = "USD", BuyRate = 1.105478m, SellRate = 1.154878m },
new Currency() { Id = 2, CurrencyCode = "USD", BuyRate = 1.115487m , SellRate = 1.167487m },
new Currency() { Id = 3, CurrencyCode = "USD", BuyRate = 1.121454m , SellRate = 1.145748m },
new Currency() { Id = 4, CurrencyCode = "EUR", BuyRate = 1.415474m , SellRate = 1.550454m },
new Currency() { Id = 5, CurrencyCode = "EUR", BuyRate = 1.424857m , SellRate = 1.564578m },
new Currency() { Id = 6, CurrencyCode = "AUD", BuyRate = 2.154857m , SellRate = 2.487876m },
};
I want to get only one record per currencyCode. I want to get the record with the highest id for the currencies that have multiple record. So result list should look like this:
3 USD 1.121454 1.145748
5 EUR 1.424857 1.564578
6 AUD 2.154857 2.487876
Any help is much appreciated.

Quite a simple query:
IEnumerable<Currency> query =
from c in list
group c by c.CurrencyCode into gcs
from gc in gcs.OrderByDescending(x => x.Id).Take(1)
select gc;
That gives me:
Or this, if you prefer method syntax:
IEnumerable<Currency> query =
list
.GroupBy(c => c.CurrencyCode)
.SelectMany(gcs =>
gcs.OrderByDescending(x => x.Id).Take(1));

var result = list
.GroupBy(x => x.CurrencyCode).ToList() //group the items by the currency code
.Select(x => x.OrderByDescending(y => y.Id).First()).ToList(); //order by the id and get the first item.
var values = result
.Select(x =>
$"{x.Id.ToString().PadLeft(3,' ')}" +
$"{x.CurrencyCode.PadLeft(13,' ')}" +
$"{x.BuyRate.ToString("N6").PadLeft(10,' ')}" +
$"{x.SellRate.ToString("N6").PadLeft(10, ' ')}").ToList(); //return a list tabulated

Related

Multiple Group By LINQ extension methods

I have a C#.NET application and want to do Group By on multiple conditions.
I have a list like this:
var testq = new List<TestQuestion>()
{
new TestQuestion
{
Id = 1,
QuestionId = 1,
SelectedAnswerId = null
},
new TestQuestion
{
Id = 2,
QuestionId = 2,
SelectedAnswerId = 1
},
new TestQuestion
{
Id =3,
QuestionId = 1,
SelectedAnswerId = 1
},
new TestQuestion
{
Id = 4,
QuestionId = 3,
SelectedAnswerId = 5
},
new TestQuestion
{
Id = 5,
QuestionId = 1,
SelectedAnswerId = 2
},
new TestQuestion
{
Id = 6,
QuestionId = 3,
SelectedAnswerId = 3
},
new TestQuestion
{
Id =7,
QuestionId = 4,
SelectedAnswerId = null
},
new TestQuestion
{
Id =8,
QuestionId = 5,
SelectedAnswerId = null
},
};
My code is :
var result = testq
.Where(p => p.SelectedAnswerId.HasValue)
.GroupBy(p => p.QuestionId)
.Select(p => p.FirstOrDefault())
.ToList();
now, result ID's is (2 ,3, 4)
but result is not true...
The result should be this :
ID's -> (2 ,3, 4, 7, 8)
I want to group by the result based on the QuestionID field and The first record that does not have a (SelectedAnswerId)field value is empty,
Also, records in which the question ID is only there once, regardless of the value of the field (SelectedAnswerId) in the output. that's mean, last two items in the list
please guide me...
Try this:
var result = testq
.Where(p => p.SelectedAnswerId.HasValue || testq.Count(x => x.QuestionId == p.QuestionId) == 1)
.GroupBy(p => p.QuestionId)
.Select(p => p.FirstOrDefault())
.Distinct()
.ToList();
C# Fiddle
You have to filter the SelectedAnswerId in Select not in the Where clause. Try the below,
var result = testq
.GroupBy(p => p.QuestionId)
.Select(p =>
{
var grouped = p.ToList(); //Get the groupBy list
TestQuestion testQuestion = grouped.FirstOrDefault(x => x.SelectedAnswerId.HasValue); //Get anything with AnswerId
return testQuestion ?? grouped.FirstOrDefault(); //If not not available with Answer then get the First or Default value
})
.ToList();
C# Fiddle with test data.
Although after a GroupBy the order of the elements in each group is fairly defined (see Enumerable.GroupBy, it seems that the element that you want is not the First one in each group.
You want the First element of each group that has a non-null SelectedAnswerId, or if there is no such one, you want the First element of each group that has a null SelectedAnswerId.
How about this:
var result = testQ.GroupBy(question => question.QuestionId);
// every group contains a sequence of questions with same questionId
// select the ones with a null SelectedAnswerId and the ones with a non-null value
.Select(group => new
{
NullSelectedAnswer = group
.Where(group.SelectedAnswerId == null)
.FirstOrDefault(),
NonNullselectedAnswer = group
.Where(group.SelectedAnswerId != null)
.FirstOrDefault(),
})
// if there is any NonNullSelectedAnswer, take that one, otherwise take the null one:
.Select(selectionResult => selectionResult.NonNullSelectedAnswer ??
selectionResult.NullSelectedAnswer);

Distinct by Category then Join all distinct values per Category in one column

I have this Data:
CategoryId Value
1 val1
1 val2
1 val2
2 test1
2 test1
3 data1
3 data2
3 data2
the output that i want is like this:
CategoryId Value
1 val1. val2.
2 test1.
3 data1. data2.
output should be Distinct in CategoryId and only distinct values per category should be displayed and joined together in 1 column value. (Assume that the values are string values which are 1 to 3 sentences long).
How do i query this in LINQ? or how do i group it with the output that i wanted?
GroupBy CategoryId and Join the Distinct Values
var distinctCategory = categoryList.GroupBy(x => x.CategoryId)
.Select(x => new Category()
{
CategoryId = x.Key,
Value = string.Join(". ", x.Select(y => y.Value).Distinct())
});
https://dotnetfiddle.net/0Z04AY
This is tested and working solution.
List<Data> data = new List<Data>();
data.Add(new Data(){CategoryId = 1,Value = "val1"});
data.Add(new Data(){CategoryId = 1,Value = "val2"});
data.Add(new Data(){CategoryId = 1,Value = "val2"});
data.Add(new Data(){CategoryId = 2,Value = "test1"});
data.Add(new Data(){CategoryId = 2,Value = "test1"});
data.Add(new Data(){CategoryId = 3,Value = "data1"});
data.Add(new Data(){CategoryId = 3,Value = "data2"});
data.Add(new Data(){CategoryId = 3,Value = "data2"});
var result = data.Distinct()
.GroupBy(d => d.CategoryId, d=>d.Value, (k,v) => new { Key=k, Values = v.Distinct() } )
.Select(d => new Data()
{
CategoryId = d.Key,
Value = string.Join(". ", d.Values.ToList())
}).ToList();
It sounds like you want to group the values by category ID, and then take the distinct values within the group.
That can all be done with a single GroupBy call:
var query = input.GroupBy(
item => item.CategoryId, // Key projection
item => item.Value, // Element projection
// Result projection. (You may want to add ToList within the lambda to materialize.)
(key, values) => new { Key = key, Values = values.Distinct() });
Here's a complete program with your example data:
using System;
using System.Linq;
class Program
{
static void Main()
{
var input = new[]
{
new { CategoryId = 1, Value = "val1" },
new { CategoryId = 1, Value = "val2" },
new { CategoryId = 1, Value = "val2" },
new { CategoryId = 2, Value = "test1" },
new { CategoryId = 2, Value = "test1" },
new { CategoryId = 3, Value = "data1" },
new { CategoryId = 3, Value = "data2" },
new { CategoryId = 3, Value = "data2" },
};
var query = input.GroupBy(
item => item.CategoryId, // Key
item => item.Value, // Element
// Projection of results
(key, values) => new { Key = key, Values = values.Distinct() });
foreach (var element in query)
{
Console.WriteLine($"{element.Key}: {string.Join(", ", element.Values)}");
}
}
}
Alternatively, you could just do the grouping of value by category ID, then perform the Distinct() part when you consume:
var query = from item in input
group item.Value by item.CategoryId;
foreach (var group in query)
{
var values = group.Distinct();
Console.WriteLine($"{group.Key}: {string.Join(", ", values)}");
}
You can use string.Join within the query, of course - but I'd normally wait until the last possible moment to convert the logical data into a display representation. If you wanted to do that with the first example, you'd just use:
var query = input.GroupBy(
item => item.CategoryId, // Key projection
item => item.Value, // Element projection
// Result projection. (You may want to add ToList within the lambda to materialize.)
(key, values) => new { Key = key, Values = string.Join(", ", values.Distinct()) });
Note that I've joined the values with a comma rather than a period. Your original expected output wouldn't be achievable with just string.Join and a period anyway, as you have a trailing period too. You could get that exact output with a projection then a string.Concat call, e.g.
foreach (var element in query)
{
var periodValues = element.Values.Select(x => x + ". ");
Console.WriteLine($"{element.Key}: {string.Concat(periodValues)}");
}

Linq query to group by field1, count field2 and filter by count between values of joined collection

I'm having trouble with getting a my linq query correct. I've been resisting doing this with foreach loops because I'm trying to better understand linq.
I have following data in LinqPad.
void Main()
{
var events = new[] {
new {ID = 1, EventLevel = 1, PatientID = "1", CodeID = "2", Occurences = 0 },
new {ID = 2, EventLevel = 2, PatientID = "1", CodeID = "2", Occurences = 0 },
new {ID = 3, EventLevel = 1, PatientID = "2", CodeID = "1", Occurences = 0 },
new {ID = 4, EventLevel = 3, PatientID = "2", CodeID = "2", Occurences = 0 },
new {ID = 5, EventLevel = 1, PatientID = "3", CodeID = "3", Occurences = 0 },
new {ID = 6, EventLevel = 3, PatientID = "1", CodeID = "4", Occurences = 0 }
};
var filter = new FilterCriterion();
var searches = new List<FilterCriterion.Occurence>();
searches.Add(new FilterCriterion.Occurence() { CodeID = "1", MinOccurences = 2, MaxOccurences = 3 });
searches.Add(new FilterCriterion.Occurence() { CodeID = "2", MinOccurences = 2, MaxOccurences = 3 });
filter.Searches = searches;
var summary = from e in events
let de = new
{
PatientID = e.PatientID,
CodeID = e.CodeID
}
group e by de into t
select new
{
PatientID = t.Key.PatientID,
CodeID = t.Key.CodeID,
Occurences = t.Count(d => t.Key.CodeID == d.CodeID)
};
var allCodes = filter.Searches.Select(i => i.CodeID);
summary = summary.Where(e => allCodes.Contains(e.CodeID));
// How do I find the original ID property from the "events" collection and how do I
// eliminate the instances where the Occurences is not between MinOccurences and MaxOccurences.
foreach (var item in summary)
Console.WriteLine(item);
}
public class FilterCriterion
{
public IEnumerable<Occurence> Searches { get; set; }
public class Occurence
{
public string CodeID { get; set; }
public int? MinOccurences { get; set; }
public int? MaxOccurences { get; set; }
}
}
The problem I have is that need to filter the results by the MinOccurences and MaxOccurences filter property and in the end I want the "events" objects where the IDs are 1,2,3 and 4.
Thanks in advance if you can provide help.
To access event.ID at the end of processing you need to pass it with your first query. Alter select to this:
// ...
group e by de into t
select new
{
PatientID = t.Key.PatientID,
CodeID = t.Key.CodeID,
Occurences = t.Count(d => t.Key.CodeID == d.CodeID),
// taking original items with us
Items = t
};
Having done that, your final query (including occurrences filter) might look like this:
var result = summary
// get all necessary data, including filter that matched given item
.Select(Item => new
{
Item,
Filter = searches.FirstOrDefault(f => f.CodeID == Item.CodeID)
})
// get rid of those without matching filter
.Where(i => i.Filter != null)
// this is your occurrences filtering
.Where(i => i.Item.Occurences >= i.Filter.MinOccurences
&& i.Item.Occurences <= i.Filter.MaxOccurences)
// and finally extract original events IDs
.SelectMany(i => i.Item.Items)
.Select(i => i.ID);
This produces 1, 2 as result. 3 and 4 are left out as they don't get past occurrences filtering.
I have run your program in linqpad.
My understanding is that you want to filter using filter.MinOccurences and filter.MaxOccurences on Occurences count of result data set.
You can add additional filters using Where clause.
if (filter.MinOccurences.HasValue)
summary = summary.Where (x=> x.Occurences >= filter.MinOccurences);
if (filter.MaxOccurences.HasValue)
summary = summary.Where (x=> x.Occurences <= filter.MaxOccurences);

LINQ - Grouping a list by multiple properties and returning an object with an array member

This is going to be a two part question.
I am trying to build a data structure for use with the Google Charts API (specifically, their data table).
Here is my code as it stands now:
return Json.Encode(
RMAs
.Where(r => r.CreatedDate.Year > DateTime.Now.Year - 4) //Only grab the last 4 years worth of RMAs
.GroupBy(r => new { Problem = r.Problem, Year = r.CreatedDate.Year, Quarter = ((r.CreatedDate.Month) / 3) })
.Select(r => new { Problem = r.Key.Problem, Year = r.Key.Year, Quarter = r.Key.Quarter, Count = r.Count() })
);
This gets me very close. This gets me an array similar to the following:
{"Problem":"It broke!","Year":2012,"Quarter":2,"Count":3},
{"Problem":"It broke!","Year":2012,"Quarter":1,"Count":1}
But, what I want is for the data to be grouped further by the "Problem" property so that the quarter is an array for each problem (this makes the data structure much easier to iterate over). An example of the desired structure:
{"Problem":"It broke!",
{"Year":2012,"Quarter":2,"Count":3},
{"Year":2012,"Quarter":1,"Count":1}
},
{"Problem":"Some other problem",
{"Year":2012,"Quarter":1,"Count":31}
}
The second part of the question: How can I ensure that I have data for each quarter (again, this makes it much easier to iterate over for building the data table with the API), even if a "Problem" did not occur in that quarter? Using the same example as last time:
{"Problem":"It broke!",
{"Year":2012,"Quarter":2,"Count":3},
{"Year":2012,"Quarter":1,"Count":1}
},
{"Problem":"Some other problem",
{"Year":2012,"Quarter":2,"Count":0}
{"Year":2012,"Quarter":1,"Count":31}
}
Thanks to Mr. TA for the inspiration and for showing me that you can use LINQ against a grouping.
I have tested this out in a local environment and the LINQ does indeed return a list of Problems tied to an array of Year/Quarter groupings with a total Count. I don't know if Json.Encode encodes it in the correct format though.
The following LINQ should return an anonymous type that fits the format you needed:
Edit: Query now returns count=0 for quarters where at least one problem occurred, but specified problem did not occur
var quarters = RMAs
.Where(rma => rma.CreatedDate.Year > DateTime.Now.Year - 4)
.GroupBy(rma => new {
Year = rma.CreatedDate.Year,
Quarter = ((rma.CreatedDate.Month) / 3)
});
return Json.Encode(
RMAs
//Only grab the last 4 years worth of RMAs
.Where(r => r.CreatedDate.Year > DateTime.Now.Year - 4)
// Group all records by problem
.GroupBy(r => new { Problem = r.Problem })
.Select(grouping => new
{
Problem = grouping.Key.Problem,
Occurrences = quarters.Select(quarter => new
{
Year = quarter.Key.Year,
Quarter = quarter.Key.Quarter,
Count = grouping
.GroupBy(record => new
{
Year = record.CreatedDate.Year,
Quarter = ((record.CreatedDate.Month) / 3)
})
.Where(record =>
record.Key.Year == quarter.Key.Year
&& record.Key.Quarter == quarter.Key.Quarter
).Count()
}).ToArray()
}));
Update: Thanks to JamieSee for updating with example JSON output:
This is an example of the JSON output:
[{"Problem":"P","Occurrences":[{"Year":2012,"Quarter":4,"Count":2},{"Year":2012,"Quarter":2,"Count":1},{"Year":2012,"Quarter":1,"Count":1}]},{"Problem":"Q","Occurrences":[{"Year":2012,"Quarter":3,"Count":1},{"Year":2012,"Quarter":2,"Count":1},{"Year":2012,"Quarter":1,"Count":1}]}]
Add the following to your query:
.GroupBy(x => x.Problem)
.ToDictionary(g => g.Key, g => g.Select(x=>new { Year=x.Year, Quarter=x.Quarter, Count = x.Count }));
You have to insert the following before .ToDictionary() above:
.Select(g =>
new {
Key = g.Key,
Items =
g
.GroupBy(r => r.Year)
.SelectMany(gy =>
gy.Concat(
Enumerable.Range(1,5)
.Where(q => !gy.Any(r=>r.Quarter == q))
.Select(q => new { Problem = g.Key, Year = gy.Key, Quarter = q, Count = 0 })
)
)
}
)
I think... try it out :)
I would advise against following this approach, however, and create "empty" records on the client, to avoid excessive bandwidth use.
Here's the full restatement to meet all your criteria:
public static IEnumerable<DateTime> GetQuarterDates()
{
for (DateTime quarterDate = DateTime.Now.AddYears(-4); quarterDate <= DateTime.Now; quarterDate = quarterDate.AddMonths(3))
{
yield return quarterDate;
}
}
public static void RunSnippet()
{
var RMAs = new[] {
new { Problem = "P", CreatedDate = new DateTime(2012, 6, 2) },
new { Problem = "P", CreatedDate = new DateTime(2011, 12, 7) },
new { Problem = "P", CreatedDate = new DateTime(2011, 12, 8) },
new { Problem = "P", CreatedDate = new DateTime(2011, 8, 1) },
new { Problem = "P", CreatedDate = new DateTime(2011, 4, 1) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 11, 11) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 6, 6) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 3, 3) }
};
var quarters = GetQuarterDates().Select(quarterDate => new { Year = quarterDate.Year, Quarter = Math.Ceiling(quarterDate.Month / 3.0) });
var rmaProblemQuarters = from rma in RMAs
where rma.CreatedDate > DateTime.Now.AddYears(-4)
group rma by rma.Problem into rmaProblems
select new {
Problem = rmaProblems.Key,
Quarters = (from quarter in quarters
join rmaProblem in rmaProblems on quarter equals new { Year = rmaProblem.CreatedDate.Year, Quarter = Math.Ceiling(rmaProblem.CreatedDate.Month / 3.0) } into joinedQuarters
from joinedQuarter in joinedQuarters.DefaultIfEmpty()
select new {
Year = quarter.Year,
Quarter = quarter.Quarter,
Count = joinedQuarters.Count()
})
};
string json = System.Web.Helpers.Json.Encode(rmaProblemQuarters);
Console.WriteLine(json);
}
Which yields:
[{"Problem":"P","Quarters":[{"Year":2008,"Quarter":2,"Count":0},{"Year":2008,"Quarter":3,"Count":0},{"Year":2008,"Quarter":4,"Count":0},{"Year":2009,"Quarter":1,"Count":0},{"Year":2009,"Quarter":2,"Count":0},{"Year":2009,"Quarter":3,"Count":0},{"Year":2009,"Quarter":4,"Count":0},{"Year":2010,"Quarter":1,"Count":0},{"Year":2010,"Quarter":2,"Count":0},{"Year":2010,"Quarter":3,"Count":0},{"Year":2010,"Quarter":4,"Count":0},{"Year":2011,"Quarter":1,"Count":0},{"Year":2011,"Quarter":2,"Count":1},{"Year":2011,"Quarter":3,"Count":1},{"Year":2011,"Quarter":4,"Count":2},{"Year":2011,"Quarter":4,"Count":2},{"Year":2012,"Quarter":1,"Count":0},{"Year":2012,"Quarter":2,"Count":1}]},{"Problem":"Q","Quarters":[{"Year":2008,"Quarter":2,"Count":0},{"Year":2008,"Quarter":3,"Count":0},{"Year":2008,"Quarter":4,"Count":0},{"Year":2009,"Quarter":1,"Count":0},{"Year":2009,"Quarter":2,"Count":0},{"Year":2009,"Quarter":3,"Count":0},{"Year":2009,"Quarter":4,"Count":0},{"Year":2010,"Quarter":1,"Count":0},{"Year":2010,"Quarter":2,"Count":0},{"Year":2010,"Quarter":3,"Count":0},{"Year":2010,"Quarter":4,"Count":0},{"Year":2011,"Quarter":1,"Count":1},{"Year":2011,"Quarter":2,"Count":1},{"Year":2011,"Quarter":3,"Count":0},{"Year":2011,"Quarter":4,"Count":1},{"Year":2012,"Quarter":1,"Count":0},{"Year":2012,"Quarter":2,"Count":0}]}]

How to write a LINQ query combining group by and aggregates?

Given the following input, how do I write a LINQ query or expression to return an aggregated result set for the quantity?
Input:
var foo = new[] { new { PO = "1", Line = 2, QTY = 0.5000 },
new { PO = "1", Line = 2, QTY = 0.2500 },
new { PO = "1", Line = 2, QTY = 0.1000 },
new { PO = "1", Line = 2, QTY = -0.1000 }
}.ToList();
Desired result:
Something along the lines of
new { PO = "1", Line = 2, QTY = 0.7500 } // .5 + .25 + .1 + -.1
How would I write it for multiple lines as well (see the object model in foo)?
How about this:
var result = foo.GroupBy(x => x.Line)
.Select(g => new { PO = g.First().PO,
Line = g.Key,
QTY = g.Sum(x => x.QTY) });
In the case you just have one Line, just add a .Single() - result is an IEnumerable of the anonymous type defined when you set up foo.
Edit:
If both PO and Line should designate different groups (PO can have different values), they both have to be part of the group key:
var result = foo.GroupBy(x => new { x.PO, x.Line})
.Select(g => new {
PO = g.Key.PO,
Line = g.Key.Line,
QTY = g.Sum(x => x.QTY)
});
var query = (from t in foo
group t by new {t.PO, t.Line}
into grp
select new
{
grp.Key.PO,
grp.Key.Line,
QTY = grp.Sum(t => t.QTY)
}).ToList()

Categories

Resources