Nested IEnumerable/Arrays, how to access a deeply nested property - c#

So I have an IEnumerable object that contains a queue(list), each item contains 3 properties. One of those properties ("States") contains another array... and this array contains the property "Messages". Fairly confusing, that's why I took a photo of the local in debugging.
Had to edit sensible data out.
I only have this IEnumerable object to work with. How do I reach the property "message"?
I already tried some Lambda expressions... Like
var _message = _criticalData.Select(item1 => item1.States.Select(item2 =>item2.Messages).ToArray()).ToArray()
Then I can create a new array/list of strings and foreach each _message into it.
Atleast I think it works (can't test it at home). But it would also be really really slow. Is there any other way to do this?

You have to flatten out multiple lists, so use SelectMany like:
string[] output = _criticalData.SelectMany(outer =>
outer.States.SelectMany(inner => inner.Messages))
.ToArray();

Related

Comparing one list to another with a custom class

So I wonder if I can compare two Lists without using foreach because the List is with a custom class. Inside the class, it contains two variables. one of them is called GUID. In order to access the GUID for List A, I use Any(x =>x.guid) And to access the same things in List B I have to do a foreach, which is like this, foreach(var x in List B){x.guid).
What I want to know is, is it possible to do it without the foreach? And if it is possible, how? I have been looking for an answer online but most of the example is looking at an item in one list. What I'm trying to do is compare one custom list to another, but only one variable inside the class instead of comparing the whole class.
The code below is how I do it, comparing one to another, but is there a more efficient way to do it
List<MySecondGameList> myloadinglist = JsonConvert.DeserializeObject<List<MySecondGameList>>(json);
foreach (var id in myloadinglist)
{
if (GameData_List.my_loading_list.Any(x => x.guid == id.guid))
{
Debug.Log("Matching!!!!!!!!!!!!!!!!");
continue;
}
GameData_List.my_loading_list.Add(id);
Debug.Log("It is loading");
}
}
You can hide loops within Linq queries, e.g.
List<MySecondGameList> myloadinglist = JsonConvert
.DeserializeObject<List<MySecondGameList>>(json);
// HashSet<T> provides faster Contains than List
HashSet<Guid> existing = GameData_List
.my_loading_list
.Select(item => item.guid)
.ToHashSet();
// We can put it compact with a help of AddRange instead of Add
GameData_List
.my_loading_list
.AddRange(myloadinglist.Where(item => !existing.Contains(item.guid)));

Filter IEnumerable<object> based on whether string property contains any string value of another List<string>

I have an IEnumerable collection of custom objects types which contain a string property in JSON format. I also have a List collection which I need to use in order to filter the IEnumerable. I want to remove all items from the IEnumerable where the string property contains any string value from the List collection. I can pull this off by creating a temporary list and doing some looping but I'm looking for a more elegant solution and I haven't been successful yet.
foreach (var faction in excludedFactions)
{
cardsVM.Cards = repository.Cards.ToList()
.RemoveAll(c => c.Factions.Contains(faction));
}
This is one thing that I've tried. I've been playing around with LINQ statements for the last two hours and I can't make it work. If anyone could point me in the right direction (a proper function for my requirements) that would be more than appreciated.
Make a HashSet<string> of excluded factions, then use this set to filter your cards:
var excludedFactionSet = new HashSet<string>(excludedFactions);
cardsVM.Cards = repository.Cards
.Where(c => !excludedFactionSet.Contains(c.Factions))
.ToList();
Note that using excludedFactions directly is also possible. However, using HashSet<string> makes Contains(f) check work in constant, rather than linear, time.

Add, Update, Remove between collections using Linq

Here is my scenario. I am using WPF and making use of two way binding to show a collection objects received from a service call every 60 seconds. On the first call I create a collection of objects that will be displayed from the collection of service objects. On subsequent calls I need to compare the service collection to the existing collection and then do one of three things:
If the Item exists in both collections then update ALL of the values for the object in the Display collection with the values from the object in the service collection.
If the item Exists in the Service Collection and not the Display Collection then add it to the Display Collection.
If the Item exists in the Display collection and not the Service Collection then remove it from the Display collection.
I am looking for the best way to do this.
Adding & Removing
Is it smarter to do a Left Join here and return everything essentially unique to one side of the other and then add or remove that as appropriate?
Should I attempt to do a Union since Linq is supposed to merge the two and ignore the duplicates?
If so how does it decide uniqueness? Is it evaluating all the properties? Can I specify which collection to keep from and which to discard in merging?
Should I use Except to create a list of differences and somehow use that?
Should I create a new list to add and remove using Where / Not In logic?
Updating
Since the collections aren't dictionaries what is the best way to do the comparison:
list1.ForEach(x => list2[x.Id].SomeProperty = x.SomeProperty);
Is there some way of copying ALL the property values other than specifying each one of them similar to above? Can I perform some kind of shallow copy within Linq Without replacing the actual object that is there?
I don't want to just clear my list and re-add everything each time because the object is bound in the display and I have logic in the properties that is tracking deviations as values change.
You can use the except and intersect methods to accomplish most of what you are looking to do.
However, depending on the size of your objects this can be very resource intensive.
I would recommend the following.
var listIDsA = collectionA.Select(s => s.Id).Distinct().ToList();
var listIDsB = collecitonB.Select(s => s.Id).Distinct().ToList();
var idsToRemove = listIDsB.Select (s => !listIDsA.Contains(s.Id)).ToList();
var idsToUpdate = listIDsB.Select(s => listIDsA.Contains(s.Id)).ToList();
var idsToAdd = listIDsA.SelecT(s => !listIDsB.Contains(s.Id)).ToList();
Then using the three new collections you can add/remove/update the apporpriate records.
You can also use a hashedset instead of IEnumerables for better performance. This will require you to create an extension class to add that functionality. Here is a good explanation of how to do that (it's not complicated).
How to convert linq results to HashSet or HashedSet
If you do this, you will need to replace the .ToList() in the first two lines to .ToHasedSet()
For your comparison you need to overwrite equals and get hashcode
Object.GetHashCode Method
Then you can use List.Contains
List.Contains Method
If you can use HashSet then you will get better performance
Code not tested
ListDisplay.Remove(x => !ListSerice.Contains(x));
Foreash(ListItem li in ListDisplay)
{
ListItem lis = ListSerice.FirstOrDefault(x => x.Equals(li));
if (lis == null) continue;
// perform update
}
Foreach(ListItem li in ListSerice.Where(x => !ListDisplay.Contains(x))) ListDisplay.Add(li);

Selecting all objects from a list that return true on a member function, as a list?

I'm new to LINQ and trying to get a hold of it.
It's been useful so far for various things such as cutting down the code required, like when using .ForEach() to run a function on every object.
Now I'm trying to get a list of all objects from a master list, when their IsMouseOver() function returns true.
As a standard foreach it looks like this:
this.m_EntHovered.Clear();
foreach (EntEditor ent in this.m_EntAll)
{
if (ent.IsMouseOver(mousePos))
this.m_EntHovered.Add(ent);
}
But I wanted to shortern this using LINQ, however the shortest I could get it wasn't much shorter:
this.m_EntHovered = (from ent in this.m_EntAll
where ent.IsMouseOver(input)
select ent).ToList<EntEditor>();
Is there a better way to achieve what I'm after or is what I'm doing fine?
There isn't necessarily a better way to do it, but you can write it more succinctly via:
this.m_EntHovered = this.m_EntAll.Where(ent => ent.IsMouseOver(input)).ToList();
Note that this is not the same as your original, however, as you're assigning a new list, instead of adding items to the existing list. To get the same behavior (which may not be needed), you could do:
this.m_EntHovered.Clear();
this.m_EntHovered.AddRange(this.m_EntAll.Where(ent => ent.IsMouseOver(input)));

search arraylist by property using binarysearch

I currently have an arraylist containing classes in C#. The arraylist is filled like this:
foreach (XmlElement Path in locaties)
{
ISoundSource track = engine.AddSoundSourceFromFile(Path.InnerXml);
mixarray.Add(track);
}
then the array has many ISoundSource classes as its items. Now the thing that sets them apart in the array is their 'name' property. Later on I want to get the ISoundSource from the array by doing a search. I looked up on how to search arraylists and it is said to use a binarysearch but I don't see a way to look up an object with a certain property. How can I get the item from the array which has the name I specify?
You should probably use a Dictionary<,> as it will be much easier to maintain. Also, you should use List<> instead of ArrayList. If you must use BinarySearch, you will have to pass it a custom implementation of IComparer in order to have it use the Name property. Here's an example with a dictionary:
var dictionary = new Dictionary<string, ISoundSource>();
foreach (XmlElement Path in locaties)
{
ISoundSource track = engine.AddSoundSourceFromFile(Path.InnerXml);
mixarray.Add(track);
dictionary[track.Name] = track;
}
ISoundSource item = dictionary["MyTrackName"];
Check out the two parameter overload of BinarySearch which takes an IComparer as the second parameter - you then need to create a small class that inherits from IComparer that will compare the names of two of your Track objects, and pass an instance of this comparer into the BinarySearch.
There are many ways to do what you're asking for, and the right way depends on information that you haven't provided:
Does the Name property uniquely identify items?
Does every item have a Name?
Does the match have to be exact?
Is it important to know what order the items were originally added to the list in, i.e. the order that they appear in the source XML?
Are you trying to find items given their Name, or access them in order by their Name?
How important is it that this be efficient?
It may be that the right solution is to simply use LINQ to find an item:
ISoundSource track = mixarray
.Cast<ISoundSource>
.Where(x => x.Name == name)
.FirstOrDefault();
which will set track to the first item in the list whose name matches the value you're looking for, and to null if there's no match found. (If you use a List<ISoundSource> instead of an ArrayList, you can omit the Cast<ISoundSource> - one of many, many reasons to use List<T> over ArrayList in most cases.)
Most of the time I'll use a Dictionary<TKey, TValue> for this kind of thing, but that's because most the time the answers to those questions are yes, yes, yes, no, don't care about the order, pretty important.
For posterity, here is an alternative way to generate a dictionary using a simple Linq expression.
var dictionary = locaties
.Select(p->engine.AddSoundSourceFromFile(Path.InnerXml))
.ToDictionary(t->t.Name);
The .Select() transforms each node into an ISoundSource. When done, a collection (IEnumerable of ISoundSource) is returned. The .ToDictionary() then converts that list of ISoundSource to a Dictionary of string, ISoundSource.
This requires .NET Framework 3.5 or higher.

Categories

Resources