LINQ without IEqualityComparer implementation - c#

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;

Related

C# and LINQ - arbitrary statement instead of let

Let's say I'm doing a LINQ query like this (this is LINQ to Objects, BTW):
var rows =
from t in totals
let name = Utilities.GetName(t)
orderby name
select t;
So the GetName method just calculates a display name from a Total object and is a decent use of the let keyword. But let's say I have another method, Utilities.Sum() that applies some math on a Total object and sets some properties on it. I can use let to achieve this, like so:
var rows =
from t in totals
let unused = Utilities.Sum(t)
select t;
The thing that is weird here, is that Utilities.Sum() has to return a value, even if I don't use it. Is there a way to use it inside a LINQ statement if it returns void? I obviously can't do something like this:
var rows =
from t in totals
Utilities.Sum(t)
select t;
PS - I know this is probably not good practice to call a method with side effects in a LINQ expression. Just trying to understand LINQ syntax completely.
No, there is no LINQ method that performs an Action on all of the items in the IEnumerable<T>. It was very specifically left out because the designers actively didn't want it to be in there.
Answering the question
No, but you could cheat by creating a Func which just calls the intended method and spits out a random return value, bool for example:
Func<Total, bool> dummy = (total) =>
{
Utilities.Sum(total);
return true;
};
var rows = from t in totals
let unused = dummy(t)
select t;
But this is not a good idea - it's not particularly readable.
The let statement behind the scenes
What the above query will translate to is something similar to this:
var rows = totals.Select(t => new { t, unused = dummy(t) })
.Select(x => x.t);
So another option if you want to use method-syntax instead of query-syntax, what you could do is:
var rows = totals.Select(t =>
{
Utilities.Sum(t);
return t;
});
A little better, but still abusing LINQ.
... but what you should do
But I really see no reason not to just simply loop around totals separately:
foreach (var t in totals)
Utilities.Sum(t);
You should download the "Interactive Extensions" (NuGet Ix-Main) from Microsoft's Reactive Extensions team. It has a load of useful extensions. It'll let you do this:
var rows =
from t in totals.Do(x => Utilities.Sum(x))
select t;
It's there to allow side-effects on a traversed enumerable.
Please, read my comment to the question. The simplest way to achieve such of functionality is to use query like this:
var rows = from t in totals
group t by t.name into grp
select new
{
Name = t.Key,
Sum = grp.Sum()
};
Above query returns IEnumerable object.
For further information, please see: 101 LINQ Samples

2 arrays into 1, with lambda exp distinct

I've got 2 object arrays that Im getting from the DB, some overlap, so I have to make a distinct func.
I tried to make a lambda expression , but I still got an overlapped objects.
this is my code:
ArtObject[] pinui = new ArtObject[root.Count - 1];
ArtObject[] c1= new ArtObject[root2.Count - 1];
pinui = getArticlesArray(root2, pinui);
c1= getArticlesArray(root, c1);
art = new ArtObject[c1.Count()+pinui.Count()];
pinui.CopyTo(art, 0);
c1.CopyTo(art, pinui.Count());
art = art.Distinct().OrderByDescending(a => a.dateTosort).ToArray();
I guess something wrong with my last line, art = art.Distinct().OrderByDescending(a => a.dateTosort).ToArray(); .. I wonder what and how can I get only the distinct objects..?
Distinct will use Equals and GetHashCode to determine equal values. I suspect you haven't overridden these methods to indicate how you want equality to be checked.
Also note that your last part would be simpler as:
ArtObject[] art = pinui.Union(c1).OrderByDescending(a => a.dateTosort).ToArray();
a.Union(b) is equivalent to a.Concat(b).Distinct().
An alternative to overriding GetHashCode and Equals is to specify an IEqualityComparer<Person> to either Union or Distinct.
MoreLINQ makes this easier with a DistinctBy method:
var query = collection.DistinctBy(x => x.Description);
(There's no equivalent for Union yet, but we could easliy add one.)
If you do distinct on one of fields, you can use GroupBy then get First:
pinui.Concat(c1).GroupBy(a => a.Description, (key, g) => g.First())
.OrderByDescending(a => a.dateTosort).ToArray();
Your code looks fine, although it could be shortened (no need to use CopyTo):
var art = pinui.Union(c1).OrderByDescending(a => a.dateTosort).ToArray();
Union already removes duplicates like Distinct.
The reason why the objects are not detected as duplicates is probably because they are distinct. For example, by default,
var obj1 = new ArtObject("Picasso");
var obj2 = new ArtObject("Picasso");
are two distinct objects.
You can, however, provide a custom equality comparer to Distinct or Union, the linked MSDN page contains a nice example.

How can I set properties on all items from a linq query with values from another object that is also pulled from a query?

I have a query pulling from a database:
List<myClass> items = new List<myClass>(from i in context
select new myClass
{
A = i.A,
B = "", // i doesn't know this, this comes from elsewhere
C = i.C
}
I also have another query doing a similar thing:
List<myClass2> otherItems = new List<myClass2>(from j in context
select new myClass2
{
A = j.A, // A is the intersection, there will only be 1 A here but many A's in items
B = j.B
}
In reality these classes are much larger and query data that is separated not only by database but by server as well. Is it possible to use a LINQ query to populate the property B for all items where items.A intersect? All of the built in LINQ predicates appear only to do aggregates, selections or bool expressions.
In my brain I had something like this, but this is all off:
items.Where(x => x.B = (otherItems.Where(z => z.A == x.A).Single().B));
Or am I being ridiculous with trying to make this work in LINQ and should just abandon it in favor of a for loop where the actual setting becomes trivial? Because of deadlines I will be resorting to the for loop (and it's probably going to end up being a lot more readable in the long run anyway), but is it possible to do this? Would an extension method be necessary to add a special predicate to allow this?
LINQ is designed for querying. If you're trying to set things, you should definitely use a loop (probably foreach). That doesn't mean you won't be able to use LINQ as part of that loop, but you shouldn't be trying to apply a side-effect within LINQ itself.
Query the OtherItems first. Do a ToDictionary() on the result. Then, when querying the database, do this:
var items = from i in context
select new myClass
{ A = i.A,
B = otherItems[i.A],
C = i.C
}

Select from a List<T>

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.

Convert IEnumerable<X> to List<Y>

i have, (in Linq), an IEnumerable of type Client.
Now i have to return a Generic List of type ClientVM.
ClientVM is a subset of Client (not a subtype or anything), and i can't get it to work.
Below is the code i have so far, but it doesn't work this way.
But maybe this code can give you an addition to my post to specify what i want to do:
clientVMs = clients.ToList().ConvertAll(new ClientVM( z => z.Reference, z=>z.Fullname ));
clientVMs is a generic List<ClientVM>, class ClientWM has a constructor that takes the two properties, clients is the IEnumerable<Client>
And, offtopic, the compiler messages when you're dealing with Generics aren't readible for humans, imho.
Maybe something like this?
var clientVMs = clients.Select(c => new ClientVM(c.Reference, c.Fullname))
.ToList();
You've got the wrong syntax for the delegate inside ConvertAll:
clientVMs = clients.ToList().ConvertAll(z => new ClientVM( z.Reference, z.Fullname ));
Your lambda expressions are misplaced. You probably want:
var clientVMs = clients.ToList().ConvertAll(
client => new ClientVM(client.Reference, client.Fullname));
clients.ToList().Select(new ClientVM{ z => z.Reference, z=>z.Fullname }).ToList();

Categories

Resources