I have a list of items which need to be sorted in a very particular way.
Take this example:
public class PayStubDetailItem
{
public string Code { get; set; }
}
void Main()
{
var bonus = new PayStubDetailItem() { Code = "Bonus" };
var ot = new PayStubDetailItem() { Code = "OT"};
var reg = new PayStubDetailItem() { Code = "Reg"};
var otPrem = new PayStubDetailItem() { Code = "OTPrem"};
var tps = new PayStubDetailItem() { Code = "3ps"};
var list = new List<PayStubDetailItem> {
bonus, ot, reg, otPrem, tps
};
}
My requirement states that sorting should be as follows:
Reg, OT, OTPrem, Alpha-sort by code.
Explained in words, if list contains 'Reg' code it should come first, if it also contains 'OT' it should come after Reg, etc.. All items which codes are different from those specific three should be alphabetically sorted.
In my example the sorted list should look like this:
Reg, OT, OTPrem, 3ps, Bonus
What would be the most elegant way to accomplish that? Perhaps, using LINQ or a custom comparer.
This is what I have attempted so far but it's to verbose:
var subList = list.Where(i => i.Code != "OT" && i.Code != "Reg" && i.Code != "OTPrem");
subList = subList.OrderBy(l => l.Code);
var newList = new List<PayStubDetailItem>();
if (list.Select(c => c.Code).Contains("Reg"))
{
newList.Add(list.Where(i => i.Code == "Reg").FirstOrDefault());
}
if (list.Select(c => c.Code).Contains("OT"))
{
newList.Add(list.Where(i => i.Code == "OT").FirstOrDefault());
}
if (list.Select(c => c.Code).Contains("OTPrem"))
{
newList.Add(list.Where(i => i.Code == "OTPrem").FirstOrDefault());
}
newList.AddRange(subList);
newList.Dump();
Thanks
You can use Linq like this:
var result = list.
.OrderBy(c => c.Code == "Reg" ? 0 : c.Code == "OT" ? 1 : c.Code == "OTPrem" ? 2 : 3)
.ThenBy(c => c.Code)
.ToList();
The OrderBy expression will give you the required priority order, while the ThenBy will do the alphabetical part.
As your sorting logic is quite unique to your problem, I would suggest an implementation the IComparer(T) interface and then calling Sort(IComparer(T)) on your list.
class MyComparer : IComparer<PayStubDetailItem>
{
public int Compare(PayStubDetailItem x, PayStubDetailItem y)
{
//Your implementation
}
}
Related
How can I calculate each of the averages of the students? I did this ... but the average does not work for me, how could I do it? With a JoinGroup and then a GroupBy ?, I wait to see solutions, thanks.
var listadoA = alumnos.Join(examenes,
a => a._id,
e => e._alumnoId,
(a, e) => new
{
NombreAlumno = a._nombre,
Examenes = examenes,
Notas = e._nota,
}).Where(p => p.Examenes.Count() >= 1).OrderBy(p => p.NombreAlumno).ToList();
foreach (var obj in listadoA){
var promedio = obj.Average(p => p.Nota);
Console.Write($"\nAlumno = {obj.NombreAlumno}, Promedio ={promedio}");
}
class Examen{
public double _nota{get;set;}
public int _alumnoId {get;set;}
public int cursoId{get;set;}
public Examen(int id, double nota, int idMateria){
this._alumnoId = id;
this.cursoId = idMateria;
this._nota = nota;
}
public override string ToString(){
return ($"Alumno = {this._alumnoId}, Nota = {this._nota}, Curso = {this.cursoId}");
}
public static List<Examen> GetLista(){
return new List<Examen>(){
new Examen(2,5,1),
new Examen(4,7,5),
new Examen(4,9,3),
new Examen(3,10,4),
new Examen(7,5,3),
new Examen(2,8,4),
new Examen(6,9,5),
new Examen(9,7,1),
new Examen(6,5,4),
new Examen(9,1,4),
new Examen(7,9,5),
};
}
}
I'm a bit short on time to test it but I think it should work with a few small tweaks. If I've made any typos, let me know:
var listadoA = alumnos.GroupJoin(examenes,
a => a._id,
e => e._alumnoId,
(a, eGroup) => new
{
Alumno = a,
Examenes = eGroup
}).Where(p => p.Examenes.Count() >= 1).OrderBy(p => p.Alumno._nombre).ToList();
foreach (var obj in listadoA){
var promedio = obj.Examenes.Average(e => e._nota);
I'm curious why your fields starting with underscore are publicly accessible; that's the naming convention for a private field.. should really have public properties for them. Also, I've assumed that "nota" is the exam score..
EDIT
The answer was originaly posted before precisions were made about the classes involved. I still keep the english naming, because it might be clearer for a wider audience. This also helps clarify my answer in regards to #Caius Jard pertinent comment.
Using Linq to object: (no use of Entity Framework)
This code does the following tradeof compared to the use of join. It might be less performant, but it is more simple (you don't even have to understand what a join is).
AveragesByStudents = Students
.Select(s => new
{
StudentName = s.Name,
Notes = Exams
.Where(e => e.StudentId == s.Id)
.Select(e => e.Note)
.ToList()
})
.Select(s => new
{
s.StudentName,
Average = s.Notes.Any() ? s.Notes.Average() : null
});
;
With this example, you obtain all the students, even if they have no notes (in that case their average is null). You could do it in one select, but it would not be more readable.
With the following example, you obtain only the students that have notes, so their average cannot be null.
AveragesByStudents = Students
.Select(s => new
{
StudentName = s.Name,
Notes = Exams
.Where(e => e.StudentId == s.Id)
.Select(e => e.Note)
})
.Where(s => s.Notes.Any())
.Select(s => new
{
s.StudentName,
Average = Notes.Average()
});
;
Add a ToList() at the end of queries if you want to materialize.
I have this method with a linq statement below. I'm not a fan of multiple if statement and I'm trying to find what is the best way to not have these if statement and have a private method.
My field values is being set as such:
var fieldValues = await GetFields // then it's being passed to my method.
public static AppraisalContactBorrower BuildCoBorrower(List<LoanFieldValue> fieldValues) {
var coborrower = new AppraisalContactBorrower();
if (fieldValues.Exists(f => f.FieldId == "CX.OS.AO.COBORRNAME")) {
coborrower.Name = fieldValues.First(v => v.FieldId == "CX.OS.AO.COBORRNAME").Value;
}
if (fieldValues.Exists(f => f.FieldId == "CX.OS.AO.BORRCONTACTZIP")) {
borrower.Zip = fieldValues.First(v => v.FieldId == "CX.OS.AO.BORRCONTACTZIP").Value;
}
if (fieldValues.Exists(f => f.FieldId == "CX.OS.AO.BORRCONTACTZIP")) {
borrower.Zip = fieldValues.First(v => v.FieldId == "CX.OS.AO.BORRCONTACTZIP").Value;
}
What I'm trying to do is instead of this:
coborrower.Name = fieldValues.First(v => v.FieldId == "CX.OS.AO.COBORRNAME").Value;
Is having something similar to this.
if (fieldValues.Exists(f => f.FieldId == "CX.OS.AO.BORRCONTACTZIP")) {
coborrower.Name = SETVALUE("CX.OS.AO.BORRCONTACTZIP")}
First, try using Enumerable.ToDictionary to have the field values grouped by FieldId, then use IDictionary.TryGetValue to get the existing values:
public static AppraisalContactBorrower BuildCoBorrower(List<LoanFieldValue> fieldValues) {
var groupedFieldValues = fieldValues.ToDictionary(f => f.FieldId)
var coborrower = new AppraisalContactBorrower();
if (groupedFieldValues.TryGetValue("CX.OS.AO.COBORRNAME", out var name)) {
coborrower.Name = name.Value;
}
if (groupedFieldValues.TryGetValue("CX.OS.AO.BORRCONTACTZIP", out var zip)) {
borrower.Zip = zip.Value;
}
}
Using Dictionary makes it faster to check the appropriate field existence as it is O(1) and with TryGetValue you combine two operations into one (existence check + obtaining the value).
Your two last statements are almost identitical. The equivalent of :
if (groupedFieldValues.TryGetValue("CX.OS.AO.COBORRNAME", out var name)) {
coborrower.Name = name.Value;
}
is:
coborrower.Name = fieldValues.FirstOrDefault(v => v.FieldId == "CX.OS.AO.COBORRNAME")
?? coborrower.Name;
In the original code, coborrower.Name is not updated if the field doesn't exist in the list.
I'm working on a course listing in C# and an course can have up to 5 dates of when they are running. Ideally, the next date after today in the future would be selected, and ordered accordingly in a list.
What i have so far is a course list that gets the next date, and displays it, but it displays all the events without dates first (Null/Blank). I'm trying to show the courses with next dates first, and then those without after this.
C# Code:
public ActionResult FilterList(string role = null, string category = null)
{
return View("~/Views/FilterList.cshtml", GetCourses(role, category));
}
[NonAction]
public List<IEnumerable<Course>> GetCourses(string role = null, string category = null)
{
var collection = new List<IEnumerable<Course>>();
var items = Sitecore.Context.Database.GetItem(SitecoreIDs.Pages.CourseRoot)
.Children.Where(m => m.TemplateID == Course.TemplateID)
.Select(m => (Course)m).ToList();
var dates = new List<FilterDates>();
items.ForEach(m => dates.Add(new FilterDates
{
Dates = new List<DateTime>{ m.Date1, m.Date2, m.Date3, m.Date4, m.Date5 },
Name = m.Name
}));
dates.ForEach(m => m.Dates.RemoveAll(n => n == new DateTime(0001, 01, 01)));
dates.ForEach(m => m.Dates.Sort((a, b) => a.CompareTo(b)));
dates = dates.OrderBy(m => m.Dates.AsQueryable().FirstOrDefault(n => n - DateTime.Now >= TimeSpan.Zero)).ToList();
var model = new List<Course>();
dates.ForEach(m => model.Add(items.AsQueryable().FirstOrDefault(n => n.Name == m.Name)));
if (!string.IsNullOrEmpty(role) || !string.IsNullOrEmpty(category))
{
var currentRole = Sitecore.Context.Database.GetItem(SitecoreIDs.Pages.CategoryRoot)
.Children.AsQueryable().FirstOrDefault(m => m.Fields["Key"].Value == role);
if (!string.IsNullOrEmpty(category))
{
var currentCategory = Sitecore.Context.Database.GetItem(SitecoreIDs.Pages.SeriesRoot)
.Children.AsQueryable().FirstOrDefault(m => m.Fields["Key"].Value == category);
model = model.Where(m => m.Series == currentCategory.Name).ToList();
if (string.IsNullOrEmpty(role))
{
collection.Add(model);
}
}
if (!string.IsNullOrEmpty(role))
{
model = model.Where(m => m.InnerItem.Children.Where(n => n.Fields["Key"].Value == currentRole.Name).Any()).ToList();
List<Course> required = new List<Course>(), recommended = new List<Course>(), refresh = new List<Course>();
foreach (var item in model)
{
foreach (Item inner in item.InnerItem.Children)
{
if (inner.Fields["Key"].Value == currentRole.Name)
{
switch (inner.Fields["Severity"].Value)
{
case "Required":
required.Add(item);
break;
case "Recommended":
recommended.Add(item);
break;
case "Refresh":
refresh.Add(item);
break;
}
}
}
}
collection.Add(required);
collection.Add(recommended);
collection.Add(refresh);
}
}
else
{
collection.Add(model);
}
return collection;
}
I've tried different orderbys, but can't seem to get the ordering right. Any help would be greatly appreciated.
Andy
The code you posted has some extra stuff that seems unrelated to your question about sorting. I am ignoring that and just addressing the question at hand: how to sort your courses so that the ones with the nearest future date are first.
I would create a little method to return the next future date or DateTime.MaxValue as the "null" value.
private DateTime GetNextFutureDate(Course course)
{
var dates =
new[] {course.Date1, course.Date2, course.Date3, course.Date4, course.Date5}.Where(d => d > DateTime.Now).ToArray();
return dates.Length == 0 ? DateTime.MaxValue : dates[0];
}
Then in your GetCourses method you could use it like this:
[NonAction]
public List<IEnumerable<Course>> GetCourses(string role = null, string category = null)
{
var collection = new List<IEnumerable<Course>>();
var model = Sitecore.Context.Database.GetItem(SitecoreIDs.Pages.CourseRoot)
.Children.Where(m => m.TemplateID == Course.TemplateID)
.Select(m => (Course)m).OrderBy(m => GetNextFutureDate(m));
if (!string.IsNullOrEmpty(role) || !string.IsNullOrEmpty(category))
// ... the rest of your code ...
return collection;
}
You might also want to consider making GetNextFutureDate a member or extension method on your Course class.
Title could be misleading, so an example:
I have a class:
class Pair
{
Book Book1;
Book Book2;
}
I have a list of these:
var list = new List<Pair>();
list.Add(new Pair() {
Book1 = new Book() { Id = 123 },
Book2 = new Book() { Id = 456 }
});
list.Add(new Pair() {
Book1 = new Book() { Id = 456 },
Book2 = new Book() { Id = 123 }
});
Now, despite the fact the books are 'flipped', my system should treat these as duplicates.
I need a method to remove one of these 'duplicates' from the list (any one - so let's say the first to make it simple).
What I've Tried
var tempList = new List<Pair>();
tempList.AddRange(pairs);
foreach (var dup in pairs)
{
var toRemove = pairs.FirstOrDefault(o => o.Book1.Id == dup.Book2.Id
&& o.Book2.Id == dup.Book1.Id);
if (toRemove != null)
tempList.Remove(toRemove);
}
return tempList;
This returns no items (given the example above), as both Pair objects would satisfy the condition in the lambda, I only one to remove one though.
NOTE: This wouldn't happen if I just removed the element from the collection straight away (rather than from a temporary list) - but then I wouldn't be able to iterate over it without exceptions.
You can set up an IEqualityComparer<Pair> concrete class and pass that to the .Distinct() method:
class PairComparer : IEqualityComparer<Pair>
{
public bool Equals(Pair x, Pair y)
{
return (x.Book1.Id == y.Book1.Id && x.Book2.Id == y.Book2.Id)
|| (x.Book1.Id == y.Book2.Id && x.Book2.Id == y.Book1.Id);
}
public int GetHashCode(Pair obj)
{
return obj.Book1.Id.GetHashCode() ^ obj.Book2.Id.GetHashCode();
}
}
And then use it like so:
var distinctPairs = list.Distinct(new PairComparer());
The problem is that you are removing the both duplicates.
Try this:
var uniquePairs = list.ToLookup( p => Tuple.Create(Math.Min(p.Book1.Id, p.Book2.Id), Math.Max(p.Book1.Id, p.Book2.Id)) ).Select( g => g.First() ).ToList();
I would use the following
foreach (var dup in pairs)
{
var toRemove = pairs.FirstOrDefault(o => o.Book1.Id == dup.Book2.Id
&& o.Book2.Id == dup.Book1.Id
&& o.Book1.Id > o.Book2.Id);
if (toRemove != null)
tempList.Remove(toRemove);
}
This will specifically remove the duplicate that is "out of order". But this (and your original) will fail if the duplicate pairs have the books in the same order.
A better solution (since we're looping over ever pair anyways) would be to use a HashSet
var hashSet = new HashSet<Tuple<int,int>>();
foreach (var item in pairs)
{
var tuple = new Tuple<int,int>();
if (item.Book1.Id < item.Book2.Id)
{
tuple.Item1 = item.Book1.Id;
tuple.Item2 = item.Book2.Id;
}
else
{
tuple.Item1 = item.Book2.Id;
tuple.Item2 = item.Book1.Id;
}
if (hashSet.Contains(tuple))
{
tempList.Remove(dup);
}
else
{
hashSet.Add(tuple);
}
}
I've managed to find a solution, but it's one I'm not happy with. It seems too verbose for the job I'm trying to do. I'm now doing an additional check to see whether a duplicate has already been added to the list:
if(toRemove != null && tempList.Any(o => o.Book1.Id == toRemove.Book2.Id
&& o.Book2.Id == toRemove.Book1.Id))
tempList.Remove(toRemove);
I'm very much open to alternative suggestions.
I'm looking to fill an object model with the count of a linq-to-sql query that groups by its key.
The object model looks somewhat like this:
public class MyCountModel()
{
int CountSomeByte1 { get; set; }
int CountSomeByte2 { get; set; }
int CountSomeByte3 { get; set; }
int CountSomeByte4 { get; set; }
int CountSomeByte5 { get; set; }
int CountSomeByte6 { get; set; }
}
This is what I have for the query:
var TheQuery = from x in MyDC.TheTable
where ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7
group x by x.SomeByte into TheCount
select new MyCountModel()
{
CountSomeByte1 = TheCount.Where(TheCount => TheCount.Key == 1)
.Select(TheCount).Count(),
CountSomeByte2 = TheCount.Where(TheCount => TheCount.Key == 2)
.Select(TheCount).Count(),
.....
CountSomeByte6 = TheCount.Where(TheCount => TheCount.Key == 6)
.Select(TheCount).Count(),
}.Single();
ListOfRecordIDs is list of longs that's passed in as a parameter. All the CountSomeByteN are underlined red. How do you do a count of grouped elements with the group's key mapped to an object model?
Thanks for your suggestions.
The select is taking each element of your group and projecting them to identical newly created MyCountModels, and you're only using one of them. Here's how I'd do it:
var dict = MyDC.TheTable
.Where(x => ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7)
.GroupBy(x => x.SomeByte)
.ToDictionary(grp => grp.Key, grp => grp.Count());
var result = new MyCountModel()
{
CountSomeByte1 = dict[1];
CountSomeByte2 = dict[2];
CountSomeByte3 = dict[3];
CountSomeByte4 = dict[4];
CountSomeByte5 = dict[5];
CountSomeByte6 = dict[6];
}
EDIT: Here's one way to do it in one statement. It uses an extension method called Into, which basically works as x.Into(f) == f(x). In this context, it can be viewed as like a Select that works on the whole enumerable rather than on its members. I find it handy for eliminating temporary variables in this sort of situation, and if I were to write this in one statement, it's probably how I'd do it:
public static U Into<T, U>(this T self, Func<T, U> func)
{
return func(self);
}
var result = MyDC.TheTable
.Where(x => ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7)
.GroupBy(x => x.SomeByte)
.ToDictionary(grp => grp.Key, grp => grp.Count())
.Into(dict => new MyCountModel()
{
CountSomeByte1 = dict[1];
CountSomeByte2 = dict[2];
CountSomeByte3 = dict[3];
CountSomeByte4 = dict[4];
CountSomeByte5 = dict[5];
CountSomeByte6 = dict[6];
});
Your range variable is not correct in the subqueries:
CountSomeByte6 = TheCount.Where(TheCount => TheCount.Key == 6)
.Select(TheCount).Count(),
In method notation you don't need the extra select:
CountSomeByte6 = TheCount.Where(theCount => theCount.Key == 6).Count(),
If you want to use it anyway:
CountSomeByte6 = TheCount.Where(theCount => theCount.Key == 6).Select(theCount => theCount).Count(),