How to find first N items with Min values using LINQ - c#

I am trying to get the first 6 items of offerList that have Min RegularPrice value and whose OfferCode contains "dtv". I tried the following LINQ but it retrieves only one item instead of 6. What am I doing wrong?
List<Offer> dtvOffers = offerList.Where(x =>
(x.ListPrice.CommodityPrice.RegularPrice == offerList.Min(y =>
y.ListPrice.CommodityPrice.RegularPrice)) &&
(x.OfferCode.ToLower().Contains("dtv")))
.Take(6).ToList();

Order by RegularPrice and take the first 6 rows.
offerList.Where(x => x.OfferCode.ToLower().Contains("dtv"))
.OrderBy(x.ListPrice.CommodityPrice.RegularPrice)
.Take(6)
.ToList();
This will give you the first six records with the lowest price.

The only plausible explanation to this is that there are not 6 items which remain after your filter.
The Take will take 6 if there are 6 or more items after filter. If not it take what's left. Can also return a blank collection if none left.
Oh and BTW, calculate this line before hand. No use, evaluating for each and every iteration.
var min = offerList.Min(y => y.ListPrice.CommodityPrice.RegularPrice);
List<Offer> dtvOffers = offerList.Where(x =>
(x.ListPrice.CommodityPrice.RegularPrice == min) &&
(x.OfferCode.ToLower().Contains("dtv")))
.Take(6).ToList();

Related

Check if tie in count of lists using linq

States have cities. I need the state with most cities only if there is no tie. Tie means top 2 states have the same number of cities.
var stateWithMostCities = _states
.OrderByDescending(_p => _p.cities.Count())
.Take(2)
.ToList();
Now I can check if the city count of first state = second state and determine if there is a tie. However iam asking if this can achieved on the same line shown above using takewhile, skip and other creative uses of linq. Thanks
Something like this?
var stateWithMostCitiesWithoutATie =_states.GroupBy(_p => _p.cities.Count())
.OrderByDescending(g=>g.Key)
.FirstOrDefault(g=> g.Count()==1? g.First():null);
The key is, as #Mong Zhu pointed out to Group by the counts of cities, after that you can order by desc to get the max, and if the max group has more than one then you have a tie
Technically, you can use Aggregate over ordered states:
// the state with maximum cities; null in case of tie
var stateWithMostCities = _states
.OrderByDescending(state => state.cities.Count())
.Take(2) // at most 2 items to analyze
.Aggregate((s, a) => s.cities.Count() == a.cities.Count() ? null : s);
But I doubt if you should do this: comparing top 2 states is more readable.

Get max version within a date period query

I am trying to write a LINQ query that gets all the records and groups them by Period i.e. Sep-18 and then returns the record with the highest Version number within the periods. For example if I have three periods contained within my periodNames list the output list should return:
Sep-18
Versions: 1, 2, 3 (Returns record with version 3)
Oct-18
Versions: 1, 2 (Returns record with version 2)
Nov-18
Versions: 1, 2, 3, 4 (Returns record with version 4)
This is the query I have written so far:
var previousStatements = _context.Statements.Where(x => periodNames.Contains(x.Period) &&
x.Version == _context.Statement.Max(y => y.Version)).toList();
How can I adapt this to the above specification? Thanks
You can use GroupBy in order to group the statements and Max in order to find the maximum value, e.g.
var previousStatements = _context.Statements.Where(x => periodNames.Contains(x.Period))
.GroupBy(x => x.Period)
.Select(x => new { Period = x.Key, MaxVersion = x.Max(y => y.Version))
.ToList();
The code above returns the Period and the maximum version number only. If you need the record with the highest version number for each period, you can use this:
var previousStatements = (ctx.Items.Where(x => periodNames.Contains(x.Period))
.GroupBy(x => x.Period)
.ToArray())
.Select(x => x.OrderByDescending(y => y.Version).First())
.ToList();
Please note that the code above first uses a call to ToArray to send the GroupBy-query to the database. From the returned groups, the row with the highest version number for each period is then retrieved in memory.
Try to use GroupBy and then orderbydescending for the max versión:
_context.GroupBy(f => f.Period).Select(f=>f.OrderByDescending(r=>r.Version).First()).ToList();
I think you would have known your solution if you would have written a proper requirement
You wrote:
...groups them by Period i.e. Sep-18 and then returns the highest Version number within the periods
Your examples don't return the highest version number but the row with the highest version number, so let's assume that is what you want:
From a sequence of Statements, group these statements into groups of statements with equal Period, and return from every group, the statement with the largest VersionNumber.
You haven't defined what you want if two statements within the same Period have the same VersionNumber. Let's assume you think that this will not occur, so you don't care which one is returned in that case.
So you have sequence of Statements, where every Statement has a Period and a VersionNumber.
Officially you haven't defined the class of Period and VersionNumber, the only thing we know about them is that you have some code that can decide whether two Periods are equal, and you have something where you can decide which VersionNumber is larger.
IEqualityComparer<Period> periodComparer = ...
IComparer<VersionNumber> versionComparer = ...
If Period is similar to a DateTime and VersionNumber is similar to an int, then these comparers are easy, otherwise you'll need to write comparers.
From your requirement the code is simple:
Take all input statements
Make groups of statements with equal Period
From every group of statements with this Period keep only the one with the highest VersionNumber
IEnumerable<Statement> statements = ...
var latestStatementsWithinAPeriod = statements
.GroupBy(statement => statement.Period, // group by same value for Period
(period, statementsWithThisPeriod) =>
// From every group of statements keep only the one with the highest VersionNumber
// = order by VersionNumber and take the first
statementWithThisPeriod
.OrderByDescending(statement => statement.VersionNumber,
versionComparer)
.FirstOrDefault(),
periodComparer);
Once again: if default comparers can be used to decide when two Periods are equal and which VersionNumber is larger, you don't need to add the comparers.
The disadvantage of the SorBy is that the 3rd and 4rd element etc are also sorted, while you only need the first element, which is the one with the largest VersionNumber.
This can be optimized by using the less commonly used Aggregate:
(period, statementsWithThisPeriod) => statementWithThisPeriod.Aggregate(
(newestStatement, nextStatement) =>
(versionComparer.Compare(newestStatement.VersionNumber, nextStatement.VersionNumber) >=0 ) ?
newestStatement :
nextStatement)
This will put the first statement as the newestStatement (= until now this was the one with the highest version number). The 2nd element will be put in nextStatement. both statements will be compared, and if nextStatement has a VersionNumber larger than newestStatement, then nextStatement will be considered to be newer, and thus will replace newestStatement. The end of the Aggregate will return newestStatement
You can try with GroupBy and OrderByDescending and then take first one.
var statements = _context.Statements
.Where(x => periodNames.Contains(x.Period))
.GroupBy(g => g.Period)
.Select(s => s.OrderByDescending(o => o.Version)
.FirstOrDefault()).ToList();

get priority items from a list and insert them into the same list at given position c#

I have a list of 50 sorted items(say) in which few items are priority ones (assume they have flag set to 1).
By default, i have to show the latest items (based on date) first, but the priority items should appear after some 'x' number of records. Like below
index 0: Item
index 1: Item
index 2: Priority Item (insert priority items from this position)
index 3: Priority Item
index 4: Priority Item
index 5: Item
index 6: Item
The index 'x' at which priority items should be inserted is pre-defined.
To achieve this, i am using following code
These are my 50 sorted items
var list= getMyTop50SortedItems();
fetching all priority items and storing it in another list
var priorityItems = list.Where(x => x.flag == 1).ToList();
filtering out the priority items from main list
list.RemoveAll(x => z.flag == 1);
inserting priority items in the main list at given position
list.InsertRange(1, priorityRecords);
This process is doing the job correctly and giving me the expected result. But am not sure whether it is the correct way to do it or is there any better way (considering the performance)?
Please provide your suggestions.
Also, how is the performance effected as i am doing many operations (filter, remove, insert) considering the increase in number of records from 50 to 100000(any number).
Update: How can i use IQueryable to decrease the number of operations on list.
As per documentation on InsertRange:
This method is an O(n * m) operation, where n is the number of
elements to be added and m is Count.
n*m isn't too very good, so I would use LINQ's Concat method to create a whole new list from three smaller lists, instead of modifying an existing one.
var allItems = getMyTop50();
var topPriorityItems = list.Where(x => x.flag == 1).ToList();
var topNonPriorityItems = list.Where(x => x.flag != 1).ToList();
var result = topNonPriorityItems
.Take(constant)
.Concat(topPriorityItems)
.Concat(topNonPriorityItems.Skip(constant));
I am not sure how fast the Concat, Skip and Take methods for List<T> are, though, but I'd bet they are not slower than O(n).
It seems like the problem you're actually trying to solve is just sorting the list of items. If this is the case, you don't need to be concerned with removing the priority items and reinserting them at the correct index, you just need to figure out your sort ordering function. Something like this ought to work:
// Set "x" to be whatever you want based on your requirements --
// this is the number of items that will precede the "priority" items in the
// sorted list
var x = 3;
var sortedList = list
.Select((item, index) => Tuple.Create(item, index))
.OrderBy(item => {
// If the original position of the item is below whatever you've
// defined "x" to be, then keep the original position
if (item.Item2 < x) {
return item.Item2;
}
// Otherwise, ensure that "priority" items appear first
return item.Item1.flag == 1 ? x + item.Item2 : list.Count + x + item.Item2;
}).Select(item => item.Item1);
You may need to tweak this slightly based on what you're trying to do, but it seems much simpler than removing/inserting from multiple lists.
Edit: Forgot that .OrderBy doesn't provide an overload that provides the original index of the item; updated answer to wrap the items in a Tuple that contains the original index. Not as clean as the original answer, but it should still work.
This can be done using a single enumeration of the original collection using linq-to-objects. IMO this also reads pretty clearly based on the original requirements you defined.
First, define the "buckets" that we'll be sorting into: I like using an enum here for clarity, but you could also just use an int.
enum SortBucket
{
RecentItems = 0,
PriorityItems = 1,
Rest = 2,
}
Then we'll define the logic for which "bucket" a particular item will be sorted into:
private static SortBucket GetBucket(Item item, int position, int recentItemCount)
{
if (position <= recentItemCount)
{
return SortBucket.RecentItems;
}
return item.IsPriority ? SortBucket.PriorityItems : SortBucket.Rest;
}
And then a fairly straightforward linq-to-objects statement to sort first into the buckets we defined, and then by the original position. Written as an extension method:
static IEnumerable<Item> PrioritySort(this IEnumerable<Item> items, int recentItemCount)
{
return items
.Select((item, originalPosition) => new { item, originalPosition })
.OrderBy(o => GetBucket(o.item, o.originalPosition, recentItemCount))
.ThenBy(o => o.originalPosition)
.Select(o => o.item);
}

Search List FirstOrDefault StartsWith fuzzy

If I use the following code I will find an Item.ShowName starting with “X” – if one exists.
List<Artist> myList = new List<Artist>();
//Fill list with items
Artist Item = myList.FirstOrDefault(x => x.StartsWith("X"));
My problem is if there is no Item.ShowName starting with “X”. In that case I want the nearest match, i.e. the first Item starting with “Y” or the last Item with “W” in my list.
Obviously I can enumerate through the whole list but this might be slow. What is a fast way to get the result?
Here is a little trick you can use to do so :
Artist Item = myList.Where(s => !String.IsNullOrEmpty(s))
.OrderBy(x => Math.Abs(x[0] - (int)'X')).FirstOrDefault();
Convert 'X' and first character of x to integer, order by the absolute value of the difference.

Linq performance query

I have this query that gives the correct results but it takes about 15 seconds to run
int Count= P.Pets.Where(c => !P.Pets.Where(a => a.IsOwned == true)
.Select(a => a.OwnerName).Contains(c.OwnerName) && c.CreatedDate >=
EntityFunctions.AddDays(DateTime.Now, -8)).GroupBy(b=>b.OwnerName).Count();
If I remove this part of the linq
'&& c.CreatedDate >= EntityFunctions.AddHours(DateTime.Now, -8)'
It only takes about 3 seconds to run. How can I keep the same condition happening but a lot faster?
I need that date criteria because I don't want any Classeses that were created 8 days old to be included in the count
Edit
I have a table by the name of People which is referred to in this query as P and I want to return a count of the total of Pets they are that do not have a owner and remove the ones from the query that don't do have an owner even if they exist in another Pet reference has not the owner of that Pet. Meaning if a person has at least one record in the Pets table to be considered as an owner of a pet than I want to remove all cases where that person exist in the return query and once that is done only return the Pets that have been created newer than 8 days
You should cache the date and put that evaluation first (since the DateTime evaluation should be faster than a Contains evaluation). Also avoid recalculating the same query multiple times.
DateTime eightDaysOld = EntityFunctions.AddHours(DateTime.Now, -8);
//calculate these independently from the rest of the query
var ownedPetOwnerNames = P.Pets.Where(a => a.IsOwned == true)
.Select(a => a.OwnerName);
//Evaluate the dates first, it should be
//faster than Contains()
int Count = P.Pets.Where(c => c.CreatedDate >= eightDaysOld &&
//Using the cached result should speed this up
ownedPetOwnerNames.Contains(c.OwnerName))
.GroupBy(b=>b.OwnerName).Count();
That should return the same results. (I hope)
You are loosing any ability to use indices with that snippet, as it calculates that static date for every row. Declare a DateTime variable before your query and set it to DateTime.Now.AddHours(-8) and use the variable instead of your snippet in the where clause.
By separating the query and calling ToList() on it and inserting it in the master query make it go 4 times faster
var ownedPetOwnerNames = P.Pets.Where(a => a.IsOwned == true)
.Select(a => a.OwnerName).ToList();
int Count = P.Pets.Where(c => c.CreatedDate >= Date&&
ownedPetOwnerNames.Contains(c.OwnerName)).GroupBy(b=>b.OwnerName).Count();
You could use (and maybe first create) a navigation property Pet.Owner:
var refDate = DateTime.Today.AddDays(-8);
int Count= P.Pets
.Where(p => !p.Owner.Pets.Any(p1 => p1.IsOwned)
&& p.CreatedDate >= refDate)
.GroupBy(b => b.OwnerName).Count();
This may increase performance because the Contains is gone. At least it is better scalable than your two-phase query with a Contains involving an unpredictable number of strings.
Of course you also need to make sure there is an index on CreatedDate.

Categories

Resources