I have a List<T> and trying to rank using Linq as I can with TSQL.
TSQL: RANK() OVER ( PARTITION BY [Time], [Filter] ORDER BY [Speed] Desc) AS speedrank
After the ranks are calculated trying to update the ranked column in the List<T> with the calculated rank.
I could write the data to a sql table and query using tsql but would prefer to use the strong typed list I have in the application if it's possible.
The Rank has to take in to account duplicates Speed values based on the partitioning, so sorting and incrementing row number does not work as expected.
Updated: List in MainForm.cs
private List<Ranking> _rankingList = new List<Ranking>();
Add to list.
var advancedsearchranking = new Ranking
{
Course = course,
RaceDate = racedate,
RaceTime = racetime,
RaceDayRaceNo = racenumber,
RaceDayHorseNo = horse.HNo,
Filter = "Going",
Horse = horse.HorseName,
WinPercentage = $"{winpercentage} ({wins}/{runs})",
Positions = sbpos.ToString(),
SpeedFigures = sbspeedratings.ToString(),
LastSpeedFigure = lastspeedfigure,
Average = Math.Round((double)average, 0),
BSPs = sbbsp.ToString()
};
_rankingList.Add(advancedsearchranking);
Class:
public class Ranking
{
public string Course { get; set; }
public DateTime RaceDate { get; set; }
public TimeSpan RaceTime { get; set; }
public int? RaceDayRaceNo { get; set; }
public int? RaceDayHorseNo { get; set; }
public string Filter { get; set; }
public string Horse { get; set; }
public string WinPercentage { get; set; }
public string Positions { get; set; }
public string SpeedFigures { get; set; }
public int? LastSpeedFigure { get; set; }
public int LastSpeedRank { get; set; }
public double? Average { get; set; }
public virtual string BSPs { get; set; }
public virtual double[] BSPSparkLine { get; set; }
public double? MasterAverage { get; set; }
}
I'm try to partition by Filter property and rank by LastSpeedFigure Desc, so highest figure is ranked 1 or joint 1st if two have the same value.
Regards,
Neil
You should be able to use the following code (sorry I haven't tested any of this).
var list = _rankingList.OrderBy(r => r.Filter).ThenByDescending(r => r.LastSpeedFigure);
int rank = 1;
int rowNumber = 1;
list[0].LastSpeedRank = 1; // set the first item
for (var i = 1; i < list.Count; i++)
{
if(list[i].Filter != list[i - 1].Filter) // reset numbering
{
rank = 1;
rowNumber = 1;
}
else
{
rowNumber++; // row-number always counts up
if(list[i].LastSpeedFigure != list[i - 1].LastSpeedFigure)
rank = rowNumber; // only change rank if not tied
}
list[i].LastSpeedRank = rank;
}
You can also implement an IEnumerable extension to do this
public T WithRank<T>(
this IEnumerable<T> source,
Func<T, T, bool> partitioning,
Func<T, T, bool> ordering,
Action<T, int> setRank)
{
using var enumer = source.GetEnumerator();
if(!enumer.MoveNext())
yield break;
var previous = enumer.Current;
setRank(previous, 1);
yield return previous;
int rank = 1;
int rowNumber = 1;
while(enumer.MoveNext())
{
if(!partitioning(enumer.Current,previous)) // reset numbering
{
rank = 1;
rowNumber = 1;
}
else
{
rowNumber++; // row-number always counts up
if(ordering(enumer.Current, previous))
rank = rowNumber; // only change rank if not tied
}
setRank(previous, rank);
yield return enumer.Current;
}
}
Use it like this
// the list must be pre-sorted by partitioning values then ordering values.
var list = _rankedList
.OrderBy(r => r.Filter)
.ThenByDescending(r => r.LastSpeedFigure)
.WithRanking(
(a, b) => a.Filter == b.Filter,
(a, b) => a.LastSpeedFigure == b.LastSpeedFigure,
(o, rank) => { o.LastSpeedRank = rank; })
.ToList();
You can implement ROW_NUMBER by just using the rowNumber variable and removing the conditional rank = rowNumber; statement. You can implement DENSE_RANK by changing that line to rank++; and ignoring rowNumber.
Related
Is there any way i can get number of child with max parent id and list the result with linq?
I'm trying to bring the total of values by Status, but i can only get the last one from the child
what I have done so far:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var lstRfq = new List<RfqEvents>()
{
new RfqEvents(1,1,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(2,2,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(3,2,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(4,3,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(5,4,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(6,5,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(7,5,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(8,5,DateTime.Parse("2021-05-06 00:00:00+00"),3),
new RfqEvents(9,6,DateTime.Parse("2021-05-06 00:00:00+00"),3),
new RfqEvents(10,6,DateTime.Parse("2021-05-06 00:00:00+00"),3),
};
var subquery = from c in lstRfq
group c by c.RfqId into g
select new InternalStatusInformations
{
RfqId = g.Key,
RfqEventId = g.Max(a => a.Id),
StatusId = g.Select(p => p.Status).FirstOrDefault()
};
var sss = from d in lstRfq.Where(p=> subquery.Select(p=>p.RfqEventId).Contains(p.Id))
group d by d.Status into z
select new InternalStatusInformations
{
StatusId = z.Key,
Total = z.Count(),
Past = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date < DateTime.Now.Date).Count(),
Future = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date > DateTime.Now.Date).Count(),
Today = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date == DateTime.Now.Date).Count(),
FiveDays = z.Where(p => (p.DueDate.HasValue && p.DueDate.Value.Date > DateTime.Now.Date) && p.DueDate.HasValue && p.DueDate.Value.Date < DateTime.Now.Date.AddDays(5)).Count(),
};
//expected: Status 1: 2 values
// Status 2: 3 values
// Status 3: 2 value
//output: Status 1: 2 values
// Status 2: 2 values
// Status 3: 2 values
sss.Dump();
}
public class InternalStatusInformations
{
public int RfqEventId { get; set; }
public int RfqId { get; set; }
public int StatusId { get; set; }
public int Future { get; set; }
public int Past { get; set; }
public int Today { get; set; }
public int FiveDays { get; set; }
public int Total { get; set; }
public DateTime? DueDate { get; set; }
}
public class RfqEvents
{
public RfqEvents(int id, int rfqId, DateTime? dueDate, int status)
{
Id = id;
RfqId = rfqId;
DueDate = dueDate;
Status = status;
}
public int Id { get; set; }
public DateTime? DueDate { get; set; }
public int RfqId { get; set; }
public int Status { get; set; }
}
}
https://dotnetfiddle.net/YoRsIG
but something is not right with the results, could you guys help me?
If you just want to count the number of distinct RfqId values in each status, this should do it:
var pairs = lstRfq
.GroupBy(evt => evt.Status)
.Select(grouping =>
{
var status = grouping.Key;
var count = grouping
.Select(evt => evt.RfqId)
.Distinct()
.Count();
return (status, count);
});
foreach ((var status, var count) in pairs)
{
Console.WriteLine($"Status {status}: {count} values");
}
Output is:
Status 1: 2 values
Status 2: 3 values
Status 3: 2 values
What is coming in to the webAPI is this JSON string and becomes deserilized in to this:
List<AddAssignMealView> mealtraiDeserializeObjects = JsonConvert.DeserializeObject<List<AddAssignMealView>>(mealTrainee);
mealtraiDeserializeObjects contains five index's one for each day of the week. Inside that specific index is a class that looks like what is below:
public class AddAssignMealView
{
public int TraineeID { get; set; }
public string DayOfTheWeek { get; set; }
public List<string> MealTypes { get; set; }
public List<int> MealID { get; set; }
public List<string> MealName { get; set; }
}
What I am trying to do is, be able to create a list of MealTrainee(Entity Framework):
public partial class MealTrainee
{
public int MealTraineeID { get; set; } //Ignore this one due to it being a post
public int MealID { get; set; }
public int TraineeID { get; set; }
public string DayOfTheWeek { get; set; }
public string MealType { get; set; }
public string MealName { get; set; }
public virtual Meal Meal { get; set; }
}
So I can be able to use addrange and add the list to the database. I understand how to use zip and combined two list types together if it is a single element. This is different due to it being a list of five elements and each of those five elements containing three lists. If someone could point me in the right direction, that would be helpful.
You can Zip 2 times to combine values from 3 lists into series of tuples. You can use SelectMany to flatten results. For example:
var result = mealtraiDeserializeObjects.SelectMany(c =>
c.MealID.Zip(c.MealName, (id,name) => new {id, name})
.Zip(c.MealTypes, (prev, type) => new {prev.id, prev.name, type})
.Select(r => new MealTrainee
{
TraineeID = c.TraineeID,
DayOfTheWeek = c.DayOfTheWeek,
MealID = r.id,
MealName = r.name,
MealType = r.type,
}));
This is the solution I found. I took it day by day in the sense that the first iteration would be Monday and then the count of meals(Ex:Meal for breakfast, Meal for Lunch) and put them in a count which would be "mealweek". Then I took mealweek and created a new mealtrainee for each count. Then after I made the meal out of the mealtrainee I put it in db.MealTrainees.AddRange and posted all the records.
[ResponseType(typeof(MealTrainee))]
public IHttpActionResult PostMealTrainee([FromBody] string mealTrainee)
{
List<MealTrainee> meals = new List<MealTrainee>();
using (DbContextTransaction dbContextTransaction = db.Database.BeginTransaction())
{
try
{
List<AddAssignMealView> mealtraiDeserializeObjects = JsonConvert.DeserializeObject<List<AddAssignMealView>>(mealTrainee);
foreach (var mealtraiDeserializeObject in mealtraiDeserializeObjects)
{
var mealWeek = mealtraiDeserializeObject.MealID.Select((m, i) => new
{
mealtraiDeserializeObject.TraineeID,
mealtraiDeserializeObject.DayOfTheWeek,
MealID = m,
MealTypes = mealtraiDeserializeObject.MealName[i],
MealName = mealtraiDeserializeObject.MealTypes[i]
}).ToList();
var meal = mealWeek.Select(x => new MealTrainee()
{
DayOfTheWeek = x.DayOfTheWeek,
MealID = x.MealID,
MealName = x.MealName,
MealType = x.MealTypes,
TraineeID = x.TraineeID
}).ToList();
db.MealTrainees.AddRange(meal);
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.SaveChanges();
dbContextTransaction.Commit();
return Ok(meals);
}
catch (Exception e)
{
dbContextTransaction.Rollback();
Logger.Log(e);
return BadRequest();
}
}
}
How do I filter an item from a list based on two different columns one being a number(smallest number) using LINQ C#?
public class Line
{
public int Id { get; set; }
public List<LineItem> LineItems { get; set; }
public Line()
{
LineItems = new List<LineItem> {
new LineItem{Num = 1, Name="i", Qty = 0, Active = false},
new LineItem{Num = 2, Name="j", Qty = 2,Active = false},
new LineItem{Num = 3, Name="k", Qty = 3,Active = false},
};
}
}
public class LineItem
{
public int Num { get; set; }
public string Name { get; set; }
public int Qty { get; set; }
public bool Active { get; set; }
}
I want to filter this list and get a LineItem based on Qty = 0 and smallest num value.
Try filtering by Qty == 0, sorting according to Num and keep the first one:
var lineItem = LineItems.Where(l => l.Qty == 0).OrderBy(l => l.Num).FirstOrDefault();
or just keep the first that Qty equals to 0 and Num equals to minimum possible:
var minNum = LineItems.Where(l => l.Qty == 0).Min(l => l.Num);
var lineItem = LineItems.FirstOrDefault(l => l.Qty == 0 && l.Num == minNum);
You could try to get the min value for Qty is 0 and order by Num in ascending mode, then taking the first item. For sample:
var item = LineItems.Where(x => x.Qty == 0).OrderBy(x => x.Num).First();
If you have your LineItem class implement IComparable<T>, then you can do something like this:
public class LineItem : IComparable<LineItem>
{
public int Num { get; set; }
public string Name { get; set; }
public int Qty { get; set; }
public bool Active { get; set; }
public int CompareTo(LineItem other)
{
if (other.Num > this.Num)
return -1;
else if (other.Num == this.Num)
return 0;
else
return 1;
}
}
then
var item = l.LineItems.Where(p => p.Qty == 0).Min();
I have two objects of same type with different values:
public class Itemi
{
public Itemi()
{
}
public int Prop1Min { get; set; }
public int Prop1Max { get; set; }
public int Prop2Min { get; set; }
public int Prop2Max { get; set; }
public int Prop3Min { get; set; }
public int Prop3Max { get; set; }
...................................
public int Prop25Min { get; set; }
public int Prop25Max { get; set; }
}
Now I instantiate two objects of this type and add some values to their properties.
Itemi myItem1 = new Itemi();
myItem1.Prop1Min = 1;
myItem1.Prop1Max = 4;
myItem1.Prop2Min = 2;
myItem1.Prop2Max = 4;
myItem1.Prop3Min = -1;
myItem1.Prop3Max = 5;
.............................
myItem1.Prop25Min = 1;
myItem1.Prop25Max = 5;
Itemi myItem2 = new Itemi();
myItem2.Prop1Min = 1;
myItem2.Prop1Max = 5;
myItem2.Prop2Min = -10;
myItem2.Prop2Max = 3;
myItem2.Prop3Min = 0;
myItem2.Prop3Max = 2;
................................
myItem2.Prop25Min = 3;
myItem2.Prop25Max = 6;
What is the best and fastest way to do this comparison:
take each properties from myItem1 and check if values from Prop1-25 Min and Max are within the range values of myItem2 Prop1-25 Min and Max
Example:
myItem1.Prop1Min = 1
myItem1.Prop1Max = 4
myItem2.Prop1Min = 1
myItem2.Prop1Max = 5
this is True because mtItem1 Prop1 min and max are within the range of myItem2 min and max.
the condition should be AND in between all properties so in the end after we check all 25 properties if all of them are within the range of the second object we return true.
Is there a fast way to do this using Linq or other algorithm except the traditional if-else?
I would refactor the properties to be more along the lines of:
public class Item
{
public List<Range> Ranges { get; set; }
}
public class Range
{
public int Min { get; set; }
public int Max { get; set; }
}
Then your comparison method could be:
if (myItem1.Ranges.Count != myItem2.Ranges.Count)
{
return false;
}
for (int i = 0; i < myItem1.Ranges.Count; i++)
{
if (myItem1.Ranges[i].Min < myItem2.Ranges[i].Min ||
myItem1.Ranges[i].Max > myItem2.Ranges[i].Max)
{
return false;
}
}
return true;
Otherwise you will have to use Reflection, which is anything but fast.
Linq is using standart statements like if...then, for each and other, there is no magic :)
If the final goal only to compare, without needing to say, which properties are not in the range, then you not need to check them all, on the first unequals you can end checking.
Because you have so much properties, you must think about saving it in Dictionary, or List, for example. Or to use dynamic properties (ITypedList), if it will use for binding.
You really should do something like Ginosaji proposed.
But if you want to go with your current data model, here is how I would solve it. Happy typing. :)
public static bool RangeIsContained(int outerMin, int outerMax, int innerMin, int innerMax)
{
return (outerMin <= innerMin && outerMax >= innerMax);
}
public bool IsContained(Itemi outer, Itemi inner)
{
return RangeIsContained(outer.Prop1Min, outer.Prop1Max, inner.Prop1Min, inner.Prop1Max)
&& RangeIsContained(outer.Prop2Min, outer.Prop2Max, inner.Prop2Min, inner.Prop2Max)
// ...
&& RangeIsContained(outer.Prop25Min, outer.Prop25Max, inner.Prop25Min, inner.Prop25Max);
}
With your data model this is basically the only way to go except for reflection (slow!). LINQ cannot help you because your data is not enumerable.
For the sake of completeness, here is a LINQ solution (but it's less performant and less readable than Ginosaji's solution!)
public class Range
{
public int Min { get; set; }
public int Max { get; set; }
public static bool IsContained(Range super, Range sub)
{
return super.Min <= sub.Min
&& super.Max >= sub.Max;
}
}
public class Itemi
{
public Itemi()
{
properties = new Range[25];
for (int i = 0; i < properties.Length; i++)
{
properties[i] = new Range();
}
}
private Range[] properties;
public IEnumerable<Range> Properties { get { return properties; } }
public static bool IsContained(Itemi super, Itemi sub)
{
return super.properties
.Zip(sub.properties, (first, second) => Tuple.Create(first, second))
.All((entry) => Range.IsContained(entry.Item1, entry.Item2));
}
public Range Prop1
{
get { return properties[0]; }
set { properties[0] = value; }
}
public Range Prop2
{
get { return properties[1]; }
set { properties[1] = value; }
}
// ...
}
I have the following class objects:
public class VacancyCategory
{
public int ID { get; set; }
public string Text { get; set; }
public IList<VacancySubCategory> SubCategories { get; set; }
}
public class VacancySubCategory
{
public int ID { get; set; }
public string Text { get; set; }
public VacancyCategory Category { get; set; }
public IList<Vacancy> Vacancies { get; set; }
}
public class Vacancy : IBusinessObject
{
public int ID { get; set; }
public string Title { get; set; }
public VacancySubCategory SubCategory { get; set; }
public string Body { get; set; }
public VacancyWorkType WorkType { get; set; }
public string Salary { get; set; }
public DateTime? AppsClosingDate { get; set; }
public bool Active { get; set; }
}
...so in a test repository im creating test data like so:
private IList<VacancyCategory> GetVacancyCategoriesWithAllChildCollections()
{
IList<VacancyCategory> vacancyCategories = new List<VacancyCategory>();
int cCounter = 0;
int scCounter = 0;
int vCounter = 0;
for (int i = 1; i <= 3; i++)
{
VacancyCategory vc = new VacancyCategory();
vc.ID = ++cCounter;
vc.Text = "VacancyCategory" + i.ToString();
for (int j = 1; j <= 3; j++)
{
VacancySubCategory vsc = new VacancySubCategory();
vsc.ID = ++scCounter;
vsc.Text = "VacancySubCategory" + scCounter.ToString();
vsc.Category = vc;
for (int k = 1; k <= 2; k++)
{
Vacancy v = new Vacancy();
v.ID = ++vCounter;
v.Title = "Vacancy" + vCounter.ToString();
v.Body = "VacancyBody" + vCounter.ToString();
v.Active = vCounter >= 16 ? false : true;
v.WorkType = this._workTypes.Single(wt => wt.ID == k);
v.Salary = vCounter <= 7 ? "SR " + (vCounter * 1000).ToString() : "";
v.AppsClosingDate = (vCounter >= 3 & vCounter <= 13) ? (new DateTime(2009, 3, vCounter)) : (DateTime?)null;
v.SubCategory = vsc;
if (vsc.Vacancies == null)
vsc.Vacancies = new List<Vacancy>();
vsc.Vacancies.Add(v);
}
if (vc.SubCategories == null)
vc.SubCategories = new List<VacancySubCategory>();
vc.SubCategories.Add(vsc);
}
vacancyCategories.Add(vc);
}
return vacancyCategories;
}
..so now i have some good test data. the object tree / chained objects are important to me.
so i'd like to return the individual object collections from this tree when desired. for example, if i wanted the whole tree, i can just return the VacancyCategory list with all the child objects - great. but now i want to return just the VacancySubCaregory items (all 9 of them). this would be my public method to the test repository:
public IQueryable<VacancySubCategory> GetVacancySubCategories()
{
throw new NotImplementedException("write gen code");
}
.. obviously without the exception. i have a member field called _categories that contains the results from the GetVacancyCategoriesWithAllChildCollections method. so i've been trying stuff like
this._categories.Select( ......
..but i cant seem to return a list of VacancySubCategory objects. i seem to always be selecting the root collection (ie. a result set of VacancyCategory objects). What am i doing wrong? im sure its simple... but its driving me nuts!
EDIT
thanx matt.
your suggestion led me to this:
public IQueryable<VacancySubCategory> GetVacancySubCategories()
{
return this._categories.SelectMany(c => c.SubCategories).AsQueryable<VacancySubCategory>();
}
..which works great. you're a champ
Try:
return this._categories.SelectMany(c => c.SubCategories);
This should work.
var query = from vc in GetVacancyCategoriesWithAllChildCollections()
from vcs in vc.SubCategories
select vcs