How to avoid two embedded cycles in linq query C# - c#

var listOfIds = new List<string>();
var allItems = IEnumerable<Info>();
foreach (var id in collectionIds)
{
listOfIds.AddRange(allItems
.Where(p => p.Data.FirstOrDefault(m => m.Key == "myId").Value == id)
.Select(x => x.Id));
}
I would like to avoid using AddRange but use only Add in this case and maybe use only FirstOrDefault in the place of where to avoid the last Select case.
Is this possible and if yes how?

Assuming your original code is giving you the correct data, specifically you are OK with:
Only concerned that the first item in p.Data contains a matching value and;
p.Data will always contains at least a single element.
Then this code will give you the same output:
var listOfIds = allItems
.Where(p => collectionIds.Contains(p.Data.First(m => m.Key == "myId").Value))
.ToList();
However, if you really do care that any value in p.Data matches, then this would be more appropriate:
var listOfIds = allItems
.Where(p => p.Data.Any(m => m.Key == "myId" &&
collectionIds.Contains(m.Value)))
.ToList();

How about this approach:
var listOfIds = new List<string>();
var allItems = IEnumerable<Info>();
var groupedAllItems = allItems.GroupBy(x => x.Data.FirstOrDefault(m => m.Key == "myId")?.Value ?? "MyIdNotFound");
//collectionIds should be of type HashSet<string> for the contains to be fast
listOfIds.AddRange(groupedAllItems.Where(x => collectionIds.Contains(x.Key)).SelectMany(x => x));

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.

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();

I want to convert this foreach loop to a LINQ statement

I am not an great at linq by any means but I usually have no issues with a problem of this sort. I want to convert this foreach statement to a LINQ statement:
var existingKeys = new List<int>();
foreach (var taskKey in request.Keys)
{
existingKeys.AddRange(_context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && x.TaskKey == taskKey)
.Select(x => x.TaskGroupNameKey));
}
I thought this would do it:
var existingKeys = request.Keys.ForEach(taskKey => _context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && x.TaskKey == taskKey)
.Select(x => x.TaskGroupNameKey));
That apparently returns a void not a list...
This:
var existingKeys = request.Keys.Select(taskKey =>
_context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && x.TaskKey == taskKey)
.Select(keys => keys.TaskGroupNameKey));
Gives me an "IEnumerable<IQueryable<int>>. So what is the secret sauce that I am missing here?
You shouldn't be performing N database queries in the first place. Using LINQ to perform those N queries instead of a foreach loop doesn't fix that core problem.
You need to re-conceptualize your query so that you have just one query that gets all of the data that you need. In this case that means getting all of the items that match your collection of keys rather than trying to match a single key and then performing N of those queries.
var requestedKeys = request.Keys;
var existingKeys = _context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key &&
requestedKeys.Contains(x.TaskKey))
.Select(x => x.TaskGroupNameKey))
.ToList();
var existingKeys = request
.SelectMany(r => r.Keys)
.SelectMany(tk =>
_context.WebTaskGroups
.Where(x.TaskGroupNameKey == key && x.TaskKey == tk)
.Select(x => x.TaskGroupNameKey))
.ToList();
var existingKeys = _context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && request.Keys.Contains(x.TaskKey))
.Select(x => x.TaskGroupNameKey)
.ToList();
ForEach return a void: http://msdn.microsoft.com/en-us/library/bwabdf9z(v=vs.110).aspx
ForEch: Performs the specified action on each element of the List.
So what to do, is for each item in the list of request.Keys to perform the action to add to the list of existingKeys.
For example:
request.Keys.ForEach(taskKey =>
existingKeys.AddRange(_context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && x.TaskKey == taskKey)
.Select(x => x.TaskGroupNameKey));

Join an array of string with the result of an existing linq statement

As a follow up to my last question here:
Filtering a list of HtmlElements based on a list of partial ids
I need to take this statement:
doc.All.Cast<HtmlElement>()
.Where(x => x.Id != null)
.Where(x => ids
.Any(id => x.Id.Contains(id))).ToList();
and join it with an array of strings called fields. Assuming the array and list will have the same amount of elements each and line up correctly. I tried using Zip() but thought I might need to use an additional linq statement to make it work.
Assuming that fieldList[0] and IdList[0] corresponding to each other, you can do the following:
var IdList = doc.All.Cast<HtmlElement>()
.Where(x => x.Id != null)
.Where(x => ids
.Any(id => x.Id.Contains(id))).ToList();
var resultList = fieldList
.Select( (item, index) => new { Field = item, Id = IdList[index] })
.ToDictionary(x => x.Id, x => x.Field);
You have mentioned it already, you can use Enumerable.Join:
var joined = from id in fields
join ele in elements on id equals ele.Id
select new { Element = ele, ID = id };
var dict = joined.ToDictionary(x => x.ID, x => x.Element);
I've presumed that you want to join them via ID. I've also presumed that the string[] contains only unique ID's. Otherwise you need to use Distinct.

Modifying an IEnumerable type

I have a a string IEnumerable type that I get from the below code.The var groups is an Enumerable type which has some string values. Say there are 4 values in groups and in the second position the value is just empty string "" .The question is how can I move it to the 4th ie the end position.I do not want to sort or change any order.Just move the empty "" value whereever it occurs to the last position.
List<Item> Items = somefunction();
var groups = Items.Select(g => g.Category).Distinct();
Simply order the results by their string value:
List<Item> Items = somefunction();
var groups = Items.Select(g => g.Category).Distinct().OrderByDescending(s => s);
Edit (following OP edit):
List<Item> Items = somefunction();
var groups = Items.Select(g => g.Category).Distinct();
groups = groups.Where(s => !String.IsNullOrEmpty(s))
.Concat(groups.Where(s => String.IsNullOrEmpty(s)));
You can't directly modify the IEnumerable<> instance, but you can create a new one:
var list = groups.Where(x => x != "").Concat(groups.Where(x => x == ""));
Note that in this query, groups is iterated twice. This is usually not a good practice for a deferred IEnumerable<>, so you should call ToList() after the Distinct() to eagerly evaluate your LINQ query:
var groups = Items.Select(g => g.Category).Distinct().ToList();
EDIT :
On second thought, there's a much easier way to do this:
var groups = Items.Select(g => g.Category).Distinct().OrderBy(x => x == "");
Note that this doesn't touch the order of the non-empty elements since OrderBy is stable.
var groups = Items.Select(g => g.Category).Distinct().OrderByDescending(s =>s);
I don't like my query but it should do the job. It selects all items which are not empty and unions it with the items which are empty.
var groups = Items.Select(g => g.Category).Distinct()
.Where(s => !string.IsNullOrEmpty(s))
.Union(Items.Select(g => g.Category).Distinct()
.Where(s => string.IsNullOrEmpty(s)));
Try something like
var temp = groups.Where(item => ! String.IsNullOrEmpty(item)).ToList<string>();
while (temp.Count < groups.Count) temp.Add("");

Categories