Order list of objects by a list of ids - c#

So, I have a list of objects (let's say there are 20) and they have an id. Then I have another list (which is ordered correctly).
I had this linq to sort the object list by the id list:
var outcomeIds = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select(m => m.Id)
.ToList();
groupResponseModel.Outcomes = groupOutcomes
.OrderBy(m => outcomeIds.IndexOf(m.Id))
.ToList();
Now, this "would" work, but the problem is the outcomeIds only has a selection of ids in it. I would have thought that indexOf would return -1 for any id that was not found and it would be put under the matched ids. Instead they appear first in the list.
How can I modify my code to get the matching ids at the top and the rest at the bottom. I can't do a reverse, because it would mean that the order of the matching ids would be in reverse too.

Sounds like you want to order by the result of IndexOf, but to have the -1 values go to the end instead of the start. In that case, you could just process the value of the IndexOf to, say, int.MaxValue so it'll go at the end.
I've tidied up your code a bit to make it more readable - only the OrderBy is different to your original code.
var outcomeIds = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select(m => m.Id)
.ToList();
groupResponseModel.Outcomes = groupOutcomes
.Select(m => Tuple.Create(m, outcomeIds.IndexOf(m.Id))
.OrderBy(m => outcomeIds.IndexOf(m.Id) == -1 ? int.MaxValue : outcomeIds.IndexOf(m.Id))
.ToList();
Or, if you don't want to call IndexOf multiple times, you could extract the conditional statement into a method:
var outcomeIds = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select(m => m.Id)
.ToList();
groupResponseModel.Outcomes = groupOutcomes
.Select(m => Tuple.Create(m, outcomeIds.IndexOf(m.Id))
.OrderBy(m => orderByKeySelector(outcomeIds(m.Id)))
.ToList();
where orderByKeySelector is
private static int orderByKeySelector<T>(List<T> source, T value)
{
var indexOfValue = source.IndexOf(value);
return indexOfValue == -1 ? int.MaxValue : indexOfValue;
}

var outcomeIds = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select(m => m.Id)
.ToList();
groupResponseModel.Outcomes = groupOutcomes
.OrderBy(m => outcomeIds.IndexOf(m.Id) != -1
? outcomeIds.IndexOf(m.Id)
: outcomeIds.Max())
.ToList();

I prefer keeping it simple:
var outcomeList;
var unorderedList;
//check all elements of the ordered list in order
foreach(var item in orderedList)
{
//if your unordered list has this item
if(unorderedList.Any(item))
{
//add this item to the final list
outcomeList.Add(item);
//and remove it from unordered
unorderedList.Remove(item);
}
}
//at this point, you added all your matching entities in order, the rest is the remainder:
outcomeList.AddRange(unorderedList);
You can even turn this into an extension method for reusability.

Why not using mapping (say, id == 5 corresponds to 0, id = 123 to 1 etc.) with a help of dictionary? It will be efficient in case of long lists:
var order = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select((m, index) => new {
id = m.id,
index = index })
.ToDictionary(item => item.id, // id
item => item.index); // corresponding index
Now let's sort the 2nd list:
groupResponseModel.Outcomes = groupOutcomes
.OrderBy(m => order.TryGetValue(m.Id, out var order)
? order // if we have corresponding index, use it
: int.MaxValue) // otherwise, put the item at the bottom
.ToList();

Related

convert dictionary to list model

var entity = await _abcRepository.get(Id);
var X = entity.GroupBy(c => c.number).Where(grp => grp.Count() == 1).Take(10).ToList();
in images you see [0] and inside of it one more [0].
How can I get that model value.
X[0][0] is not working.
X.Value is not working.
I need to convert that dictionary to model.
Use .Select to normalize aggregation as per your wish.
var X = entity.GroupBy(c => c.number).Where(grp => grp.Count() == 1)
.Select(group => new { GroupKey = group.Key, Items = group.ToList() })
.Take(10).ToList();
You could try something like this:
var entity = await _abcRepository.get(Id);
var results = entity.GroupBy(c => c.number)
.Where(grp => grp.Count() == 1)
.Take(10)
.ToDictionary(grp => grp.Key, grp => grp.First());
Essentially, the lambda you pass in Where method certifies that the groups are created contains only one item. That being said, you can use the First on each group to fetch that one element.

Filter a List of Strings and select only one occurance of a string with multiple occurance

var paymentTypes = _context
.BursaryTransactions
.Select(c => c.PaymentType)
.ToList();
string[] obj = paymentTypes
.ToArray();
var a = obj[1];
The first line retrieves a list of Payment type which are in strings from BursaryTransactions Table
the second line converts the list to array.
The list from the first line however contains Similar Strings for example
Post Utme
School Fee
School Fee
Post Utme
Hnd Form
Hnd Form
I want to filter these list and retrive just one occurrance of an item that appears more than once. then converts the new list to array.
You can try GroupBy and choose groups with more than 1 item:
var result = _context
.BursaryTransactions
.GroupBy(c => c.PaymentType) // Group By PaymentType
.Where(group => group.Count() > 1) // groups with more than 1 item
.Select(group => group.First()) // we want just the 1st item of such group
.ToList(); // materialized as a List<T>
Edit: to remove duplicates we can take First item from each group:
var result = _context
.BursaryTransactions
.GroupBy(c => c.PaymentType)
.Select(group => group.First()) // First().PaymentType if you want PaymentType only
.ToList();
You can try this way
var result = _context.BursaryTransactions.GroupBy(c => c.PaymentType)
.Where(g => g.Count() > 1)
.Select(g => g.First())
.ToArray();

How can i remove same item in list

Could you help me? I cant remove the same item in a list.
List<string> text = new List<string>();
text.Add("A");
text.Add("B");
text.Add("C");
text.Add("D");
text.Add("D");
text.Add("A");
foreach(string i in text)
{
Console.WriteLine(i);
}
the result is A,B,C,D,D,A but I need to be B,C . How can i do?
Here is my solution,
var result = text.GroupBy(x => x).Where(y => y.Count() == 1).Select(z => z.Key);
Console.WriteLine(string.Join(", ", result));
Explanation:
GroupBy(x => x): This will group list based on characters i.e predicate.
.Where(y => y.Count() == 1): This will filter elements which are duplicates.
.Select(z => z.Key): Select will create new enumerable which contains Keys from Grouped elements
Something like
text.GroupBy(t => t).Where(tg => tg.Count()==1).Select(td => td.First());
This proberly wont compile, you need to fix that your self.
The idea is:
1. Group by item.
2. Take all groups with exactly 1 item
3. Select the Item

Finding the most specific matching item

User input will be like 'BY1 2PX', which will split and stored into list like below
var items = new List<string> {'BY1 2PX', 'BY12', 'BY1', 'BY'};
I have source list of Products
public class Product
{
public string Name {get;set;}
public string Id {get;set;}
}
Below is a sample product list. There is no guarentee on ordering, it could be in any order.
var products = new List<Product>{
new Product("1", "BY1 2PX"),
new Product("2", "BY12"),
new Product("3", "BY1"),
new Product("4", "BY"),
new Product("5", "AA2 B2X"),
//...etc
}
my output should fetch 1, because its most specific match. If Id = 1 is not there then it should have fetched Id =2 like that...etc Could anyone help me in writing a linq query. I have tried something like below, is this fine?
var result = items.Select(x => products.FirstOrDefault(p =>
string.Equals(p.Name.Trim(), x, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault();
Well, you can use dictionary with its fast lookups :
var productsDict = products.ToDictionary(p => p.Name, p => p);
var key = items.FirstOrDefault(i => productsDict.ContainsKey(i));
Product result = key != null ? productsDict[key] : null;
Or as Tim suggested, if you have multiple elements with same names you can use Lookup :
var productsDict = products.ToLookup(p => p.Name, p => p);
var key = items.FirstOrDefault(i => productsDict.Contains(i));
Product result = key != null ? productsDict[key] : null;
If you want to select the best-matching product you need to select from the product- not the string-list. You could use following LINQ approach that uses List.FindIndex:
Product bestProduct = products
.Select(p => new {
Product = p,
Index = items.FindIndex(s => String.Equals(p.Name, s, StringComparison.OrdinalIgnoreCase))
})
.Where(x => x.Index != -1)
.OrderBy(x => x.Index) // ensures the best-match logic
.Select(x => x.Product)
.FirstOrDefault();
The Where ensures that you won't get an arbitrary product if there is no matching one.
Update:
A more efficient solution is this query:
Product bestProduct = items
.Select(item => products.FirstOrDefault(p => String.Equals(p.Name, item, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault(p != null); // ensures the best-match logic
You can try to find resemblance of words by using a specific algorythm called Levenshtein's distance algorythm, which is mostly used on "Did you mean 'word'" on most search websites.
This solution can be found here:
https://stackoverflow.com/a/9453762/1372750
Once you find the distance difference, you can measure which word or phrase is more "like" the searched one.
This will find for each product what is the "most specific" (the longest) match in items and will return the product with the longest match (regardless to order of either of the collections)
var result = products
.Select(p => new
{
Product = p,
MostSpecific = items.Where(item => p.Name.Contains(item))
.OrderByDescending(match => match.Length
.FirstOrDefault()
})
.Where(x => x.MostSpecific != null)
.OrderByDescending(x => x.MostSpecific.Length)
.Select(x => x.Product)
.FirstOrDefault();

Remove every first element of grouped collection

I have a collection of elements and some of these elements are duplicating. I need to extract all records but only the first record if the record is one of a duplicate set.
I was able to group the elements and find all elements that have duplicates, but how to remove every first element of a group?
var records =
dbContext.Competitors
.GroupBy(x => x.Email)
.Select(x => new { Properties = x,
Count = x.Key.Count() })
.Where(x => x.Count > 1)
.ToList();
EDIT: Seems like it's impossible to accomplish this task with EF, because it fails to translate the desired linq expression to SQL. I'll be happy if someone offer different approach.
To exclude the first record from each email-address group with more than one entry, you could do this:
var records = dbContext.Competitors
.GroupBy(x => x.Email)
.SelectMany(x => (x.Count() == 1) ? x : x.OrderBy(t=>t).Skip(1))
.ToList();
This is the logic :
Group by a property > Select every Group > (Possibly) Sort that > Skip first one
This can be turned into some linq code like this :
//use SelectMany to flat the array
var x = list.GroupBy(g => g.Key).Select(grp => grp.Skip(1)).SelectMany(i => i);

Categories