Linq select item after if statement - c#

I've got a list that i want to breakdown to a other list.
So when the if statement is correct the current item has to be added to the new list
var newList = oldList.ForEach( x =>
{
if (condition)
{
// select the current item
}
})
the part of select the current item is the question

Use Where:
var newList = oldList.Where(x => condition(x));
In this version, newList will be an object with lazy evaluation. To make the result concrete, you can additionally evaluate it at once:
var newList = oldList.Where(x => condition(x)).ToList();

With Where():
var newList = oldList.Where(x => x < 5);

Use Where method with ToList in the end:
var newList = oldList.Where(x => condition(x)).ToList();

In your code example, "x" will be your current item. So all you have to do is actually add it to the other list.
if (condition)
{
newList.Add(x);
}

Related

Select list elements contained in another list in linq

I have a string with "|" seperators:
string s = "item1|item2|item3|item4";
a list of objects that each have a name and value:
//object
List<ItemObject> itemList = new List<ItemObject>();
itemList.Add(new ItemObject{Name="item0",Value=0});
itemList.Add(new ItemObject{Name="item1",Value=1});
//class
public class ItemObject(){
public string Name {get;set;}
public int Value {get;set;}
}
How could the following code be done in one line in linq?
var newList = new List<object>();
foreach (var item in s.Split("|"))
{
newList.Add(itemList.FirstOrDefault(x => x.Name == item));
}
// Result: newList
// {Name="item1",Value=1}
I would suggest to start from splitting the string in the beginning. By doing so we won't split it during each iteration:
List<ItemObject> newList = s
.Split("|")
.SelectMany(x => itemList.Where(i => i.Name == x))
.ToList();
Or even better:
List<ItemObject> newList = s
.Split("|") // we can also pass second argument: StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
.Distinct() // remove possible duplicates, we can also specify comparer f.e. StringComparer.CurrentCulture
.SelectMany(x => itemList
.Where(i => string.Equals(i.Name, x))) // it is better to use string.Equals, we can pass comparison as third argument f.e. StringComparison.CurrentCulture
.ToList();
Try this:
var newList = itemList.Where(item => s.Split('|').Contains(item.Name));
The proposed solution also prevents from populating newList with nulls from nonpresent items. You may also consider a more strict string equality check.
string s = "item1|item2|item3|item4";
I don't see a need for splitting this string s. So you could simply do
var newList = itemList.Where(i => s.Contains(i.Name));
For different buggy input you can also do
s = "|" + s + "|";
var newList = itemList.Where(o => s.Contains("|" + o.Name + '|')).ToList();
List<object> newList = itemList.Where(item => s.Split("|").Contains(item.Name)).ToList<object>();

Searching a List of List

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

Two for loops in lambda expression

How to create the exactly following two for's in lambda expression?
foreach (var item in list1)
{
foreach (var item2 in list2)
{
if (item.number == item2.number)
{
return false;
}
}
}
Since you're just checking to see if any one item matches, you can use Any().
return !list1.Any( item1 => list2.Any(item2 => item2 == item1 ));
I would just use the Intersect function available for lists and this will return you all the elements that are common in 2 lists. If you just want to see if one exists then you can do it very easily by checking the count.
int count = List1.Select(s => s.number).Intersect(List2.Select(s => s.number)).Count;
If you want to know which elements are unique in both lists then use the Exclude method.
var uniqueItems = List1.Select(s => s.number).Except(List2.Select(s => s.number));
Here you go !!
Using Linq Method Syntax :
!list1.Any(item => list2.Any(item2 => item.number == item2.number))
Using Linq Query syntax:
!(from item in list1
from item2 in list2
where item.number==item2.number select item).Any()

Add Collection to End of IOrderedEnumerable

I currently have a bunch of config files I need to load into an IOrderedEnumerable My current approach looks like so:
foreach (var item in configFiles)
{
XDocument myxml = XDocument.Load(item);
var items = myxml.Root.Elements("Item");
Items = items.OrderBy(x => x.Attribute("ID").Value);
ItemsLength += Items.Count();
}
The problem is, instead of making Items equal to items.OrderBy(x => x.Attribute("ID").Value) I'd like to join that to the end of the currently existing IOrderedEnumerable so I'm not overwriting it each time I load a new XDocument and get all of the elements from it. How would I do this?
EDIT: I know that if I change this ItemsLength += Items.Count(); will no longer function properly. That's something I'll change on my own.
You can do the whole thing declaratively:
Items = configFiles.Select((item, index) => new { Doc = XDocument.Parse(item),
Index = index })
.SelectMany(pair => pair.Doc.Root.Elements("Item")
.Select(x => new { Item = x,
Index = pair.Index }))
.OrderBy(pair => pair.Index)
.ThenBy(pair => (string) pair.Attribute("ID"))
.Select(pair => pair.Item);
This basically finds all the elements, but remembers which configuration each is in.
Alternatively, just create a List<XElement> and add each item's contents:
var items = new List<XElement>();
foreach (var item in configFiles)
{
items.AddRange(XDocument.Parse(item)
.Root.Elements("Item")
.OrderBy(x => (string) x.Attribute("ID")));
}
Items = items;
In some ways that's less elegant, but it's probably easier to understand :)
If you can change Items to be of type IEnumerable rather than IOrderedEnumerable, you could use Concat:
Items = Items.Concat(items.OrderBy(x => x.Attribute("ID").Value));
This would keep items sorted by ID within their own document.
Note that using Concat in a loop may impair the performance. You would be better off declaring Items as IList, and calling ToList at the end of each iteration:
Items.AddRange(items.OrderBy(x => x.Attribute("ID").Value));

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