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.
Related
The challenge is about converting from method chain to standard linq a piece of code full of group by.
The context
To fully understand the topic here you can read the original question (with class definitions, sample data and so on): Linq: rebuild hierarchical data from the flattened list
Thanks to #Akash Kava, I've found the solution to my problem.
Chain method formulation
var macroTabs = flattenedList
.GroupBy(x => x.IDMacroTab)
.Select((x) => new MacroTab
{
IDMacroTab = x.Key,
Tabs = x.GroupBy(t => t.IDTab)
.Select(tx => new Tab {
IDTab = tx.Key,
Slots = tx.Select(s => new Slot {
IDSlot = s.IDSlot
}).ToList()
}).ToList()
}).ToList();
But, for sake of knowledge, I've tried to convert the method chain to the standard Linq formulation but something is wrong.
What happens is similar to this..
My attempt to convert it to Linq standard syntax
var antiflatten = flattenedList
.GroupBy(x => x.IDMacroTab)
.Select(grouping => new MacroTab
{
IDMacroTab = grouping.Key,
Tabs = (from t in grouping
group grouping by t.IDTab
into group_tx
select new Tab
{
IDTab = group_tx.Key,
Slots = (from s in group_tx
from s1 in s
select new Slot
{
IDSlot = s1.IDSlot
}).ToList()
}).ToList()
});
The result in LinqPad
The classes and the sample data on NetFiddle:
https://dotnetfiddle.net/8mF1qI
This challenge helped me to understand what exactly returns a Linq Group By (and how prolix is the Linq syntax with Group By).
As LinqPad clearly shows a Group By returns a List of Groups. Group is a very simple class which has just one property: a Key
As this answer states, from definition of IGrouping (IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable) the only way to access to the content of the subgroups is to iterate through elements (a foreach, another group by, a select, ecc).
Here is shown the Linq syntax formulation of the method chain.
And here is the source code on Fiddle
But let's go on trying to see another solution:
What we usually do in SQL when we do a Group By is to list all the columns but the one which have been grouped. With Linq is different.. it still returns ALL the columns.
In this example we started with a dataset with 3 'columns' {IDMacroTab, IDTab, IDSlot}. We grouped for the first column, but Linq would return the whole dataset, unless we explicitly tell him..
Suppose I have one collection, call it ids it is of type IEnumerable<string>, I have a second collection call it objects it's of type MyObject[]. MyObject has a string property called id. I would like a LINQ statement that returns all off the objects in the objects collection who's id matches any value in the ids collection. ids will be a strict subset of objects.Select(x => x.id). Meaning, for every string in ids I know there will be exactly one corresponding MyObject in objects. Can someone post a pure LINQ solution? I've tried a couple things with no luck. I can come up with an iterative solution easily enough so unless it's impossible to do with only LINQ please don't post any.
"Just" LINQ:
var r = obj.Where(o => ids.Any(id => id == o.id));
But better, for larger n, with a set:
var hs = new HashSet(ids);
var r = obj.Where(o => hs.Contains(o.id));
I think this is pretty straightforward with query syntax.
It would look something like:
var a = from o in objects
join i in ids on o.id equals i
select o;
If you just want a list of MyObject that match, you can do :
var solution = objects.Where(x=> ids.Contains(x.id));
With this instead, you'll get a List<T> where T is an Anonymous type with 2 properties, Id that is the string that work as "key" in this specific case, and Obj, a list of MyObject which id correspond to the Id property.
var solution = ids.Select(x=>new{ Id = x, Obj=objects.Where(y=>y.id == x).ToList()})
.ToList();
If you just want to know if there is any object in the intersection (which was what I was looking for)
Based on this
var a = from o in objects
join i in ids on o.id equals i
select o;
You can do this as well
var isEmpty = objects.Any(x => ids.Any(y => y == x.ToString()));
The accepted answer is correct. However, if someone doesn't like using SQL style LINQ, here is the LINQ extension method approach to solving the same problem.
var filteredObjects = objects.Join(ids, obj => obj.Id, id => id, (obj, _) => obj);
We are joining two different types, so the 2nd & 3rd Join parameter signify that join will be made on id.
The fourth parameter is used to select an object out of the resultant (obj, id) pair after applying join.
These two rows seems to do the same thing. A plussign (+) can be used instead of an anonymous type.
var newlist1 = list.GroupBy(x => x.FIELD1 + x.FIELD2).Select(y => y.First());
var newlist2 = list.GroupBy(x => new {x.FIELD1, x.FIELD2}).Select(y => y.First());
Now my question:
Is the plussign (+) something thats documented for GroupBy?
be careful of this :)
If for example x.FIELD1 and x.FIELD2 are properties of type string, you're just grouping by the result of concatenating the two.... which is probably not what you want. Same applies for other types of course, but an example in strings still:
Given Field1 = "ABC" and Field2 = "DEF", your grouping will be with the key "ABCDEF", right?
So what if you had Field1 = "AB" and Field2 = "CDEF"? Very much different values, but your grouping would still be "ABCDEF"...
You should stick to anonymous types for grouping (when used within a method only), or when needed externally, a new class, struct, or make use of a Tuple.
EDIT: Another quick note: after you have executed the GroupBy (without the projection), take a look at the Key values you are getting.... it should show you an example of what I mean.
There are quite a few other questions similiar to this but none of them seem to do what I'm trying to do. I'd like pass in a list of string and query
SELECT ownerid where sysid in ('', '', '') -- i.e. List<string>
or like
var chiLst = new List<string>();
var parRec = Lnq.attlnks.Where(a => a.sysid IN chiList).Select(a => a.ownerid);
I've been playing around with a.sysid.Contains() but haven't been able to get anywhere.
Contains is the way forward:
var chiLst = new List<string>();
var parRec = Lnq.attlnks.Where(a => chiList.Contains(a.sysid))
.Select(a => a.ownerid);
Although you'd be better off with a HashSet<string> instead of a list, in terms of performance, given all the contains checks. (That's assuming there will be quite a few entries... for a small number of values, it won't make much difference either way, and a List<string> may even be faster.)
Note that the performance aspect is assuming you're using LINQ to Objects for this - if you're using something like LINQ to SQL, it won't matter as the Contains check won't be done in-process anyway.
You wouldn't call a.sysid.Contains; the syntax for IN (SQL) is the reverse of the syntax for Contains (LINQ)
var parRec = Lnq.attlnks.Where(a => chiList.Contains(a.sysid))
.Select(a => a.ownerid);
In addition to the Contains approach, you could join:
var parRec = from a in Lnq.attlnks
join sysid in chiLst
on a.sysid equals sysid
select a.ownerid
I'm not sure whether this will do better than Contains with a HashSet, but it will at least have similar performance. It will certainly do better than using Contains with a list.
I have a list that I want to sort using to parameters. That means it are all values and if for example I have
A 2/2
B 3/3
C 3/4
I want the sorting C B A
I tried to implement that the following way:
methods.Sort((y, x) => x.GetChangingMethodsCount().CompareTo(y.GetChangingMethodsCount()));
methods.Sort((y, x) => x.GetChangingClassesCount().CompareTo(y.GetChangingClassesCount()));
First sort the list with the second parameter and then sort it again with the first parameter. But the ordering isn0t correct. Any hints how to achieve that?
What you need to do is combine the two sort keys into a single function. If the first comparison returns 0, only then try the second one:
methods.Sort((y, x) =>
{
int sort = x.GetChangingClassesCount().CompareTo(y.GetChangingClassesCount());
if (sort == 0)
sort = x.GetChangingMethodsCount().CompareTo(y.GetChangingMethodsCount());
return sort;
});
Probably the easiest way is to use the OrderBy and ThenBy extension methods like that :
methods.OrderByDescending(x => x.GetChangingMethodCount()).
ThenByDescending(x => x.GetChangingClassesCount()).
ToList();
It's not clear (to me at least) if this is what you want based on your example, but you could give this a try:
var sortedMethods = methods.OrderByDescending(m => m.GetChangingMethodsCount()).ThenByDescending(m => m.GetChangingClassesCount());