Search List FirstOrDefault StartsWith fuzzy - c#

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.

Related

Order list where ordering parameter is embedded in list item

I wish to order a list (descending number format) where the list items are strings in the format: n:text (n is a count of text contained within a source list that I built earlier), e.g:
5:word1
10:word2
124:word3
Output should be:
124:word3
10:word2
5:word1
Code so far:
newList.OrderBy(i => int.Parse(i.Split(':')[0]));
When I run, the list is unchanged. What am I doing wrong?
You need OrderByDescending to have the integers sort from highest to lowest. Also, the function doesn't modify your list, it returns a sequence which you can then convert to a list:
var sorted = newList.OrderByDescending(i => int.Parse(i.Split(':')[0])).ToList();
OrderBy does not change the order of your list but rather returns a IEnumerable<T> that is ordered. If you want to save it into your newList you could do:
newList = newList.OrderByDescending(i => int.Parse(i.Split(':')[0])).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);
}

Selecting first or default index of list item where property in list matches a property in another list?

I have this code:
int index = Convert.ToInt32(PointSeries.XValues.FirstOrDefault(e => this.PointsRects.Any(ep => ep.BottomLeft.X == e)));
This returns me the item in the PointSeries.XValues list where the x value matches the bottom left in the PointsRects list. I want the index of the item in the PointSeries.XValues list (of type List<double>).
Since you have List<T>, you can mix LINQ with the concrete FindIndex method specifically provided for that purpose:
int index = PointSeries.XValues.FindIndex(
e => this.PointsRects.Any(ep => ep.BottomLeft.X == e));
The returned value is
The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1.
In case PointRects list is big, you can further optimize it by building HashSet<double> and replacing Any with HashSet.Contains:
int index = PointSeries.XValues.FindIndex(
new HashSet<double>(this.PointsRects.Select(ep => ep.BottomLeft.X)).Contains);

Use LINQ to group by one field, sum based on group AND store on Dictionary

I am having a hard time to achieve a result likely because I'm misunderstanding sintax. Here the case:
I have a class that outputs a list. In this list there are two fields that I want specifically. they are ItemID (string) and TotalNet (string). There are other fields but I don't need to work with them at this point, but I do later at code.
The ItemID is the identifier of an Item, it is not unique in the list, so I must group it into one and account how many times this item appears. I achieved that with the following:
Dictionary<string, int> dstLoops = list.Select(l => l.ItemID).ToList()
.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
So I get my dictionary with the ItemID, now unique, with the total times that Item appears on my list.
The part that I am struggling to achieve is to sum the TotalNet of all the times the ItemID appears on the list.
I managed to get that using this:
var APTotal = from i in list group i by i.ItemID into g select new
{ total = g.Key, totals = g.Sum(i =>Convert.ToDecimal(i.TotalAmountPaid)) };
The problem is, it is not a dictionary, and later in the code that will be a headache... well... not much but I would like to avoid it, so I tried:
Dictionary<string, decimal> liststotal = list.Select(l => l.ItemID).ToList().
GroupBy(x => x).ToDictionary(g => g.Key, g => g.Sum(Convert.ToDecimal(item.TotalAmountPaid)));
Unfortunately it returns me that I cannot convert decimal to System.Func
I have tried some other solutions from this forum and other places, but they usually don't add values to the dictionary.
I can accept two scenarios as a solution:
Exactly what I requested above, where I will get a
dictionary with all Items from my List grouped by
ItemID and sum of their respective item.TotalNet;
FROM:
Item1, 10
Item2, 15
Item2, 15
TO:
Item1, 10
Item2, 30
OR:
2. a var/decimal/whatever that returns me the TotalNet of the current
ItemID iteration (I can use a foreach/for later on the code to
achieve the result I am looking for). So to be clear, this TotalNet
must be the sum of all TotalNets with the same ItemID from my list
that I am iterating at the moment. Something like "I'am on ItemID 1,
go and sum all the TotalNet of ItemID 1 from List".
Honestly... I would like help with the first option, for the sake of learning, but a solution is appreciated whatever the means (sacrificing children in name of an old god to get my output will not be marked as a solution...and possibly down voted).
Would something like this work?
list.GroupBy(x => x.ItemID)
.ToDictionary(x => x.Key,
x => x.Sum(t => Convert.ToDecimal(t.TotalAmountPaid)));
#bashis answer is functionally what you are looking for. I thought I could explain the logic a bit.
You have a list with items that have an ItemID as the identifier and TotalNet as the amount, given as a string. You would like to calculate the sum of TotalNet of all elements with the same ItemId, and store the results in a dictionary.
First, we begin with a GroupBy statement. It accepts a lambda used for determining what items in the list to group together. Its return value:
The GroupBy(IEnumerable, Func, Func) method returns a collection of IGrouping objects, one for each distinct key that was encountered. An IGrouping is an IEnumerable that also has a key associated with its elements.
So you get a collection of groups. Each group contains an enumeration of all the elements which returned the same value for the grouping expression that you entered into the GroupBy statement. In our example, all elements that have the same ItemId. Furthermore, the value of the ItemId used for each group is stored as the group's key.
The final step presented in this solution uses the ToDictionary method. Namely, this flavor:
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector
)
Here, we need to pass two funcs - one to select the key used for the dictionary, and the other to create the value that will be stored in our dictionary.
For the key selector, we choose the group's key, which in turn is the element's ItemId. For the value, remember that each group contains all the elements with the same ItemId, so we can just Sum the TotalAmountPaid value (converting to decimal from string) and get the desired result.

Using Linq lambdas, how can I get the first item in a two-key-sorted list?

I know this is simple, but my mind is playing tricks on me right now. If we have a flat list of objects with the properties GroupSortIndex and ItemSortIndex (within the group) and we want to find the first item in the list, what's the Linq/lambda for that?
About all I can think of is (meta, not literal code...)
var soughtItem = Source.OrderBy(ItemSortIndex).OrderBy(GroupSortIndex).ToList()[0]
...but that just looks so wrong to me for some reason.
Read post : Default Extension methods to get difference between first and firstordefault
you can use FirstOrDefualt() or First() function
var soughtItem = Source.OrderBy(ItemSortIndex).
ThenBy(GroupSortIndex).FirstOrDefualt();
if(soughtItem !=null)//advantage of using firstordefault
{
}
its better to use FirstOrDefualt because if there is no data it will return null intead of excetipn
You can use IOrderedEnumerable.ThenBy (Note: an IOrderedEnumerable is returned from IEnumerable.OrderBy):
var firstItem = source.OrderBy(s => s.GroupSortIndex)
.ThenBy(s => s.ItemSortIndex)
.First();
This orders first by the group and then by the item. You should use FirstOrDefault if the sequence can be empty. Otherwise First raises an exception.
(i've assumed that you want to order first by group and then by the item instead, since the ItemSortIndex is the index of the item within the group(as mentioned))
var soughtItem = Source
.OrderBy(ItemSortIndex)
.ThenBy(GroupSortIndex).First();
If ItemSortIndex and GroupSortIndex are properties instead of functions, then you need:
var soughtItem = Source
.OrderBy(i => ItemSortIndex)
.ThenBy(i => GroupSortIndex).First();

Categories

Resources