Searching a List of List - c#

I have the following object:
List<List<MyObj>> lst;
I need to find a list of all the objects (List< MyObj >) in the inner list, that has ID equal 1.
I tried:
lst.Where(x => x.FindAll(y=> y.ID== "1"));
lst.FindAll(x => x.FindAll(y=> y.ID== "1"));
and also tried to use Any() but no luck.

You can use SelectMany() to flatten the lists and then filter the elements:
var result = lst.SelectMany(x => x).Where(y => y.ID == "1").ToList();

List<MyObj> list1 = lst.SelectMany(x => x.Where(y=> y.ID== "1")).ToList();
or
List<List<MyObj>> list2 = lst.Where(x => x.Any(y=> y.ID== "1")).ToList();
depending on what it is you want as a result..

SelectMany is your friend. Example:
var listOfListsOfStrings = new List<List<string>>();
listOfListsOfStrings.Add(new List<string>() {"a", "b"});
listOfListsOfStrings.Add(new List<string>() {"c", "d"});
var allStrings = listOfListsOfStrings.SelectMany(s => s);
Console.WriteLine(string.Join(", ", allStrings.ToArray())); //prints: a, b, c, d
So in your case you just need:
lst.SelectMany(x => x).Where(y => y.ID == "1")

Let me add another option to the already good set of options. It is using Hashset<T> for search, by converting the internal List<T>, this would help when data size is more, since Hashset<T> has O(1) search instead of O(N) for List<T>
List<List<MyObj>> lst;
var result = lst.where(x =>
{
// Assuming ID to be string type
var hashset = new Hashset<string>(x.Select(y => y.ID));
return hashset.Contains("1");
}
);
In case you are not keen to do conversion, then following code would do:
var result = lst.where(x => x.Any(y => y.ID == "1"));
result will be of type List<List<MyObj>>, which will be filtered, currently we are are supplying Func<T,bool> to the Enumerable.Where, when data is supplied at run-time, then its easier to construct Expression<Func<T,bool>>, which gets compiled at run-time into correct Func<T,bool> delegate to filter actual list

Related

C# Linq union multiple properties to one list

Basically I have an object with 2 different properties, both int and I want to get one list with all values from both properties. As of now I have a couple of linq queries to do this for me, but I am wondering if this could be simplified somehow -
var componentsWithDynamicApis = result
.Components
.Where(c => c.DynamicApiChoicesId.HasValue ||
c.DynamicApiSubmissionsId.HasValue);
var choiceApis = componentsWithDynamicApis
.Select(c => c.DynamicApiChoicesId.Value);
var submissionApis = componentsWithDynamicApis
.Select(c => c.DynamicApiSubmissionsId.Value);
var dynamicApiIds = choiceApis
.Union(submissionApis)
.Distinct();
Not every component will have both Choices and Submissions.
By simplify, I assume you want to combine into fewer statements. You can also simplify in terms of execution by reducing the number of times you iterate the collection (the current code does it 3 times).
One way is to use a generator function (assuming the type of items in your result.Components collection is Component):
IEnumerable<int> GetIds(IEnumerable<Component> components)
{
foreach (var component in components)
{
if (component.DynamicApiChoicesId.HasValue) yield return component.DynamicApiChoicesId.Value;
if (component.DynamicApiSubmissionsId.HasValue) yield return component.DynamicApiSubmissionsId.Value;
}
}
Another option is to use SelectMany. The trick there is to create a temporary enumerable holding the appropriate values of DynamicApiChoicesId and DynamicApiSubmissionsId. I can't think of a one-liner for this, but here is one option:
var dynamicApiIds = result
.Components
.SelectMany(c => {
var temp = new List<int>();
if (c.DynamicApiChoicesId.HasValue) temp.Add(c.DynamicApiChoicesId.Value);
if (c.DynamicApiSubmissionsId.HasValue) temp.Add(c.DynamicApiSubmissionsId.Value);
return temp;
})
.Distinct();
#Eldar's answer gave me an idea for an improvement on option #2:
var dynamicApiIds = result
.Components
.SelectMany(c => new[] { c.DynamicApiChoicesId, c.DynamicApiSubmissionsId })
.Where(c => c.HasValue)
.Select(c => c.Value)
.Distinct();
Similar to some of the other answers, but I think this covers all your bases with a very minimal amount of code.
var dynamicApiIds = result.Components
.SelectMany(c => new[] { c.DynamicApiChoicesId, c.DynamicApiSubmissionsId}) // combine
.OfType<int>() // remove nulls
.Distinct();
To map each element in the source list onto more than one element on the destination list, you can use SelectMany.
var combined = componentsWithDynamicApis
.SelectMany(x => new[] { x.DynamicApiChoicesId.Value, x.DynamicApiSubmissionsId.Value })
.Distinct();
I have not tested it but you can use SelectMany with filtering out the null values like below :
var componentsWithDynamicApis = result
.Components
.Select(r=> new [] {r.DynamicApiChoicesId,r.DynamicApiSubmissionsId})
.SelectMany(r=> r.Where(p=> p!=null).Cast<int>()).Distinct();

check if an array contains values of another array

I have two list, I want the values of list 1 if it contains any of value from list 2.
List<string> list1 = new List<string>();
list1.Add("Sunday is far away");
list1.Add("Today is Monday");
list1.Add("Tuesday is too near");
List<string> list2 = new List<string>();
list2.Add("Sunday");
list2.Add("Monday");
list2.Add("Tuesday");
var result1 = list1.Where(x => list2.Any(y => y.Contains(x))).ToList(); //no results
var result2 = list2.Where(x => list1.Any(y => y.Contains(x))).ToList(); //give values of list2. But I need values of list1
Update:
I need values of list1 in result, how can I get that?
Simple thing you missed, Take a look into the collection, All Items in the first list are larger than that of second, so the contains will return false. So you have to check for second item in first like the following:
Here is your modified code with result:
var result1 = list1.Where(x => list2.Any(y => x.Contains(y))).ToList();
var result2 = list2.Where(x => list1.Any(y => y.Contains(x))).ToList();
Simply you can. If List1 contains any value of List2 then result=List1. Otherwise null;
var result = list2.Any(l2 => list1.Contains(l2))==true?list1:null;

Linq: Exclude results using Zip

I have a list of bool, and a list of strings. I want to use IEnumerable.Zip to combine the lists, so if the value at each index of the first list is true, the result contains the corresponding item from the second list.
In other words:
List<bool> listA = {true, false, true, false};
List<string> listB = {"alpha", "beta", "gamma", "delta"};
IEnumerable<string> result = listA.Zip(listB, [something]);
//result contains "alpha", "gamma"
The simplest solution I could come up with is:
listA.Zip(listB, (a, b) => a ? b : null).Where(a => a != null);
...but I suspect there's a simpler way to do this. Is there?
I think this is simpler:
listA
.Zip(listB, (a, b) => new { a, b } )
.Where(pair => pair.a)
.Select(pair => pair.b);
That logically separates the steps. First, combine the lists. Next, filter. No funky conditionals, just read it top to bottom and immediately get it.
You can even name it properly:
listA
.Zip(listB, (shouldIncludeValue, value) => new { shouldIncludeValue, value } )
.Where(pair => pair.shouldIncludeValue)
.Select(pair => pair.value);
I love self-documenting, obvious code.
This is as short as I could get it:
var items = listB.Where((item, index) => listA[index]);
Where has an overload that provides the index. You can use that to pull the corresponding item in the bool list.
listA.Zip(listB, (a, b) => new { a, b }).Where(x => x.a).Select(x => x.b);
It uses anonymous type to handle Zip method subresults.
You don't need to use Zip if you can index into listA:
var res = listB.Where((a, idx) => listA[idx]);

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

LINQ casting during enumeration

I have a List<string>
List<string> students;
students.Add("Rob");
students.Add("Schulz");
and a Dictionary<string,string>
Dictionary<string, string> classes= new Dictionary<string, string>();
classes.Add("Rob", "Chemistry");
classes.Add("Bob", "Math");
classes.Add("Holly", "Physics");
classes.Add("Schulz", "Botany");
My objective now is to get a List with the values - Chemistry and Botany - for which I am using this
var filteredList = students.Where(k => classes.ContainsKey(k))
.Select(k => new { tag = students[k] });
While trying to enumerate the values - I am able to obtain - tag=Chemistry & tag=Botany...while I want just Chemistry and Botany.
What is the appropriate casting to be applied? Is there a better way to get to these values?
You only have to write:
var filteredList = students.Where(student => classes.ContainsKey(student));
Here, student is a string, since students is a List<string>, so you only have to apply Where(). The result will be an IEnumerable<string>.
You can apply ToList() if you want to exhaust the enumerable into another List<string>:
var filteredList = students.Where(student => classes.ContainsKey(student)).ToList();
If you want a list of classes (it's not clear from the code in your question), then you have to apply Select() to project classes from students:
var filteredList = students.Where(student => classes.ContainsKey(student))
.Select(student => classes[student]);
try:
var filteredList = students.Where(k => classes.ContainsKey(k))
.Select(k => students[k]);
var filteredList = students.Where(k => classes.ContainsKey(k))
.Select(k => students[k]);

Categories