Select from a List<T> - c#

I'm sure there's an wasy way of doing this (I'm guessing one of the extension methods?), but am struggling to find it with Google.
Basically I have a List of custom classes; I want to select some items from this into a new List where one of the properties is equal to any value in another List.
Here's a (simplified) quick example of what I'm trying to do:
public class Job
{
public int Number;
public string ClientCompanyName;
}
List<Job> lstJobs = new List<Job>();
List<Job> lstCompare = new List<Job>();
normally I would do something like:
List<Job> lstFiltered = new List<Job>();
foreach(Job jobThis in lstCompare)
{
foreach(jobComp in lstCompare)
{
if(jobThis.Number = jobComp.Number)
{
lstFiltered.Add(jobThis);
}
}
}
Is there an extension method that neatens this last bit up into (ideally) a single line?
Cheers

You can use Intersect() for this:
http://msdn.microsoft.com/en-us/library/bb460136.aspx

Use Intersect.
For it to work with your custom comparison you either need to implement IEquatable<T> in your class or create a new class the implements IEqualityComparer<T> for your class and pass that to the overload of Intersect.

Jez,
You might be able to use the LINQ intersect function, or try:
var matches = from jobs in lstJobs
join comp in lstCompare on jobs.Number equals comp.Number
select jobs;
or LINQ syntax:
var matches = lstJobs.Join(lstCompare, jobs => jobs.Number,
comp => comp.Number, (jobs, comp) => jobs);
and here was reSharper's version based on your original loop:
List<Job> lstFiltered = (lstJobs.SelectMany(jobThis => lstCompare,
(jobThis, jobComp) => new {jobThis, jobComp})
.Where(#t => #t.jobThis.Number == #t.jobComp.Number)
.Select(#t => #t.jobThis)).ToList();
slightly verbose, but another way to skin the cat.
[edited] as had set to new list, rather than selected elements - doh

var lstFiltered = lstJobs
.Where(job => lstCompare.Any(item => item.Number == job.Number))
.ToList();
The above solution works well if the number of items in the lstCompare is small. For bigger comparison lists you may want to use some hash based collection.
var compareSet = new HashSet<int>(lstCompare.Select(item => item.Number));
var lstFiltered = lstJobs
.Where(job => compareSet.Contains(job.Number))
.ToList();
If the comparison condition is more complex or it is needed in several places, you should create a comparer class that implements IEqualityComparer<T>. Then you could use the Intersect() method as others have already suggested. However, it is not functionally identical with the above solutions. It returns only distinct elements while my solutions return all matching elements. It may be a significant difference in some applications.
My second example can be easily changed to use IEqualityComparer<T> if necessary. The HashSet<T> takes the comparer as second parameter.

Related

Sorting a list of objects based on another

public class Product
{
public string Code { get; private set; }
public Product(string code)
{
Code = code;
}
}
List<Product> sourceProductsOrder =
new List<Product>() { new Product("BBB"), new Product("QQQ"),
new Product("FFF"), new Product("HHH"),
new Product("PPP"), new Product("ZZZ")};
List<Product> products =
new List<Product>() { new Product("ZZZ"), new Product("BBB"),
new Product("HHH")};
I have two product lists and I want to reorder the second one with the same order as the first.
How can I reorder the products list so that the result would be : "BBB", "HHH", "ZZZ"?
EDIT: Changed Code property to public as #juharr mentioned
You would use IndexOf:
var sourceCodes = sourceProductsOrder.Select(s => s.Code).ToList();
products = products.OrderBy(p => sourceCodes.IndexOf(p.Code));
The only catch to this is if the second list has something not in the first list those will go to the beginning of the second list.
MSDN post on IndexOf can be found here.
You could try something like this
products.OrderBy(p => sourceProductsOrder.IndexOf(p))
if it is the same Product object. Otherwise, you could try something like:
products.OrderBy(p => GetIndex(sourceProductsOrder, p))
and write a small GetIndex helper method. Or create a Index() extension method for List<>, which would yield
products.OrderBy(p => sourceProductsOrder.Index(p))
The GetIndex method is rather simple so I omit it here.
(I have no PC to run the code so please excuse small errors)
Here is an efficient way to do this:
var lookup = sourceProductsOrder.Select((p, i) => new { p.Code, i })
.ToDictionary(x => x.Code, x => x.i);
products = products.OrderBy(p => lookup[p.Code]).ToList();
This should have a running time complexity of O(N log N), whereas an approach using IndexOf() would be O(N2).
This assumes the following:
there are no duplicate product codes in sourceProductsOrder
sourceProductsOrder contains all of the product codes in products
you make the Code field/property non-private
If needed, you can create a safeguard against the first bullet by replacing the first statement with this:
var lookup = sourceProductsOrder.GroupBy(p => p.Code)
.Select((g, i) => new { g.Key, i })
.ToDictionary(x => x.Key, x => x.i);
You can account for the second bullet by replacing the second statement with this:
products = products.OrderBy(p =>
lookup.ContainsKey(p.Code) ? lookup[p.Code] : Int32.MaxValue).ToList();
And you can use both if you need to. These will slow down the algorithm a bit, but it should continue to have an O(N log N) running time even with these alterations.
I would implement a compare function that does a lookup of the order from sourceProductsOrder using a hash table. The lookup table would look like
(key) : (value)
"BBB" : 1
"QQQ" : 2
"FFF" : 3
"HHH" : 4
"PPP" : 5
"ZZZ" : 6
Your compare could then lookup the order of the two elements and do a simple < (pseudo code):
int compareFunction(Product a, Product b){
return lookupTable[a] < lookupTable[b]
}
Building the hash table would be linear and doing the sort would generally be nlogn
Easy come easy go:
IEnumerable<Product> result =
products.OrderBy(p => sourceProductsOrder.IndexOf(sourceProductsOrder.FirstOrDefault(p2 => p2.Code == p.Code)));
This will provide the desired result. Objects with ProductCodes not available in the source list will be placed at the beginning of the resultset. This will perform just fine for a couple of hundred of items I suppose.
If you have to deal with thousands of objects than an answer like #Jon's will likely perform better. There you first create a kind of lookup value / score for each item and then use that for sorting / ordering.
The approach I described is O(n2).

Lambda Expressions, Compare item1, if they are equal compare item2

I tried to search but I cannot seem to find my answer. I think an answer may exist as it not a uncommon question. I trying to say Sort by Item1. If they are equal, sort by Item2
sorted.Sort((a,b)=>(a.Item1.CompareTo(b.Item1)));
While you can build a comparer to do this with List<T>.Sort, it's much easier to use LINQ, which is built for this sort of thing:
sorted = unsorted.OrderBy(x => x.Item1).ThenBy(x => x.Item2).ToList();
If you really want to use Sort, you can use the ProjectionEqualityComparer in my MiscUtil project - but it won't be as nice as the LINQ approach.
var sorted = original.OrderBy(c => c.Item1).ThenBy(n => n.Item2).ToList()
Try this
As an alternative to the LINQ methods, you can create a comparer:
class FrobComparer : IComparer<Frob>
{
public int Compare(Frob x, Frob y)
{
int item1Comparison = x.Item1.CompareTo(y.Item1);
if (item1Comparison == 0)
return x.Item2.CompareTo(y.Item2);
return item1Comparison;
}
}
And then pass that into Sort(), assuming unsorted is a List<Frob>:
var sorted = unsorted.Sort(new FrobComparer());

Making a list distinct in C#

In C#, I have an object type 'A' that contains a list of key value pairs.
The key value pairs is a category string and a value string.
To instantiate object type A, I would have to do the following:
List<KeyValuePair> keyValuePairs = new List<KeyValuePair>();
keyValuePairs.Add(new KeyValuePair<"Country", "U.S.A">());
keyValuePairs.Add(new KeyValuePair<"Name", "Mo">());
keyValuePairs.Add(new KeyValuePair<"Age", "33">());
A a = new A(keyValuePairs);
Eventually, I will have a List of A object types and I want to manipulate the list so that i only get unique values and I base it only on the country name. Therefore, I want the list to be reduced to only have ONE "Country", "U.S.A", even if it appears more than once.
I was looking into the linq Distinct, but it does not do what I want because it I can't define any parameters and because it doesn't seem to be able to catch two equivalent objects of type A. I know that I can override the "Equals" method, but it still doesn't solve the my problem, which is to render the list distinct based on ONE of the key value pairs.
To expand upon Karl Anderson's suggestion of using morelinq, if you're unable to (or don't want to) link to another dll for your project, I implemented this myself awhile ago:
public static IEnumerable<T> DistinctBy<T, U>(this IEnumerable<T> source, Func<T, U>selector)
{
var contained = new Dictionary<U, bool>();
foreach (var elem in source)
{
U selected = selector(elem);
bool has;
if (!contained.TryGetValue(selected, out has))
{
contained[selected] = true;
yield return elem;
}
}
}
Used as follows:
collection.DistinctBy(elem => elem.Property);
In versions of .NET that support it, you can use a HashSet<T> instead of a Dictionary<T, Bool>, since we don't really care what the value is so much as that it has already been hashed.
Check out the DistinctBy syntax in the morelinq project.
A a = new A(keyValuePairs);
a = a.DistinctBy(k => new { k.Key, k.Value }).ToList();
You need to select the distinct property first:
Because it's a list inside a list, you can use the SelectMany. The SelectMany will concat the results of subselections.
List<A> listOfA = new List<A>();
listOfA.SelectMany(a => a.KeyValuePairs
.Where(keyValue => keyValue.Key == "Country")
.Select(keyValue => keyValue.Value))
.Distinct();
This should be it. It will select all values where the key is "Country" and concat the lists. Final it will distinct the country's. Given that the property KeyValuePairs of the class A is at least a IEnumerable< KeyValuePair< string, string>>
var result = keyValuePairs.GroupBy(x => x.Key)
.SelectMany(g => g.Key == "Country" ? g.Distinct() : g);
You can use the groupby statement. From here you can do all kind off cool stuf
listOfA.GroupBy(i=>i.Value)
You can groupby the value and then sum all the keys or something other usefull

LINQ without IEqualityComparer implementation

I have 2 collection with different classes. MyClass1 - Name,Age,etc MyClass2 - Nick, Age, etc
I want to find except of this collections. Something like
list1.Exept(list2, (l1,l2) => l1.Name==l2.Nick);
But i cannt write this code and need to implement my own comparer class with IEqualityComparer interface and it's looking very overhead for this small task. Is there any elegant solution?
Except really doesn't work with two different sequence types. I suggest that instead, you use something like:
var excludedNicks = new HashSet<string>(list2.Select(x => x.Nick));
var query = list1.Where(x => !excludedNicks.Contains(x.Name));
(Note that this won't perform the "distinct" aspect of Except. If you need that, please say so and we can work out what you need.)
Well, build a set of all the nicknames, then run against that.
var nicknames = new HashSet<string>(list2.Select(l2 => l2.Nick));
var newNames = from l1 in list1
where !nicknames.Contains(l1.Name)
select l1;

ArrayList C# Contains method query

I have an ObservableCollection<myClass> list. It contains a 10 objects of type MyClass.
class MyClass
{
string name;
int age;
}
If I want to find all items in list where age = 10, can I use the Contains method?
If yes how can I do this without using iteration?
var age10 = list.Where(i => i.age == 10);
Lots more queries here: http://msdn.microsoft.com/en-us/vcsharp/aa336746.aspx
No, Contains only looks for a specific value, not something matching a predicate. It also only finds one value rather than every matching value.
You can, however, use Where from LINQ to Objects, assuming you're on .NET 3.5 or higher:
foreach (var item in list.Where(x => x.Age == 10))
{
// Do something with item
}
Since ObservableCollection<T> implements Collection<T> which implements IEnumerable<T>...you can use the LINQ to Object extension methods to make this simple (even though it will use iteration in the background):
var results = list.Where(m => m.age == 10);
As others have stated, using .Where(i => i.Age == 10) would be the correct way to get the result stated in the question. You would use .Contains() to check your collection for a specific instance of your class.
You can use linq to do this but not Contains
var foo = from bar in myCollection where bar.age == 10 select bar;

Categories

Resources