Linq GroupBy behaves differently for classes and values - c#

Please consider the following code segment:
var list = new string[] { "ab", "ab", "cd", "cd", "cd" };
var groups = list.GroupBy(l => l);
var count = groups.Count();
The results:
count: 2,
groups: [{ Key: "ab", elements: ["ab", "ab"] }, { Key: "cd", elements: ["cd", "cd", "cd"] }]
When I do the same for class X:
public class X
{
public int A { get; set; }
public string B { get; set; }
}
And the same algorithm is used in order to create the grouped results:
var list2 = new X[]
{
new X { A = 1, B = "b1" },
new X { A = 1, B = "b1" },
new X { A = 2, B = "b2" },
new X { A = 2, B = "b2" },
new X { A = 2, B = "b2" },
};
var groups2 = list2.GroupBy(l => l);
var count2 = groups2.Count();
I would expect the same behavior. I would say count2 is 2, and groups2 contains the two different distinct data sets with 2 and 3 elements respectively.
However when I run this, I get 5 as count and a list of groups containing one item each. Why is the different behavior? I would expect the same aggregation algorithm to behave the same.
Thanks in advance for the explanation.

GroupBy uses default equality comparer for the type unless you provide any implementation.The default comparer for reference types only return true if they are same instances, meaning they have same references. If this is not the behaviour you want you have two choices:
Override Equals and GetHashCode methods in your clas
Implement an IEqualityComparer for your type and pass it to GroupBy

Related

Order items by custom order

I have a list which I inserted on a database:
List<House> houses = new List<House> {
new House { Id = 1, Type = "A" },
new House { Id = 2, Type = "B" },
new House { Id = 3, Type = "C" }
new House { Id = 4, Type = "B" }
}
Using Linq to Entities I need to get Houses ordered by Type but it should be:
Houses of Type C
Houses of Type A
Houses of Type B
How to do this?
You can chain the ? : opeartor to create a custom sort like this:
var query = from h in context.Houses
orderby h.Type == "C" ? 0 : (h.Type == "A" ? 1 : 2)
select h;
Or method syntax
var query = context.Houses.OrderBy(h => h.Type == "C" ? 0 : (h.Type == "A" ? 1 : 2))
Sorry for late answer, but I would write an extension similar to this:
static void Main(string[] args)
{
var items = new[] { 1, 2, 3, 4, 5 }.AsQueryable();
//for example, revert entire list
var newOrder = new Dictionary<int, int>() { { 1, 5 }, { 2, 4 }, { 3, 3 }, { 4, 2 }, { 5, 1 } };
var sorted = items.OrderBy(newOrder.ToSwithExpression())).ToList();
foreach(var i in sorted)
{
Console.WriteLine(i);
}
Console.ReadKey();
}
static Expression<Func<T, K>> ToSwithExpression<T, K>(this Dictionary<T, K> dict, K defaultValue = default(K))
{
var paramm = Expression.Parameter(typeof(T), "x");
//If nothing maps - use default value.
Expression iter = Expression.Constant(defaultValue);
foreach (var kv in dict)
{
iter = Expression.Condition(Expression.Equal(paramm, Expression.Constant(kv.Key)), Expression.Constant(kv.Value, typeof(K)), iter);
}
return Expression.Lambda<Func<T, K>>(Expression.Convert(iter, typeof(K)), paramm);
}
As you see you can specify mapping switch instead of Dictionary. I used dictionary just because it is easier. EF will have no problem in chewing this one and transforming it into similar to other answers expression.

Group items by the items it holds

Please note: My question contains pseudo code!
In my army I have foot soldiers.
Every soldier is unique: name, strength etc...
All soldiers have inventory. It can be empty.
Inventory can contain: weapons, shields, other items.
I want to group my footsoldiers by their exact inventory.
Very simple example:
I have a collection of:
Weapons: {"AK-47", "Grenade", "Knife"}
Shields: {"Aegis"}
OtherItems: {"KevlarVest"}
Collection of footsoldiers. (Count = 6)
"Joe" : {"AK-47", "Kevlar Vest"}
"Fred" : {"AK-47"}
"John" : {"AK-47", "Grenade"}
"Rambo" : {"Knife"}
"Foo" : {"AK-47"}
"Bar" : {"KevlarVest"}
These are the resulting groups (count=5) : (already in specific order now)
{"AK-47"}
{"AK-47", "Grenade"}
{"AK-47", "Kevlar Vest"}
{"Knife"}
{"KevlarVest"}
I want to sort the groups by: Weapons, then by shields, then by other items in specific order in which they are declared within their collection.
When I open the inventorygroup {"Knife"} I will find a collection with 1 footsoldier named "Rambo".
Please note: I have made this simplified version, in order not to distract you with the complexity of the data at hand. In my business case I am working with ConditionalActionFlags, that may hold Conditions of a certain type.
Hereby I supply a TestMethod that still fails now.
Can you rewrite the GetSoldierGroupings method so that the TestSoldierGroupings method succeeds ?
public class FootSoldier
{
public string Name { get; set; }
public string[] Inventory { get; set; }
}
public class ArrayComparer<T> : IEqualityComparer<T[]>
{
public bool Equals(T[] x, T[] y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(T[] obj)
{
return obj.Aggregate(string.Empty, (s, i) => s + i.GetHashCode(), s => s.GetHashCode());
}
}
[TestMethod]
public void TestSoldierGroupings()
{
//Arrange
var weapons = new[] { "AK-47", "Grenade", "Knife" };
var shields = new[] { "Aegis" };
var otherItems = new[] { "KevlarVest" };
var footSoldiers = new FootSoldier[]
{
new FootSoldier() { Name="Joe" , Inventory= new string[]{ "AK-47", "Kevlar Vest" } },
new FootSoldier() { Name="Fred" , Inventory= new string[]{ "AK-47" } },
new FootSoldier() { Name="John" , Inventory= new string[]{ "AK-47", "Grenade" } },
new FootSoldier() { Name="Rambo" , Inventory= new string[]{ "Knife" } },
new FootSoldier() { Name="Foo" , Inventory= new string[]{ "AK-47" } },
new FootSoldier() { Name="Bar" , Inventory= new string[]{ "Kevlar Vest" } }
};
//Act
var result = GetSoldierGroupings(footSoldiers, weapons, shields, otherItems);
//Assert
Assert.AreEqual(result.Count, 5);
Assert.AreEqual(result.First().Key, new[] { "AK-47" });
Assert.AreEqual(result.First().Value.Count(), 2);
Assert.AreEqual(result.Last().Key, new[] { "Kevlar Vest" });
Assert.AreEqual(result[new[] { "Knife" }].First().Name, "Rambo");
}
public Dictionary<string[], FootSoldier[]> GetSoldierGroupings(FootSoldier[] footSoldiers,
string[] weapons,
string[] shields,
string[] otherItems)
{
//var result = new Dictionary<string[], FootSoldier[]>();
var result = footSoldiers
.GroupBy(fs => fs.Inventory, new ArrayComparer<string>())
.ToDictionary(x => x.Key, x => x.ToArray());
//TODO: the actual sorting.
return result;
}
You need to group your soldiers by a key of combined items. It can be done using custom comparers.
As for me, I would make it simpler by using String.Join with separator which cannot be met in any weapon, shield etc.
Assuming that a soldiers has a property Items which is an array of strings (like ["AK-47", "Kevlar Vest"]), you can do something like this:
var groups = soldiers
.GroupBy(s => String.Join("~~~", s.Items))
.ToDictionary(g => g.First().Items, g => g.ToArray());
It will result into a Dictionary where key is unique item set, and value is an array of all soldiers having such set.
You may change this code such that it returns IGrouping, array of classes \ structs, Dictionary, whatever else convenient for you.
I would go for a Dictionary or an array of something like SoldiersItemGroup[] with items and soldiers as properties.
Make sure to change such join separator that no weapon can theoretically contain it.

Sorting a List of Lists by the amount of intergers of the same type they contain

Hey everyone I have a list of lists which I am trying to sort based on how many of the same type of int it contains. Each item in the list of list contains 4 ints, for ex:
[0,1,2,3]
[0,1,1,1]
[0,2,2,2] etc. Im trying to sort the lists based on which contains the most of a specific int. So if I wanted to sort these three the [0,1,1,1] list would be on top since it contains three 1's etc. I was told to use linq to do this, but didnt know how to use linq.orderby with a method. Can someone help me if there is the correct approach? Thanks!
public class EnemyAI : MonoBehaviour
{
List<List<int>> listOfPaths = new List<List<int>>();
public void sortListofLists ()
{
listOfPaths.OrderBy(runeList => runeList.customMethod(runeType)); //rune type is an int
}
public int customMethod(int sort)
{
int holder = 0;
for (int i = 0; i < MethodList.Count; i++)
{
if (MethodList[i] == sort)
{
holder++;
}
}
return holder;
}
Just order descending by count of particular digits:
var list = new List<List<int>>()
{
new List<int> { 0, 1, 2, 3 },
new List<int> { 0, 1, 1, 1 },
new List<int> { 0, 2, 2, 2 }
};
var result = list.OrderByDescending(c => c.Count(y => y == 1)).ToList();
Your problem can be separated into two sub-problems.
1. How to get the count of the most frequent integer in List?
You can use the following LINQ query:
int countOfMostOccurences = arr.GroupBy(x => x).Max(x => x.Count());
2. How to sort a List by a certain rule in descending order:
list = list.SortByDescending(x => rule);
Now, combine it:
List<List<int>> lists = new List<List<int>>
{
new List<int> { 1, 2, 3, 4 },
new List<int> { 1, 3, 3, 3 },
new List<int> { 1, 2, 2, 3 },
new List<int> { 1, 1, 1, 1 }
};
lists = lists.OrderByDescending(x => x.GroupBy(g => g).Max(g => g.Count())).ToList();

c# combinations with linq

I have an array of values, e.g. { 0, 1, 2 } which can be in one of two states { 0, 1 }.
Is there a simple way (perhaps using a linq query) to get a list of all combinations of { value, state } (where value is unique) so that I get results like:
{
{ { 0, 0 }, { 1, 0 }, { 2, 0 } },
{ { 0, 0 }, { 1, 0 }, { 2, 1 } },
{ { 0, 0 }, { 1, 1 }, { 2, 0 } },
{ { 0, 0 }, { 1, 1 }, { 2, 1 } },
{ { 0, 1 }, { 1, 0 }, { 2, 0 } },
{ { 0, 1 }, { 1, 0 }, { 2, 1 } },
{ { 0, 1 }, { 1, 1 }, { 2, 0 } },
{ { 0, 1 }, { 1, 1 }, { 2, 1 } },
}
The "value" array can be of varying size, but they can only ever be in one of two states.
(It's not a cartesian product exactly, and I'm not sure what term can be used to describe it, so don't know what to google).
Thanks!
It's a Cartesian product of Cartesian products:
var groups = from x in
(from v in values
from s in states
select new {v,s})
group x by x.v into gx
select gx;
var perms = from a in groups[0]
from b in groups[1]
from c in groups[2]
select new {a,b,c};
The groups query produces a Lookup (conceptually a read-only Dictionary of IEnumerables) containing the simple Cartesian product of all values and states (6 elements), grouped by their value. Then, the second query produces a Cartesian product of the elements of the Cartesian product taken three at a time, one from each group in the Lookup.
To make this work with an unknown number of dimensions would be tricky; if you don't absolutely have to make it work that way I would avoid it. I think the most elegant way would be to define a set of extension methods for the System.Tuple generic classes:
public static Tuple<T1,T2> Append(this Tuple<T1> tuple, T2 addend)
{
return Tuple.Create(tuple.Item1, addend);
}
public static Tuple<T1,T2, T3> Append(this Tuple<T1,T2> tuple, T3 addend)
{
return Tuple.Create(tuple.Item1, tuple.Item2, addend);
}
...
Then, you can take these helpers and use them in a looped version of the second query:
var perms = from a in groups[0]
select Tuple.Create(a);
foreach(var group in groups.Skip(1))
perms = from a in perms
from b in group
select a.Append(b);
This will produce an enumerable of Tuples of the required length, containing the elements of the anonymous type produced in the first query (which can be refactored to produce strongly-typed 2-item Tuples if you wish). You may have an issue with using the perms collection variable to refer to collections of ever-growing Tuples; this is the tricky part.
try
var values = new int[]{0,1,2};
var states = new int[]{0,1};
var permutations = from v in values
from s in states
select new {v,s}
you simply need a cross product of two arrays regardless of size of arrays
Maybe something like:
var lst1 = new List<int> { 0, 1, 2 };
var lst2 = new List<int> { 0, 1 };
lst1.ForEach(i => {
Console.WriteLine("{");
lst2.ForEach(j => Console.Write("{ " + i + "," + j + "}"));
Console.WriteLine("}");
});
This won't really be in the format you want; you should build some kind of array(s) and then join it on "," and/or "}, {". This should give you the general idea, though.
As the two states can be seen as bits, you can get the combinations by simply counting and converting the bits to items in an array:
int maxValue = 2;
int[][][] values = Enumerable.Range(0, 1 << maxValue).Select(n =>
Enumerable.Range(0, maxValue + 1).Select(m =>
new[] { m, (n >> (maxValue - m)) & 1 }
).ToArray()
).ToArray();

LINQ C# - Combining multiple groups

LINQ Groupby query creates a new group for each unique key. I would like to combine multiple groups into a single group based on the key value.
e.g.
var customersData = new[]
{
new { id = 1, company = "ABC" },
new { id = 2, company = "AAA" },
new { id = 3, company = "ABCD" },
new { id = 4, company = "XYZ" },
new { id = 5, company = "X.Y.Z." },
new { id = 6, company = "QQQ" },
};
var groups = from d in customersData
group d by d.company;
Let's say I want ABC, AAA, and ABCD in the same group, and XYZ, X.Y.Z. in the same group.
Is there anyway to achieve this through LINQ queries?
You will need to use the overload of GroupBy that takes an IEqualityComparer.
var groups = customersData.GroupBy(k => k.company, new KeyComparer());
where KeyComparer could look like
public class KeyComparer : IEqualityComparer
{
public bool Equals(string x, string y)
{
// put your comparison logic here
}
public int GetHashCode(string obj)
{
// same comparison logic here
}
}
You can comparer the strings any way you like in the Equals method of KeyComparer.
EDIT:
You also need to make sure that the implementation of GetHashCode obeys the same rules as the Equals method. For example if you just removed the "." and replaced with "" as in other answers you need to do it in both methods like this
public class KeyComparer : IEqualityComparer
{
public bool Equals(string x, string y)
{
return x.Replace(".", "") == y.Replace(".", "");
}
public int GetHashCode(string obj)
{
return obj.Replace(".", "").GetHashCode();
}
}
I am assuming the following:
You meant to have quotes surrounding the company "names" (as below).
Your problem is simply solved by removing the '.'s from each company name.
If these assumptions are correct, the solution is simply the following:
var customersData = new[] {
new { id = 1, company = "ABC" },
new { id = 2, company = "A.B.C." },
new { id = 3, company = "A.B.C." },
new { id = 4, company = "XYZ" },
new { id = 5, company = "X.Y.Z." },
new { id = 6, company = "QQQ" },
};
var groups = from d in customersData
group d by d.company.Replace(".", "");
If these assumptions are not correct, please clarify and I can help work closer to a solution.
var groups = from d in customersData
group d by d.company.Replace(".", "");
public void int GetId(Company c)
{
int result = //whatever you want
return result;
}
then later:
var groups = from d in customersData
group d by GetId(d.company);
I think this is what you want:
var customersData = new[]
{
new { id = 1, company = "ABC" },
new { id = 2, company = "AAA" },
new { id = 3, company = "ABCD" },
new { id = 4, company = "XYZ" },
new { id = 5, company = "X.Y.Z." },
new { id = 6, company = "QQQ" },
};
var groups = from d in customersData
group d by d.company[0];
foreach (var group in groups)
{
Console.WriteLine("Group " + group.Key);
foreach (var item in group)
{
Console.WriteLine("Item " + item.company);
}
}
Console.ReadLine();

Categories