IEnumerable - exclude using an array - c#

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

Related

Linq - Performant selection of specific class properties in list

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

Instantiating a List

If I want to instantiate a array the syntax is
int[] items = new int[] { 1, 2, 3 };
and the shortcut is
int[] items = {1,2,3};
Now I want to do the same to a List.
Question:
why does this work:
List<int> items = new int[] { 1, 2, 3 }.ToList();
but not this:
List<int> items = { 1, 2, 3 }; //invalid
or
List<int> items = { 1, 2, 3 }.ToList(); //invalid
The syntax
int[] array = {1,2,3};
is special syntactic sugar for array initialization. {1,2,3} is not itself an array yet.
This line
List<int> list = new[] {1,2,3}.ToList();
works because the expression new[] {1,2,3} returns an int[], which implements IEnumerable<int> and so you can call ToList() on it.
In the specs that's the difference between 12.6 Array initializers and 7.5.10.2 Array creation expressions.
List<T> has a constructor that takes an IEnumerable<T> as argument to initialize the list's content with. So you can either call
List<int> list = new List<int>(new[]{1,2,3});
or use the collection initializer syntax sugar:
List<int> list = new List<int> {1,2,3};
which is converted by the compiler to something like this:
var tmp = new List<int>();
tmp.Add(1);
tmp.Add(2);
tmp.Add(3);
List<int> list = tmp;

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) */

SortedList<> and its strange setters

I initially had some code, which when simplified, looks like this:
var planets = new List<Planet>
{
new Planet {Id = 1, Name = "Mercury"},
new Planet {Id = 2, Name = "Venus"},
};
I got into a scenario where the list was being populated all at once, but the reads weren't fast enough. And so, I changed this to use a SortedList instead.
I later realized that I could rewrite it like this
var planets = new SortedList<int, Planet>
{
{1, new Planet {Id = 1, Name = "Mercury"}},
{2, new Planet {Id = 2, Name = "Venus"}},
//in my actual code, i am reading the ids from a db
};
But before I got to this approach, I had the code written like this
var planets = new SortedList<int, Planet>
{
Keys = {1, 2},
Values =
{
new Planet {Id = 1, Name = "Mercury"},
new Planet {Id = 2, Name = "Venus"},
}
};
which gives me this exception
System.NotSupportedException: This operation is not supported on SortedList
nested types because they require modifying the original SortedList.
at System.ThrowHelper.ThrowNotSupportedException(ExceptionResource resource)
at System.Collections.Generic.SortedList`2.KeyList.Add(TKey key)
which I found to be very strange, coz IMHO, I wasn't really modifying the "original SortedList" as it claims, and what "nested types" is it talking about? Is it the list of keys internal to the SortedList?
I see then that the Keys and Values properties in SortedList don't actually have setters. They are read-only properties, and yet, I don't get a compile-time error. I am allowed to make a set call, as I can see in the stack trace with KeyList.Add. I feel the only reason why this fails is because of an explicit check within SortedList, which seems bizarre to me!
For instance
var str = new String {Length = 0}; gives me a compile-time error as expected, since Length is a read-only property, as does planets.Keys = null;
Someone please tell me - what simple fact am I overlooking here?
The code that you've written is comparable to this:
var planets = new SortedList<int, Planet>();
planets.Keys.Add(1);
planets.Keys.Add(2);
planets.Values.Add(new Planet { Id = 1, Name = "Mercury" });
planets.Values.Add(new Planet { Id = 2, Name = "Venus" });
SortedList requires that you add the value and key at the same time via SortedList<TKey, TValue>.Add(TKey key, TValue value) method, so that it can sort the value by the key. The implementation of the IList<T> which is used for Keys and Values internally does not support adding a respective key or value independently via the IList<T>.Add(T value) method.
You should be able to reproduce this error by calling Keys.Add(...) or Values.Add(...)
My initial query about the SortedList has now minimized to this concern about array, collection & object initializers, and the way the compiler interprets them differently. Thanks to #Haney again for the first answer to guide me towards this point of view, and to ILSpy for these insights.
Here are some array and collection initializers:
int[] a = { 1, 2, 3 };
int[] b = new int[] { 1, 2, 3 };
IList<int> c = { 1, 2, 3 };
IList<int> d = new int[] { 1, 2, 3 };
They all look kind of similar. Here, the compiler produces the exact same output for a & b. For c, we will get this compile-time error:
Can only use array initializer expressions to assign to array types.
Try using a new expression instead.
which makes sense since we shouldn't use array initializers for collections. But then, d produces the exact same result as a & b. And I thought that was an array initializer as well. Apparently not.
Now consider this class
class MyCollectionContainer
{
public int[] MyIntArray { get; set; }
public IList<int> MyList { get; set; }
}
and this code that operates on it
var containerA = new MyCollectionContainer { MyIntArray = { 1, 2, 3 } };
var containerB = new MyCollectionContainer { MyIntArray = new int[]{ 1, 2, 3 } };
var containerC = new MyCollectionContainer { MyList = { 1, 2, 3 } };
var containerD = new MyCollectionContainer { MyList = new int[]{ 1, 2, 3 } };
containerA gives this compile-time error:
Cannot initialize object of type 'int[]' with a collection initializer
For containerB, the compiler effectively converts it into this code:
MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyIntArray = new int[] {1, 2, 3};
For containerD, its pretty much the same, barring the fact that its another property that gets initialized:
MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyList = new int[] {1, 2, 3};
For containerC, the compiler morphs it into:
MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyList.Add(1);
myCollectionContainer.MyList.Add(2);
myCollectionContainer.MyList.Add(3);
This results in a run-time NullReferenceException since MyList is not initialized.
This means the only valid ways to initialize the collection container object here is containerB and containerD. To me, this clearly shows that object initializers are different when compared to array & collection initializers, in the way the compiler interprets them.

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