Linq - Performant selection of specific class properties in list - c#

I'm having a class with several material properties (e.g . temperature). The cross section of a sample is represented by a list of class elements.
I only need material properties in multiple methods at specific positions of the cross section or indices, respectively.
At the moment I'm using a Linq-select to first create an IEnumerable of the needed property.
Than I'm creating a list using the IEnumerable, where I can select the wanted elements by index.
Example (indices is a List with :
var indices = new List<int>() {1, 3, 7, 15, 30, 50};
var Ts = microstructures.Select(x => x.T).ToList();
var list = new List<double>();
for (int i = 0; i < indices.Count; i++)
{
list.Add(Ts[indices[i]]);
}
Is there a more efficient way without creating a list to perform this task?
microstructures has < 100 elements, indices ~ 10 and the properties of the microstructure can be complex classes themselves.

You may filter the T directly using the where overload that expose the object and it's index. msdn
Then replace the Select by a Select many to flattern List<List> to List
public class Toto {
public List<int> T { get; set; }
}
var target = new HashSet<int>{ 1, 3, 7, 15, 30, 50 };
var inputs = Enumerable.Range(0, 10) // generate Data Sample
.Select(x=>new Toto
{
T = Enumerable.Range(100*x,100).ToList()
});
var r = inputs.SelectMany(x => x.T.Where((y,i)=> target.Contains(i)));
live Demo

Related

IEnumerable - exclude using an array

Does anyone know if it's possible to create a new IEnumerable by using an array parameter to exclude values.
For instance, below is an example of how I imagine it would look.
class Item
{
public int id { get; set; }
public string name { get; set; }
}
IEnumerable looks like this:
item1 {id = 1}
item2 {id = 2}
item3 {id = 3}
I want to create a new IEnumerable but exclude the id numbers in the array.
Made up code to suggest idea:
Int32[] arrayList = {1,2};
var newIEnumerable = _exisitingIEnumerable.Where(o => (o.id NOT IN arrayList));
Looking at your question again, when the element type of _exisitingIEnumerable is not the same as that of arrayList, you will need to use Where to filter out the elements of arrayList
_exisitingIEnumerable.Where(o => !arrayList.Contains(o.Id))
Original answer:
_exisitingIEnumerable.Except(arrayList)
will return the distinct elements from _exisitingIEnumerable that are not in arrayList
If you need duplicates, you can use
_exisitingIEnumerable.Where(o => !arrayList.Contains(o))
What's wrong with the approach you suggested in the question? You can use Where and check if the array contains the value. Below the example using List as a target collection:
var myList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
int[] myArray = { 1, 2, 3 };
var result = new List<int>(myList.Where(n => !myArray.Contains(n)));

removing items from list that exist in another list based on an ID member

I have a model:
public class Post
{
public int PostId { get; set; }
public string Description { get; set; }
}
I have two lists:
List<Post> posts
List<Post> exceptions
I want to remove all items in "posts" that have a PostId matching that of an item in "exceptions"
I have tried:
foreach (var post in posts)
{
if (exceptions.Where(x => x.PostId == post.PostId) != null)
{
posts.RemoveAll(x => x.PostId == post.PostId);
}
}
but I bet there is a cleaner way to do it.
Thanks!
Just get the posts you want to keep and override the original list:
posts = posts.Where(p => !exceptions.Any(e => e.PostId == p.PostId).ToList();
First point : Your can't remove an item of posts when you do a foreach on posts.
Your should use a for loop instead.
Second point : use a map between each postid and the object post containing thid id, before looping. So your won't have to have a n^2 complexity.
For easier example calculation, I have implemented the two list to contain integer numbers, not class objects but the logic is the same. As far as I understood from your example, you want to remove all the posts objects that are available in exceptions list.
List<int> posts = new List<int>() { -3, -2, -1, 0, 1, 2, 3 };
List<int> exceptions = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
IEnumerable<int> intersection = exceptions.Intersect(posts); /* returns the numbers that are both in the two lists */
posts.RemoveAll(p => intersection.Contains(p)); /* remove the numbers from 'posts' that are intersected (1, 2, 3 are removed) */

Rearrange a list based on given order in c#

I have a list as follows:
{CT, MA, VA, NY}
I submit this list to a function and I get the optimum waypoint order list
{2,0,1,3}
Now I have to rearrange the list as per the order that is newly provided. i.e. after rearranging, the list should look like:
{VA, CT, MA, NY}
What is the optimum way to do it? Using linq is there a way?
You could try the following:
var list = new List<string>{"CT", "MA", "VA", "NY"};
var order = new List<int>{2, 0, 1, 3};
var result = order.Select(i => list[i]).ToList();
This seems like the simplest approach:
oldItems = LoadItems(); //{"CT","MA","VA","NY"};
List<string> newItems = List<string>();
foreach(int idx in returnedIndexes)
{
newItems.Add(oldItems[idx]);
}

c# Linq grouped by with composite key of int, list<int>

I have a product object that has a certain location and allowable shipping methods for that product. What I'm trying to do is Group the products by location AND by the allowable ship methods.
For example the following example data would product two groupings one with IDLocation = 1 and ShipMethods of 1,2 with a count of 2 and the other would be IDLocation = 1 and ShipMethods of 1,2 with a count of 3.
public class CartProduct
{
public int IDLocation { get; set; }
public List<int> ShipMethods { get; set; }
public List<CartProduct> GetExampleData()
{
return new List<CartProduct>() { new CartProduct() { IDLocation = 1, ShipMethods = new List<int>(){ 1, 2 } },
new CartProduct() { IDLocation = 1, ShipMethods = new List<int>(){ 1, 2 } },
new CartProduct() { IDLocation = 1, ShipMethods = new List<int>(){ 3, 4 } },
new CartProduct() { IDLocation = 1, ShipMethods = new List<int>(){ 3, 4 } },
new CartProduct() { IDLocation = 1, ShipMethods = new List<int>(){ 3, 4 } }
};
}
}
I would like to see a grouping of IDLocation first, then if the ship methods are the same group those together as well.
I've tried several version of group by and select many with no luck.
List<CartProduct> CPList = new CartProduct().GetExampleData();
var GroupItems = CPList.GroupBy(x => x.IDLocation) // then by ShipMethods??
I think that the key is to use GroupBy's ability to specify a "key" to use when grouping. You want to group things together that have the same IDLocation and the same set of ShipMethods, so the key should include those things. Ideally you'd use a proper comparer that does the right thing. The hackish way to do this (which is easier to write, so I can tell this'll work) is to mash everything together into a string, so that the normal string comparison does what we want. So here's the quick answer:
var answer = GetExampleData()
.GroupBy(x=>String.Format("{0} {1}",
x.IDLocation,
String.Join(",",x.ShipMethods.OrderBy(y=>y))));
For better performance, you'll have to implement the "proper" way I'm describing. It's a bit of work but it shouldn't be too hard.
edit: I am sorting the ShipMethods so that something that can be shipped via 1 or 2 is correctly seen to be the same thing as if it can be shipped via 2 or 1. Ideally the ShipMethods list is already sorted so we can save the time.
(the funky formatting is to try to make it visible without scrolling)
The comparer argument in GroupBy allows you to define equality for purposes of the object grouping. The comparer is a separate class that compares two like objects and returns true if they are equal. The class, which needs to implement IComparer<CartItem>, can be implemented like this:
class CartGroupComparer : IEqualityComparer<CartProduct>
{
public bool Equals(CartProduct x, CartProduct y)
{
return x.IDLocation == y.IDLocation
&& x.ShipMethods.OrderBy(x=>x)
.SequenceEqual(y.ShipMethods.OrderBy(x=>x));
}
public int GetHashCode(CartProduct obj)
{
return obj.IDLocation.GetHashCode()
^ obj.ShipMethods.Sum().GetHashCode();
}
}
(Note: for simplicity, this assumes that ShipMethods will never be null.)
The Equals method tests two items for equality; if equal, they will be added added to the same group. The GetHashCode method must return an equal value for equal items, and a simple implementation is above.
You can use this comparer directly in your GroupBy clause:
new CartProduct().GetExampleData()
.GroupBy(a => a, new CartGroupComparer());

Sorting an array related to another array

I have two arrays, x and y, where y is the value of the tens of every element in x. Now, I want to sort y. But, the order of y will be different of x's. So, I can't tell after sorting which element in y was related to, for instance, x[0].
I want a "double sorting" maybe.
Array.Sort has an overload that accepts two arrays; one for the keys, and one for the items. The items of both are sorted according to the keys array:
int[] keys = { 1, 4, 3, 2, 5 };
string[] items = { "abc", "def", "ghi", "jkl", "mno" };
Array.Sort(keys, items);
foreach (int key in keys) {
Console.WriteLine(key); // 1, 2, 3, 4, 5
}
foreach (string item in items) {
Console.WriteLine(item); // abc, jkl, ghi, def, mno
}
So in your case, it sounds like you want:
Array.Sort(y,x); // or Sort(x,y); - it isn't 100% clear
How about?
var selectedArr = new int[] { 1, 3, 5, 7, 9 };
var unorderArr = new int[] { 9, 7, 5, 3, 1 };
var orderedArr = unorderArr.OrderBy(o => selectedArr.IndexOf(o));
If we have two arrays of complex objects and want to sort them according to one of the two arrays then we can use the next approach:
// We want to sort "people" array by "Name" and
// accordingly to it reorder "countries" array.
Person[] people = new Person[]
{
new Person {Name = "Fill"},
new Person {Name = "Will"},
new Person {Name = "Bill"},
};
Country[] countries = new Country[]
{
new Country {Name = "Canada"},
new Country {Name = "UK"},
new Country {Name = "USA"}
};
// Here we sort "people" array, but together with each "Person"
// in sorted array we store its "index" in unsorted array. Then we
// will use this "index" to reorder items in "countries" array.
var sorted = people
.Select((person, index) => new {person, index})
.OrderBy(x => x.person.Name)
.ToArray();
// Here "people" array is sorted by "Name", and
// "contries" array is reordered accordingly to it.
people = sorted.Select(x => x.person).ToArray();
countries = sorted.Select(x => countries[x.index]).ToArray();
Another approach is to use overload of the method Array.Sort with IComparer. At first we should implement IComparer:
private class PeopleComparer : IComparer<Person>
{
public int Compare(Person x, Person y)
{
return x.Name.CompareTo(y.Name);
}
}
And then we can sort two our arrays:
Array.Sort(people, countries, new PeopleComparer());
Here is complete sample that demonstrates these two approaches.
If y is always the tens value of x, y probably shouldn't exist - you should probably just calculate it's value directly off of x when needed.
In general, sorting parallel arrays is only possible (without hand rolling a sort algorithm) when the sort algorithm takes a custom "swap" function, which you can implement in terms of swapping elements in both arrays simultaneously. std::sort in C++ and qsort in C don't allow this.
Also in the general case, consider a single array where the element is a pair of items, rather than a parallel array for each item. This makes using "standard" algorithms easier.

Categories