Separate collection on 2 different NEW collections - c#

I have collection of elements and one additional small collection as filter.
I need to separate it on 2 new collections by some filter. In my case it is first collection that contains some elements and another that doesn't.
There aren't items that doesn't exists out of that 2 new collections.
I did it like :
var collection1= baseCollection.Where(r => filterCollection.Contains(r.Property)).ToList();
var collection2= baseCollection.Where(r => !filterCollection.Contains(r.Property)).ToList();
But is there another, I hope more elegant way, to separate collection?
For me it looks like "I repeat myself", use almost the same code 2 times.

You can create a variable for the function - this way you will not "repeat yourself" (wouldn't use in this case, there are better options below, but still an option):
Func<YourClass,bool> filtering = (r) => filterCollection.Contains(r.Property);
var collection1 = baseCollection.Where(r => filtering(r));
var collection2 = baseCollection.Where(r => !filtering(r));
If your type of the collection overrides Equals and GetHashCode you can use Except:
var collection1 = baseCollection.Where(r => filterCollection.Contains(r.Property));
var collection2 = baseCollection.Except(collection1);
Using Except with a given IEqualityComparer (Check also first comment for guidlines):
public class Comparer : IEqualityComparer<YourClass>
{
public bool Equals(YourClass x, YourClass y)
{
// Your implementation
}
public int GetHashCode(YourClass obj)
{
// Your implementation
}
}
var collection1 = baseCollection.Where(r => filterCollection.Contains(r.Property));
var collection2 = baseCollection.Except(collection1, new Comparer());
You can also use GroupBy (probably less good performance wise):
var result baseCollection.GroupBy(r => filterCollection.Contains(r.Property))
.ToDictionary(key => key.Key, value => value.ToList());
var collection1 = result[true];
var collection2 = result[false];
Otherwise another way will just to use a loop:
List<YourType> collection1 = new List<YourType>();
List<YourType> collection2 = new List<YourType>();
foreach(var item in baseCollection)
{
if(filterCollection.Contains(item.Property))
{
collection1.Add(item);
}
else
{
collection2.Add(item);
}
}

Related

Order IGrouping in C#

Example is here, should work in online compilers:
internal class Program
{
static void Main(string[] args)
{
var i1 = new Item();
i1.Val1 = 1;
i1.Val2 = 2.1;
var i2 = new Item();
i2.Val1 = 1;
i2.Val2 = 1.5;
var i3 = new Item();
i3.Val1 = 3;
i3.Val2 = 0.3;
var list = new List<Item>
{
i1,
i2,
i3
};
var grouped = list.GroupBy(x => x.Val1);
Program p = new Program();
foreach(var group in grouped)
p.Func(group);
}
public void Func(IGrouping<int, Item> list)
{
list.OrderBy(x => x.Val2); //list will be ordered, but not saved
list = (IGrouping<int, Item>)list.OrderBy(x => x.Val2); //exception
}
}
public class Item
{
public int Val1 { get; set; }
public double Val2 { get; set; }
}
It's simplified code of what I'm trying to do - I need to order list inside Func, but I have no idea how. First line works in theory, but since it's not a void it's not working in practice - list is not actually ordered.
Second line should work, actually Visual Studio suggested that, but it throws runtime exception - Unable to cast object of type System.Linq.OrderedEnumerable to System.Linq.IGrouping.
I'm out of ideas for the time being, but there is no way of bypassing it - I absolutely need to order it there.
Edit
My current solution is to use Select(x => x) to flatten the IGrouping to normal List, this way I can easily order it and edit values without losing reference to grouped. If you really want to keep IGrouping then you are out of luck, does not seem to be possible.
Try this.
var grouped = list.GroupBy(x => x.Val1).Select(a=> a.OrderBy(a=>a.Val2).ToList());
OrderBy returns IOrderedEnumerable you can't cast that to IGrouping
Use First method at the end in order to get IGrouping collection of ordered items.
public void Func(IGrouping<int, Item> list)
{
list = list.OrderBy(x => x.Val2).GroupBy(x => x.Val1).First();
}
Your example code doesn't show what you are trying to arrive at.
list.OrderBy(x => x.Val2); //list will be ordered, but not saved
OrderBy doesn't order the existing collection in-place. It effectively returns a new collection.
list = (IGrouping<int, Item>)list.OrderBy(x => x.Val2); //exception
OrderBy returns an IOrderedEnumerable<TElement>. Both IOrderedEnumerable<TElement> and IGrouping<TKey,TElement> derive from IEnumerable<TElement> but you can't cast an IOrderedEnumerable to an IGrouping.
If all you want is to write out the values, then Func could be:
public IEnumerable<Item> Func(IGrouping<int, Item> list)
{
return list.OrderBy(x => x.Val2);
}
and the foreach loop could be:
foreach(var group in grouped)
{
var orderedList = p.Func(group);
Console.WriteLine($"group: {group.Key}");
foreach (var value in orderedList)
{
Console.WriteLine($" {value.Val2}");
}
}
Hopefully this helps.

C# List.OrderBy with multiple lists

I got 5 lists. One is containing the date of release and the others are the attributes of that list but seperated in multiple lists.
List<string> sortedDateList = x1.OrderBy(x => x).ToList();
This code is sorting the list with the oldest date first, like it should. But I also want to sort (sync) the other attributes list, because they need the same index as the date.
How can I realize that? I'm new to Linq-methods.
You could use the .Zip() method to combine the lists as described here. You could combine them into a class or an anonymous type and then sort them.
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => new { Num = first, Word = second });
var sorted = numbersAndWords.OrderBy(x => x.Num).ToList();
Alternately, if you can guarantee that all the lists are of the same length (or just grab the shortest list) you could use the following instead of the .Zip() extension.
var numbersAndWords = numbers.Select((number, i) => new { Num = number, Word = words[i], Foo = myFoos[i] }); // Where myFoos is another collection.
And in the lambda combine all the items from the separate lists into an object at the same time by accessing the collection by index. (Avoids multiple use of .Zip()) Of course, if you try to access an index that is larger than the list size you will get an IndexOutOfRangeException.
As far as I understand your question, you have different lists containing properties of certain objects. You should definitely look into storing all data into one list of a class of your making, where you consolidate all separate information into one object:
var list = new List<YourClass>
{
new YourClass
{
Date = ...,
OtherProperty = ...,
},
new YourClass
{
Date = ...,
OtherProperty = ...,
},
};
var ordered = list.OrderBy(o => o.Date);
But if you insist in storing different properties each in their own list, then you could to select the dates with their index, then sort that, as explained in C# Sorting list by another list:
var orderedDates = list.Select((n, index) => new { Date = n, Index = index })
.OrderBy(x => x.Date)
.ToList();
Then you can use the indexes of the sorted objects to look up the properties in the other lists, by index, or sort them on index as explained in C# Sort list while also returning the original index positions?, Sorting a list and figuring out the index, and so on.
It almost sounds like you want 1 list of a class.
public class MyClass{
public string Date{get; set;} //DateTime is a better type to use for dates by the way
public string Value2{get; set;}
public string Value3{get; set;}
public string Value4{get; set;}
public string Value5{get; set;}
}
...
var sortedDateList = x1.OrderBy(x => x.Date).ToList()
Create an Object containing the date and attributes:
public class DateWithAttributes
{
public string Date {get;set;}
public Attribute Attribute1 {get;set;}
public Attribute Attribute2 {get;set;}
...
}
List<DateWithAttributes> DateWithAttributesList = new List<DateWithAttributes>()
{
DateWithAttribute1,
DateWithAttribute2
}
List<DateWithAttributes> sortedDateList = DateWithAttributesList.OrderBy(x => x.date).ToList();
If you want to keep the lists separate, and/or create the ordered versions as separate lists, then you can concatenate the index to the dates and sort by dates, then use the sorted indexes:
var orderedIndexedDateOfReleases = dateOfReleases.Select((d, i) => new { d, i }).OrderBy(di => di.d);
var orderedDateOfReleases = orderedIndexedDateOfReleases.Select(di => di.d).ToList();
var orderedMovieNames = orderedIndexedDateOfReleases.Select(di => movieNames[di.i]).ToList();
If you don't mind the result being combined, you can create a class or use an anonymous class, and again sort by the dates:
var orderedTogether = dateOfReleases.Select((d, i) => new { dateOfRelease = d, movieName = movieNames[i] }).OrderBy(g => g.dateOfRelease).ToList();

Order two generic collections of complex type using a property

I have below method to compare two collections of any given complex type. Before perform the Sequence Equal operation , I need to sort both collections using a given property of the type being compared to make sure that objects are in the same order.
public static bool CompareCollections<T>(IEnumerable<T> expectedCollection, IEnumerable<T> actualCollection,Func<T,string> selector)
{
if (expectedCollection.Count() != actualCollection.Count())
{
return false;
}
expectedCollection.OrderBy(selector).ToList();
actualCollection.OrderBy(selector).ToList();
return expectedCollection.SequenceEqual(actualCollection, new TypeComparer<T>());
}
I am invoking the method as below
CompareCollections(first,second,x => x.ID)
Where first and second collections look like below, NOTE that both collections have the same two objects, but second one have its items in reverse order and I am expecting OrderBy method to sort it before compare for equality. But it doesn't sort as I expected
var first = new List<Fee>()
{
new Fee
{
ID = "00001",
BaseFee = "3.50"
},
new Fee
{
ID = "00002",
BaseFee = "5.50"
}
};
var second = new List<Fee>()
{
new Fee
{
ID = "00002",
BaseFee = "5.50"
},
new Fee
{
ID = "00001",
BaseFee = "3.50"
}
};
You need to assign the results of OrderBy().ToList() into a new local variable. OrderBy returns an ordered sequence, it does not sort the incoming sequence in-place. Thus:
var sortedExpectedCollection = expectedCollection.OrderBy(selector);
var sortedActualCollection = actualCollection.OrderBy(selector);
return sortedExpectedCollection.SequenceEqual(sortedActualCollection , new TypeComparer<T>());
expectedCollection = expectedCollection.OrderBy(selector).ToList();
actualCollection = actualCollection.OrderBy(selector).ToList();
If sorting is the issue just assign the values to the list and do the comparison.
You may do that as following:
public static bool CompareCollections<T>(IEnumerable<T> expectedCollection, IEnumerable<T> actualCollection,
Func<T, string> selector)
{
var expectedAsList = expectedCollection.OrderBy(selector).ToList();
var actualAsList = actualCollection.OrderBy(selector).ToList();
return expectedAsList.Count == actualAsList.Count &&
!expectedAsList.Where((t, i) => !t.Equals(actualAsList[i])).Any();
}

LINQ select more fields of same type

For simple example, I have a class like this:
public class MyClass {
public string name;
public int item1;
public int item2;
}
and a List(MyClass), how do I query the list to create List(int) of both fields item1 and item2? It seems it should be simple but I am struggling a long time.
var result = myList.Select(i => i.item1).ToList() //selects only one field
I know i could use anonymous type but since both item1 and item2 are integers I dont need any new type.
var result = myList.Select(i => new { i.item1, i.item2} ).ToList() //dont need new type, both are integers
how to create list of int? Or did I misunderstood what the anonymous types do?
If you don't like using anonymous types, you could always use a Tuple
var result = myList.Select(i => Tuple.Create(i.item1, i.item2) )
But since both item1 and item2 are integers, you can use an array:
var result = myList.Select(i => new[] { i.item1, i.item2 } )
This will result in a IEnumerable<int[]>. If you want an IEnumerable<int> (with each record's item1 and item2 together in one result set), use SelectMany:
var result = myList.SelectMany(i => new[] { i.item1, i.item2 } )
If you want a flattened list you can do:
List<int> ints = myList.SelectMany(i => new[] { i.item1, i.item2 }).ToList();
if you want to keep the values together you can create a tuple:
List<Tuple<int, int>> pairs = myList.Select(i => Tuple.Create(i.item1, i.item)).ToList():
You can use the following expression for example to create a list containing both items fro all elements of the original list myList.
var result = myList.Select(i => item1).Concat(myList.Select(i => i.item2));

Can List.Distinct() apply to the list type of List<List<String>>?

List<List<String>> ls = new List<List<String>>();
List<String> l1 = new List<String>();
l1.Add("Peter");
l1.Add("123");
ls.Add(l1);
List<String> l2 = new List<String>();
l2.Add("Peter");
l2.Add("123");
ls.Add(l2);
ls = ls.Distinct().ToList();
I suppose there are only one element in ls, but actually there are still 2 elements. What are the possible reasons?
That's because List<T> has no Equals and GetHashCode implemented, so standard reference comparison is being performed. And it returns false, because you have two separated lists.
You can write your own IEqualityComparer<List<string>> implementation and provide it as Distinct method parameter. Within the comparer you can use Enumerable.SequenceEqual) method to check if lists has the same content.
With your case, you have to build the custom comparer to implement the interface IEqualityComparer<List<string>>, and use SequenceEqual to compare in Equal method:
public class CustomComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(List<string> obj)
{
int hashCode = 0;
foreach (string str in obj)
{
hashCode ^= str.GetHashCode();
}
return hashCode;
}
}
Then:
ls = ls.Distinct(new CustomComparer()).ToList();
Another tricky way to distinct by using GroupBy:
ls = ls.GroupBy(x => string.Join("", x))
.Select(g => g.First())
.ToList();
The Comparison used by List is based on reference comparison. Since the 2 lists are different instances, they are not the same and distinct considers them to be different.
If you want the distinct values, you can use .SelectMany() to select the strings in each list within the parent list:
var list = new List<List<String>>();
var list1 = new List<String>();
list1.Add("Peter");
list1.Add("123");
list.Add(list1);
var list2 = new List<String>();
list2.Add("Peter");
list2.Add("123");
list.Add(list2);
var distinct = list.SelectMany(x => x).Distinct().ToList();
distinct.ForEach(x => Console.WriteLine(x));

Categories

Resources