Here is the pseudo case:
class parent{
string name; //Some Property
List<int> myValues;
.......
}
........
//Initialize some parent classes
List<parent> parentList = new List<parent>();
parentList.add(parent123); //parent123.myValues == {1,2,3}
parentList.add(parent456); //parent456.myValues == {4,5,6}
parentList.add(parentMatch); //parentMatch.myValues == {1,2,3}
What I am aiming for is a query which retrieves a List of parent objects where their
myValues Lists are equivalent. In this case it would return parent123 and parentMatch.
So you can wrap the logic up and just use GroupBy if you implement an IEqualityComparer:
class IntegerListComparer : IEqualityComparer<List<int>>
{
#region IEqualityComparer<List<int>> Members
public bool Equals(List<int> x, List<int> y)
{
//bool xContainsY = y.All(i => x.Contains(i));
//bool yContainsX = x.All(i => y.Contains(i));
//return xContainsY && yContainsX;
return x.SequenceEqual(y);
}
public int GetHashCode(List<int> obj)
{
return 0;
}
#endregion
}
Call it like so:
var results = list
.GroupBy(p => p.MyValues, new IntegerListComparer())
.Where(g => g.Count() > 1)
.SelectMany(g => g);
Very silly solution:
var groups = list.GroupBy(p => string.Join(",", p.list.Select(i => i.ToString()).ToArray()))
.Where(x => x.Count() > 1).ToList();
Result:
an IEnumerable of groups containing parent objects having list with same int (in the same order).
If you need to match list of elements in any order (i.e. 1,2,3 == 3,1,2), just change p.list to p.list.OrderBy(x => x).
Plus, if you're targeting framework 4.0, you can avoid ToArray and ToString
EDIT:
added a where to filter single-occurrence groups.
Now if you have these parents:
parent A 1,2,3
parent B 1,2,3
parent C 1,2,3
parent D 4,5,6
parent E 4,5,6
parent F 7,8,9
it returns:
(A,B,C) - (D,E)
Try this:
var matches = (from p1 in parentList
from p2 in parentList
let c1 = p1.myValues
let c2 = p2.myValues
where p1 != p2 &&
c1.All(child => c2.Contains(child)) &&
c2.All(child => c1.Contains(child))
select p1).Distinct();
Related
I have 2 IEnumerable collections of objects in memory.
Collection1 is a list containing:
Id, Name, Category etc.
Collection2 is a list containing
Id, SortOrder
Id in each collection will have corresponding values.
SortOrder is an int.
I need to sort Collection1 by the value of SortOrder from Collection2
Anyone know how to do this?
You probably simply want to join them and order by the right field
from obj1 in collection1
join obj2 in collection2 on obj1.Id equals obj2.Id
orderby obj2.SortOrder
select obj1
Or, If like me you prefer Lamda syntax
var result = collection1.Join(collection2,
a => a.Id,
b => b.Id,
(a, b) => new {Obj1 = a,Obj2 = b})
.OrderBy(x => x.Obj2.SortOrder)
.Select(x => x.Obj1);
The way to do this without joining is to make the Id/Sortorder collection into a dictionary and lookup the sortorder:
var sortOrderDict = collection2.ToDictionary(k => k.Id, v => v.SortOrder);
var result = coll1.OrderBy(x => sortOrderDict[x.Id]);
static void Main(string[] args)
{
var coll1 = new List<(int Id, string Name, string Category)>
{
(1,"A","AA"),
(2,"B","BB"),
(3,"C","CC"),
(4,"D","DD"),
(5,"E","EE")
};
var coll2 = new List<(int Id, int Order)>
{
(5,1),
(4,2),
(3,3),
(2,4),
(1,5),
(0,6)
};
var sorted = coll1.OrderBy(x => coll2.FirstOrDefault(y => x.Id == y.Id).Order);
foreach (var item in sorted)
Console.WriteLine(item);
}
Result
(5, E, EE)
(4, D, DD)
(3, C, CC)
(2, B, BB)
(1, A, AA)
Press any key to continue . . .
And this elegant way you can even shuffle:
var sorted = coll1.OrderBy(_ => Guid.NewGuid());
// here sortedlist1 will be sorted
var sortedlist1 = list1.OrderBy(x=>x.id = GetOrder(x.id));
public int Getorder(int id)
{
return list2.Where(x=> x.id ==id).FirstOrDefault().SortedOrder;
}
it works for me..
I am trying to find the number of items in a list that differ in a property which itself is a list. I found this example using Linq here:
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.First())
.ToList();
This works nicely if the property PersonId is scalar. But in my case this does not work and in the following the items in SelectedTargets are always returned as distinct even though the ListOfActions is equal in all items:
List<Target> distinctTargets = SelectedTargets.GroupBy(p => p.ListOfActions).Select(g => g.First()).ToList();
If instead I pick the first item in ListOfActions it works:
List<Target> distinctTargets = SelectedTargets.GroupBy(p => p.ListOfActions[0]).Select(g => g.First()).ToList();
So how can I check for equality of the whole list ListOfActions? (it doesn't necessarily have to user Linq)
The definition of SelectedTargets is:
List<Target> SelectedTargets = new List<Target>();
and is DispensingActionList:
private DispensingActionList ListOfActions = new DispensingActionList();
public class DispensingActionList : List<DispensingAction>
{ ...
You could use a custom IEqualityComparer<T> for the GroupBy overload which compares sequences. For example this which uses Enumerable.SequenceEqual:
public class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>>
{
public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
var comparer = EqualityComparer<T>.Default;
return x.SequenceEqual(y, comparer);
}
public int GetHashCode(IEnumerable<T> items)
{
unchecked
{
int hash = 17;
foreach (T item in items)
{
hash = hash * 23 + (item == null ? 0 : item.GetHashCode());
}
return hash;
}
}
}
Now this should work:
List<Target> distinctTargets = SelectedTargets
.GroupBy(p => p.ListOfActions, new SequenceComparer<DispensingAction>())
.Select(g => g.First())
.ToList();
Of course DispensingAction also needs to override Equals to compare the objects meaningfully and not only checks whether they're the same reference or not.
You could use Enumerable.SequenceEqual<TSource>
You will have to override the GetHashCode and Equals methods for your types if you didn't do so yet.
I have two lists
List A List B
ID FirstName DepartmentID ID FirstName DepartmentID
5 Peter Null 5 Peter 1
9 Steve Null 9 Steve 2
16 Mark Null 16 Mark 3
I want to compare these lists using LINQ and if IDs from both lists are equal, I want to set DepartmentID in the first list, A list.
Thank you in advance.
ListA.ForEach(a=>a.DepartmentID = ListB.Any(b=>b.ID == a.ID) ? ListB.First(b=>b.ID == a.ID).DepartmentID : null);
Edit: if you want to check for null.
You could join them. Then enumerate the results and save the changes:
var results = from a in ListA
join b in ListB on ai.ID equals bi.ID
select new
{
itemA = a,
itemB = b
};
foreach(var result in results)
{
// This was not listed as a requirement, but it may be a valid check
if (itemA.FirstName == itemB.FirstName)
{
itemB.DepartmentID = itemA.DepartmentID;
}
}
DataContext.SubmitChanges();
ListA.Join(ListB, x => x.ID, y => y.ID, (x, y) =>
{
x.DepartmentID = y.DepartmentID;
return 0;
}).ToArray();
What you can do use another list and use following code
var temp = from ai in ListA
join bi in ListB on ai.id equals bi.id
select bi;
var ListC = temp.ToList();
var replacements = ListA
.Where(a => a.DepartmentID == null)
.Join(ListB,
a => a.ID,
b => b.ID,
(a, b) => new { Source = a, Replacement = b }
);
foreach (var item in replacements)
{
// here you could perform more replacements
item.Source.DepartmentID = item.Replacement.DepartmentID;
}
I have a class that has a sub collection. Something like this:
public class Item {
public string Type {get;set}
public Subitem[] {get;set;}
}
I know that I can separate and count by Type this way:
var count = (from x in items
group x by x.Type into grouped
select new {
typename = grouped.Key,
count = grouped.Count()
}).ToDictionary<string, int>(x => x.typename, x => x.count);
This will return something like this:
{ type1, 13 }
{ type2, 26 }
and so on.
But how can I count by Subitem?
To return something like:
{ subitem1, 15 }
{ subitem2, 46 }
Your code sample is not legal C#, but suppose you have a collection called Subitems in your items - then you can use SelectMany() or in query syntax:
var count = (from i in items
from x in i.Subitems
group x by x into grouped
select new
{
typename = grouped.Key,
count = grouped.Count()
}).ToDictionary(x => x.typename, x => x.count);
Or alternatively in method syntax:
var countDict = items.SelectMany(x => x.Subitem)
.GroupBy(x => x)
.ToDictionary(g => g.Key, g => g.Count());
I have a troublesome query to write. I'm currently writing some nasty for loops to solve it, but I'm curious to know if Linq can do it for me.
I have:
struct TheStruct
{
public DateTime date {get; set;} //(time portion will always be 12 am)
public decimal A {get; set;}
public decimal B {get; set;}
}
and a list that contains these structs. Let's say it's ordered this way:
List<TheStruct> orderedList = unorderedList.OrderBy(x => x.date).ToList();
If you put the orderedList struct dates in a set they will always be contiguous with respect to the day.. that is if the latest date in the list was 2011/01/31, and the earliest date in the list was 2011/01/01, then you'd find that the list would contain 31 items, one for each date in January.
Ok, so what I want to do is group the list items such that:
Each item in a group must contain the same Decimal A value and the same Decimal B value
The date values in a group must form a set of contiguous dates, if the date values were in order
If you summed up the sums of items in each group, the total would equal the number of items in the original list (or you could say a struct with a particular date can't belong to more than one group)
Any Linq masters know how to do this one?
Thanks!
You can group adjacent items in a sequence using the GroupAdjacent Extension Method (see below):
var result = unorderedList
.OrderBy(x => x.date)
.GroupAdjacent((g, x) => x.A == g.Last().A &&
x.B == g.Last().B &&
x.date == g.Last().date.AddDays(1))
.ToList();
Example:
(1,1) 2011-01-01 \
(1,1) 2011-01-02 > Group 1
(1,1) 2011-01-03 __/
(2,1) 2011-01-04 \
(2,1) 2011-01-05 > Group 2
(2,1) 2011-01-06 __/
(1,1) 2011-01-07 \
(1,1) 2011-01-08 > Group 3
(1,1) 2011-01-09 __/
(1,1) 2011-02-01 \
(1,1) 2011-02-02 > Group 4
(1,1) 2011-02-03 __/
Extension Method:
static IEnumerable<IEnumerable<T>> GroupAdjacent<T>(
this IEnumerable<T> source, Func<IEnumerable<T>, T, bool> adjacent)
{
var g = new List<T>();
foreach (var x in source)
{
if (g.Count != 0 && !adjacent(g, x))
{
yield return g;
g = new List<T>();
}
g.Add(x);
}
yield return g;
}
Here is an entry for "Most Convoluted way to do this":
public static class StructOrganizer
{
public static IEnumerable<Tuple<Decimal, Decimal, IEnumerable<MyStruct>>> OrganizeWithoutGaps(this IEnumerable<MyStruct> someStructs)
{
var someStructsAsList = someStructs.ToList();
var lastValuesSeen = new Tuple<Decimal, Decimal>(someStructsAsList[0].A, someStructsAsList[0].B);
var currentList = new List<MyStruct>();
return Enumerable
.Range(0, someStructsAsList.Count)
.ToList()
.Select(i =>
{
var current = someStructsAsList[i];
if (lastValuesSeen.Equals(new Tuple<Decimal, Decimal>(current.A, current.B)))
currentList.Add(current);
else
{
lastValuesSeen = new Tuple<decimal, decimal>(current.A, current.B);
var oldList = currentList;
currentList = new List<MyStruct>(new [] { current });
return new Tuple<decimal, decimal, IEnumerable<MyStruct>>(lastValuesSeen.Item1, lastValuesSeen.Item2, oldList);
}
return null;
})
.Where(i => i != null);
}
// To Test:
public static void Test()
{
var r = new Random();
var sampleData = Enumerable.Range(1, 31).Select(i => new MyStruct {A = r.Next(0, 2), B = r.Next(0, 2), date = new DateTime(2011, 12, i)}).OrderBy(s => s.date).ToList();
var sortedData = sampleData.OrganizeWithoutGaps();
Console.Out.WriteLine("Sample Data:");
sampleData.ForEach(s => Console.Out.WriteLine("{0} = ({1}, {2})", s.date, s.A, s.B));
Console.Out.WriteLine("Output:");
sortedData.ToList().ForEach(s => Console.Out.WriteLine("({0}, {1}) = {2}", s.Item1, s.Item2, String.Join(", ", s.Item3.Select(st => st.date))));
}
}
If I understood you well, a simple Group By would do the trick:
var orderedList = unorderedList.OrderBy(o => o.date).GroupBy(s => new {s.A, s.B});
Just that. To print the results:
foreach (var o in orderedList) {
Console.WriteLine("Dates of group {0},{1}:", o.Key.A, o.Key.B);
foreach(var s in o){
Console.WriteLine("\t{0}", s.date);
}
}
The output would be like:
Dates of group 2,3:
02/12/2011
03/12/2011
Dates of group 4,3:
03/12/2011
Dates of group 1,2:
04/12/2011
05/12/2011
06/12/2011
Hope this helps.
Cheers