- Home /
How to select certain Elements in a list using LINQ-methods?
Hi,
I am fascinated about what one can achieve with oneliners when using System.Linq methods. But it seems that the syntax is a real struggle.
Basically I want to add certain objects to a list, where the selected script has a value, in other words it should filter the objects out.
The following code should add to all the characters in the list em.characters all others except itself to their targets-list (Type Transform) AKA logic for Deathmatch/All vs. All:
foreach(CharacterMotor c in em.characters)
c.targets.InsertRange(c.targets.Count, em.characters.Select(go => go.transform)
.Where(c => !c.transform ).ToList());
I am not sure how to use the Where function. Any help appreciated.
Answer by Bunny83 · Mar 08, 2017 at 05:38 AM
While the others have basically presented you with some solutions, i want to focus on what you actually looking at ^^. First the "Where" method is an extension method for IEnumerables. It basically takes an input IEnumerable as well as a "predicate method", it "processes" the input collection and returns a new IEnumerable. For each element in the input collection it runs the predicate and if it returns "true" that element will go into the output. If not it's basically skipped.
Now i used some terms here which i might explain shortly:
IEnumerable
An IEnumerable is basically just an interface that provides you of a way to get an "IEnumerator". Therefore the only method that the interface provides is called "GetEnumerator". So what the heck is an IEnumerator then?IEnumerator
You may already heared about that interface when you used or read about coroutines in Unity. However Unity just uses this interface to implement coroutines.The IEnumerator actually is just an interface that allows you to "iterate" over "things". To keep it simple we just focus on two things: An IEnumerator has a method called "MoveNext" and a property called "Current".
MoveNext simply moves the iterator "forward" to the next element. Further more it returns a boolean indicating if we reached the end of the iteration. A value of "false" means there are no more elements and we're done. A value of "true" means the iterator found another object and "Current" can be used to access that object. So when iterating the IEnumerator we always call MoveNext first, see if it returns true, then access the current element. If it returns false we stop iterating.
Since IEnumerable and IEnumerator are interfaces they need to be implemented by actual objects. A lot of classes implement the IEnumerable interface (like the generic List for example). Each object usually has their own enumerator class which implements the IEnumerator interface which does the actual iteration. So when calling GetEnumerator on a List object it will create an instance of that enumerator object.
Normal vs Generic
It might be worth mentioning that both interfaces come in a "normal" / untyped version and a generic "typed" version. Most of the Linq stuff will work on the generic version only. So the generic version basically just let you specify the exact type of objects the iterator will spit out. i.e. the type of the Current property.Chaining enumerators
What most of those Linq extension methods do is basically creating wrapper objects which also implement IEnumerable / IEnumerator around the input IEnumerable / IEnumerator. So when you call MoveNext on the wrapper it will in turn call MoveNext on the input enumerator to get one element from the input and returns that one as Current.For example:
List<string> someList;
IEnumerable<string> myEnumerable = someList.Where(x=>x != "not me");
foreach(string s in myEnumerable)
{
Debug.Log(s);
}
Note that in the second line we just create the "wrapper" IEnumerable. We don't iterate it. The foreach loop will actually call GetEnumerator and then iterate over each item that the wrapper object returns, While iterating the wrapper will in turn iterate over the input enumerator. This "chaining" can go crazy.
Predicate function
A predicate is actually a method that will tell the caller if a certain object satisfies a certain criteria. It's actually just a method that takes an object as parameter and returns a boolean. In most cases when using Linq you would use Lambda expression / anonymous method, but you can use actual methods as well. The example above could be written as: static bool FilterOutNotMe(string aObj)
{
if (aObj == "not me")
return false;
return true;
}
IEnumerable<string> myEnumerable = someList.Where(FilterOutNotMe);
Lambda expression
A Lambda expression is basically just an anonymous method. The syntax is fairly easy. You first have to specify the paramters of your method, followed by the lambda operator=>
. Behind that operator you actually specify the body of your method. The formal syntax would look like this:
(Type variableName1, Type variableName2) => { /* code */ }
This basically defines a method seemingly ad-hoc that can be passed as delegate to methods that require such a callback method. Now there are a few simplifications. First of all if you pass the lambda expression as parameter to another method, the compiler can figure out (infer) the types of the parameters based on the expected type of the expected delegate type. So one could simply omit the types and just define the parameters:
(a, b) => { }
Furthermore if there's only one parameter (and you don't need to specify the type explicitly) you can omit the brackets ( )
around the parameters:
Action<int> myAction = a => { Debug.Log("a is " + a); };
So in this example the compiler will infer that the type of the parameter "a" is "int". When you now call myAction(5)
it will actually print "a is 5"
in the console. It will also infer the return type is one is required
Func<int, string> myFunc = a => { return "FooBar " + a; };
The last simplification is that if you only have one statement in your method body you can omit the curly brackets as well. In the case of a method with return type that single statement has to evaluate to the expected return type. You also have to omit the "return" keyword in this case. So the above example becomes this:
Func<int, string> myFunc = a => "FooBar " + a;
Where
As mentioned above the Where method takes a predicate which is evaluated for each element as the enumerator is iterated. What the enumerator class for the Where method basically looks like is something like this: // written by me from scratch
class Where_Enumerator<T> : IEnumerator<T>
{
IEnumerator<T> input;
Predicate<T> condition;
public T Current { get { return input.Current; } }
public bool MoveNext()
{
while(true)
{
if (!input.MoveNext())
return false;
if (condition(input.Current))
return true;
}
}
}
So each time MoveNext is called it will advance the input enumerator and if we reach the end of the input stream, return false. Otherwise call the predicate method with the current element from the input enumerator. If the predicate returns true we return true so the current element should be processed by who ever currently is iterating over our enumerator, If the predicate returns false, so the element should not be returned, we simply keep looping and pulling the next element from the input until we get one that satisfies our condition or we reach the end of the input stream.
Select
The Select method is a bit different as it can have different input and output types. It also takes a delegate as parameter, but this time a selector method instead of a predicate. A selector method takes an object from the input enumerator as parameter and returns the desired output type. So in the case of your code sampleSelect(go => go.transform)
the lambda expression will take a CharacterMotor as parameter (as this is the type of the input enumerator) and returns a Transform. The Transform type is inferred from whatever you actually return.
So Select creates another wrapper around it's input enumerator and to each element from the input it applies the "converting" / selecting callback.
Things to keep in mind
As mentioned above chaining enumerators have the advantage that in the end you only iterate through your base collection only once and each element will go through the whole chain, one by one.However there are some methods that might break that rule because it's necessary for their correct behaviour. For example "OrderBy" or "Reverse". They actually need to iterate through the whole input collection and store all the elements in an internal structure in order to perform any sorting or the reverse of the collection.
There are also "ending" methods like ToList(), ToArray(), Count(), Sum(), Max(), ... which obviously will iterate through the current stack of enumerators since they also need access to all elements.
Short addition to lambda expressions (closures!)
I didn't mention that above since it's a topic on it's own but lambda expression as well as anonymous methods basically come in two variations:simple methods
closures
A simple method is just what an ordinary method looks like. It has parameters and might return a value. However they do not reference anything outside the methods body. Basically a simple method is any lambda expression you could write as a normal static method (like my "FilterOutNotMe" example above).
A closure is a real beast. It's a method that actually references variables outside the scope of the own methods body. Those external variables are said to be closed upon / around. But how the heck does this work? Well, in both cases the code for those methods is not generated at runtime as some people believe but completely generated at compile time. But how is a method able to use variable from a completely different scope? The answer is the compiler creates a new internal class which acts as "shared scope". So using a closure actually affects the code that is generated for the outer scope as well. Simple example:
Func<int> Method()
{
int myInt = 6;
Func<int> myClosure = ()=>myInt;
Debug.Log("myInt: " + myInt);
return myClosure;
}
As we all know myInt is a local variable of "Method". It's allocated on the stack when we enter the method and automatically freed when the method ends. However not in this case. Because our closure uses "myInt", the compiler will generate a closure class which will contain the variable myInt. Every point where you use myInt will be replaced by the class variable.
Rough example what happens under the hood:
private class Closure123
{
public int myInt;
public int Closure123_456()
{
return myInt;
}
}
Func<int> Method()
{
Closure123 c = new Closure123();
c.myInt = 6;
Func<int> myClosure = c.Closure123_456;
Debug.Log("myInt: " + c.myInt);
return myClosure;
}
As you can see the compiler will create a new instance of the closure class and every access to myInt has been changed to use the closure instance now. Also our lambda expression is simply a member method of the closure class where it can easily access the variable.
When "Method" finished all of it's local variable will go out of scope and extend which includes the "c" variable that holds our closure object. However since the method returns the myClosure delegate, the actual object reference is stored inside that delegate so the closure object as well as the myInt variable will survive.
When you call "Method" again a new closure object will be created.
Damn, the "short" turned out longer than i expected ^^. It's difficult for me to explain things in a short way ^^. Hopefully it helps so you get a better understanding of enumerators, lambda expressions and Linq.
Awesome Bunny83! Thank you for this extraordinary long, yet wonderful explaination :)
Answer by jwulf · Mar 08, 2017 at 02:15 AM
I am not a 100 percent sure what you want to achieve, but I guess you rather want
foreach(CharacterMotor c in em.characters)
c.targets.InsertRange(c.targets.Count, em.characters.Select(go => go.transform)
.Where(charTransform => charTransform != !c.transform ).ToList());
Although I'd rather for better understandability write the Linq-"query" as
foreach(CharacterMotor c in em.characters) {
c.targets.InsertRange(
c.targets.Count, em.characters.Where(character => character != c) // of all characters which are not c itself...
.Select(character => character.transform) // ... select the transform ...
.ToList() // ... and make a list out of it.
);
}
You could even skip the ToList(), I think, because List.InsertRange accepts any IEnumerable (as is returned by Select() and Where().
Answer by TrickyHandz · Mar 08, 2017 at 02:28 AM
@Noxury, Rather than trying to Insert a range in this case, you may just want to filter your list and create a new list from it. Here is a snippet that should help you:
foreach (CharacterMotor c in em.characters)
{
// The target list is equal to the objects in
// the em.characters list Where that object's
// transform is not equal to the transform of
// the current object in the loop
c.targets = em.characters.Where(go => go.transform != c.transform).ToList();
}
Hope the helps you out.
Cheers,
TrickyHandz
if Character$$anonymous$$otor.targets is a List of Transforms, there is still the select-clause missing, so:
c.targets = em.characters.Where(go => go.transform != c.transform).Select(go => go.transform).ToList();
Your answer
Follow this Question
Related Questions
Remove and Add to List By Name aad 1 Answer
A node in a childnode? 1 Answer
Add to generic list on start problem 1 Answer
Iterate through Variables in a Script and adding them to a Generic List 1 Answer
Javascript Array to Generic List 1 Answer