I need to get a totalization by enum values. See this example:
In this source:
namespace ConsoleApplication
{
enum fruits { Orange, Grape, Papaya }
class item
{
public fruits fruit;
public string foo;
}
class Program
{
static void Main(string[] args)
{
item[] list = new item[]
{
new item() { fruit = fruits.Orange, foo = "afc" },
new item() { fruit = fruits.Orange, foo = "dsf" },
new item() { fruit = fruits.Orange, foo = "gsi" },
new item() { fruit = fruits.Orange, foo = "jskl" },
new item() { fruit = fruits.Grape, foo = "mno" },
new item() { fruit = fruits.Grape, foo = "pqu" },
new item() { fruit = fruits.Grape, foo = "tvs" },
};
var vTotals = from... //Here
}
}
}
I would like to vTotals be
of type
Dictionary<fruits, int>
with the values
{
{ fruits.Orange, 4 }
{ fruits.Grape, 3 }
{ fruits.Papaya, 0 }
}
How I can do it with Linq?
Thanks in advance
What you want to do here is logically a group join. You want to join this table with a sequence representing each of the fruits, and then count the size of those groups.
var totals = Enum.GetValues(typeof(fruits)).OfType<fruits>()
.GroupJoin(list,
fruit => fruit,
item => item.fruit,
(fruit, group) => new { Key = fruit, Value = group.Count() })
.ToDictionary(pair => pair.Key, pair => pair.Value);
You can use group join of all fruit types with items:
var vTotals = from fruits f in Enum.GetValues(typeof(fruits))
join i in list on f equals i.fruit into g
select new {
Fruit = f,
Count = g.Count()
};
Result:
[
{ Fruit: "Orange", Count: 4 },
{ Fruit: "Grape", Count: 3 },
{ Fruit: "Papaya", Count: 0 }
]
Here's one way to do it. It may not be considered as pretty as doing it all in one query, but it is (IMO) clear: (first part of the code thanks to D Stanley, who since deleted his answer)
var vTotals = list.GroupBy(i => i.fruit)
.ToDictionary(g => g.Key, g => g.Count());
foreach (var fruit in Enum.GetValues(typeof(fruits)).Cast<fruits>()
.Where(x => !vTotals.ContainsKey(x)))
{
vTotals.Add(fruit, 0);
}
var total = from e in Enum.GetValues(typeof(fruits)).OfType<fruits>()
select new
{
Fruit = e,
Count = list.Where(f => f.fruit == e).Count()
};
From #Servy's answer, if you want to unit test it, using MSTest.
[TestClass]
public class DummyTests {
[TestMethod]
public void GroupCountByFruitType() {
// arrange
var expected = new Dictionary<Fruits, int>() {
{ Fruits.Grape, 3 }
, { Fruits.Orange, 4 }
, { Fruits.Papaya, 0 }
};
Item[] list = new Item[] {
new Item() { Fruit = Fruits.Orange, Foo = "afc" },
new Item() { Fruit = Fruits.Orange, Foo = "dsf" },
new Item() { Fruit = Fruits.Orange, Foo = "gsi" },
new Item() { Fruit = Fruits.Orange, Foo = "jskl" },
new Item() { Fruit = Fruits.Grape, Foo = "mno" },
new Item() { Fruit = Fruits.Grape, Foo = "pqu" },
new Item() { Fruit = Fruits.Grape, Foo = "tvs" }
};
// act
var actual = Enum.GetValues(typeof(Fruits)).OfType<Fruits>()
.GroupJoin(list
, fruit => fruit
, item => item.Fruit
, (fruit, group) => new { Key = fruit, Value = group.Count() })
.ToDictionary(pair => pair.Key, pair => pair.Value);
// assert
actual.ToList()
.ForEach(item => Assert.AreEqual(expected[item.Key], item.Value));
}
private class Item {
public Fruits Fruit { get; set; }
public string Foo { get; set; }
}
private enum Fruits {
Grape,
Orange,
Papaya
}
}
This should do it
var vTotals = list.GroupBy(item => item.fruit)
.Select(item => Tuple.Create(item.Key, item.Count()))
.ToDictionary(key => key.Item1, value => value.Item2);
Here we simply group on the fruit name with their count and later turn it into a dictionary
Related
I have a string search query which I have from the frontend app but I have a problem with the query.
I have a list of objects which have Id (number = int).
If the user will write in the search box number 12(string) he should have all lists of objects which contains the number 12 in id.
Objects (1,8,80,90,180);
Another example is if the user will input the number 8. He should have output 8,80,180;
How to write linq for questions about such a thing?
Any example remember search query is a string and id is a number :(
using System;
using System.Linq;
public class Program
{
public class MyObject
{
public int Id { get; set; }
}
public static void Main()
{
var arr = new MyObject[]
{
new MyObject() { Id = 1 },
new MyObject() { Id = 8 },
new MyObject() { Id = 80 },
new MyObject() { Id = 90 },
new MyObject() { Id = 180 }
};
var searchQuery = "8";
var result = arr.Where(x => x.Id.ToString()
.Contains(searchQuery))
.Select(x => x.Id)
.ToList();
Console.WriteLine(String.Join(",", result));
}
}
https://dotnetfiddle.net/AiIdg2
Sounds like you want something like this
var input = "8";
var integers = new[] { 8, 80, 810, 70 };
var result = integers.Where(x => x.ToString().Contains(input));
Something like this could be enough:
using System.Globalization;
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
var items = new[]
{
new Item { Id = 8 },
new Item { Id = 18 },
new Item { Id = 80 },
new Item { Id = 6 },
new Item { Id = 13 },
};
var itemsWithSearchString = items
.Select(x => new { Item = x, SearchString = x.Id.ToString(CultureInfo.InvariantCulture) })
.ToArray();
const string userInput = "8";
var matchingItems = itemsWithSearchString
.Where(x => x.SearchString.Contains(userInput, StringComparison.Ordinal))
.Select(x => x.Item)
.ToArray();
foreach (var item in matchingItems)
{
Console.WriteLine($"Matching item: {item}");
}
}
}
public class Item
{
public int Id { get; set; }
public override string ToString()
{
return $"Id {this.Id}";
}
}
}
Is it possible to group collection by inner property value? how would I do this without creating a new object using only Linq. Or I need to create anonymous object before grouping process.
public class Item
{
public int Id { get; set; }
public List<ItemInv> Inventory {get; set;}
}
public class ItemInv
{
public int wid {get; set;}
}
var lst = new List<Item> {
new Item {
Id=1,
Inventory= new List<ItemInv> { new ItemInv() { wid = 2 } }
},
new Item {
Id=2,
Inventory= new List<ItemInv> { new ItemInv() { wid = 2 }}
}
};
I need group this lst variable by wid property.
I think I have almost the same solution as Gilad Green, but in method syntax.
var itemsByWid = lst
.SelectMany(item => item.Inventory.Select(itemInv => Tuple.Create(itemInv, item)))
.GroupBy(itemTuple => itemTuple.Item1.wid, itemTuple => itemTuple.Item2);
To group the Items by the ItemInvs with in their collections, first flatten the nested collections and then group by the ItemInvs:
var result = (from item in list
from inv in item.Inventory
group item by inv.wid into invGrouping
select new {
ItemInv = invGrouping.Key,
Items = invGrouping.ToList()
}).ToList();
For testing:
// Testing data
var list = new List<Item> {
new Item {
Id=1,
Inventory= new List<ItemInv> { new ItemInv() { wid = 2 } }
},
new Item {
Id=2,
Inventory= new List<ItemInv> {
new ItemInv() { wid = 2 },
new ItemInv() { wid = 3 } }
},
new Item {
Id=3,
Inventory= new List<ItemInv> {
new ItemInv() { wid = 3 } }
} };
//Output:
// wid 2 - Item id=1, Item id = 2
// wid 3 - Item id=2, Item id = 3
See that currently the grouping is by the inv.wid. If you desire grouping by the ItemInv instance you must first override the Equals and GetHashCode.
I have a list in this table
public class Fruits
{
public int ID { get; set; }
public string Name{ get; set; }
}
I want to know what are the most frequent fruit in this table what is the code that appears to me this result
I am use
var max = db.Fruits.Max();
There is an error in that?
Try
public class Fruits
{
public int ID { get; set; }
public string Name{ get; set; }
}
var Val = fruitList.GroupBy(x => x.ID,
(key, y) => y.MaxBy(x => x.ID).value)
As Drew said in the comments, you want to GroupBy on the value that you care about (I did Name, since ID tends to be unique in most data structures) and then OrderByDescending based on the count.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var fruits = new List<Fruit> { new Fruit { ID = 1, Name = "Apple" }, new Fruit { ID = 2, Name = "Apple" }, new Fruit { ID = 3, Name = "Pear" } };
var most = fruits.GroupBy(f => f.Name).OrderByDescending(group => group.Count());
Console.WriteLine(most.First().Key);
}
}
public class Fruit
{
public int ID { get; set; }
public string Name{ get; set; }
}
If you want to get the name of the item that exists most in your list, first find the id that is most occurring:
var fruitAnon = fruits
.GroupBy(item => item.ID)
.Select(item => new {
Key = item.Key,
Count = item.Count()
})
.OrderByDescending(item => item.Count)
.FirstOrDefault();
This will return an anonymous object that will have the most frequent id, and the count represents the number of times it exists in the list. You can then find that object's name:
var fruit = fruits.FirstOrDefault(x => x.ID == fruitAnon.Key);
If you had a list like this:
List<Fruits> fruits = new List<Fruits>() {
new Fruits { ID = 1, Name = "Apple" },
new Fruits { ID = 1, Name = "Apple" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" }
};
Then:
Console.WriteLine(fruit.Name);
Would print Orange.
I have a number of objects and each object has an array, I would like to group these objects by the values inside the array, so conceptually they look as follows:
var objects = new []{
object1 = new object{
elements = []{1,2,3}
},
object2 = new object{
elements = []{1,2}
},
object3 = new object{
elements = []{1,2}
},
object4 = new object{
elements = null
}
}
after grouping:
group1: object1
group2: object2,object3
group3: object4
somethings that I have tried:
actual classes:
public class RuleCms
{
public IList<int> ParkingEntitlementTypeIds { get; set; }
}
var rules = new List<RuleCms>()
{
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1,2}
},
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1,2}
},
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1}
},
new RuleCms()
{
ParkingEntitlementTypeIds = null
}
};
var firstTry = rules.GroupBy(g => new { entitlementIds = g.ParkingEntitlementTypeIds, rules = g })
.Where(x => x.Key.entitlementIds !=null && x.Key.entitlementIds.Equals(x.Key.rules.ParkingEntitlementTypeIds));
var secondTry =
rules.GroupBy(g => new { entitlementIds = g.ParkingEntitlementTypeIds ?? new List<int>(), rules = g })
.GroupBy(x => !x.Key.entitlementIds.Except(x.Key.rules.ParkingEntitlementTypeIds ?? new List<int>()).Any());
You can use IEqualityComparer class. Here is the code:
class MyClass
{
public string Name { get; set; }
public int[] Array { get; set; }
}
class ArrayComparer : IEqualityComparer<int[]>
{
public bool Equals(int[] x, int[] y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(int[] obj)
{
return string.Join(",", obj).GetHashCode();
}
}
Then
var temp = new MyClass[]
{
new MyClass { Name = "object1", Array = new int[] { 1, 2, 3 } },
new MyClass { Name = "object2", Array = new int[] { 1, 2 } },
new MyClass { Name = "object3", Array = new int[] { 1, 2 } },
new MyClass { Name = "object4", Array =null }
};
var result = temp.GroupBy(i => i.Array, new ArrayComparer()).ToList();
//Now you have 3 groups
For simple data that really is as simple as your example you could do this:
.GroupBy(x => string.Join("|", x.IDS))
.Select(x => new
{
IDS = x.Key.Split('|').Where(s => s != string.Empty).ToArray(),
Count = x.Count()
});
Have a collection of objects. Schematically:
[
{ A = 1, B = 1 }
{ A = 1, B = 2 }
{ A = 2, B = 3 }
{ A = 2, B = 4 }
{ A = 1, B = 5 }
{ A = 3, B = 6 }
]
Need:
[
{ A = 1, Bs = [ 1, 2 ] }
{ A = 2, Bs = [ 3, 4 ] }
{ A = 1, Bs = [ 5 ] }
{ A = 3, Bs = [ 6 ] }
]
Is it possible to LINQ such?
Note: Ordering is important. So Bs = [5] can't be merged with Bs = [1, 2]
Given these simplistic classes:
class C {
public int A;
public int B;
}
class R {
public int A;
public List<int> Bs = new List<int>();
}
You can do it like this:
var cs = new C[] {
new C() { A = 1, B = 1 },
new C() { A = 1, B = 2 },
new C() { A = 2, B = 3 },
new C() { A = 2, B = 4 },
new C() { A = 1, B = 5 },
new C() { A = 3, B = 6 }
};
var rs = cs.
OrderBy(o => o.B).
ThenBy(o => o.A).
Aggregate(new List<R>(), (l, o) => {
if (l.Count > 0 && l.Last().A == o.A) {
l.Last().Bs.Add(o.B);
}
else {
l.Add(new R { A = o.A, Bs = { o.B } });
}
return l;
});
Note: In the above I assume that the Bs and then the As have to be sorted. If that's not the case, it's a simple matter of removing the sorting instructions:
var rs = cs.
Aggregate(new List<R>(), (l, o) => {
if (l.Count > 0 && l.Last().A == o.A) {
l.Last().Bs.Add(o.B);
}
else {
l.Add(new R { A = o.A, Bs = { o.B } });
}
return l;
});
So basically you want to group together what has the same A-value and is consecutive.
You need to tranform the list of objects to an anonymous type which contains the previous/next element. I've used two Selects to make it more redable. Then you need to check if the two elements are consecutive(adjacent indices).
Now you have all you need to GroupBy, the value and the bool.
Your objects:
var list = new System.Collections.Generic.List<Foo>(){
new Foo(){ A = 1, B = 1 },
new Foo(){ A = 1, B = 2 },
new Foo(){ A = 2, B = 3 },
new Foo(){ A = 2, B = 4 },
new Foo(){ A = 1, B = 5 },
new Foo(){ A = 3, B = 6 }
};
The query:
var groups = list
.Select((f, i) => new
{
Obj = f,
Next = list.ElementAtOrDefault(i + 1),
Prev = list.ElementAtOrDefault(i - 1)
})
.Select(x => new
{
A = x.Obj.A,
x.Obj,
Consecutive = (x.Next != null && x.Next.A == x.Obj.A)
|| (x.Prev != null && x.Prev.A == x.Obj.A)
})
.GroupBy(x => new { x.Consecutive, x.A });
Output the result:
foreach (var abGroup in groups)
{
int aKey = abGroup.Key.A;
var bList = string.Join(",", abGroup.Select(x => x.Obj.B));
Console.WriteLine("A = {0}, Bs = [ {1} ] ", aKey, bList);
}
Here's the working demo: http://ideone.com/fXgQ3
You can use The GroupAdjacent Extension Method .
Then , you just need
var grps = objects.GroupAdjacent(p => new { p.A });
I think it is the easiest way to implement it .
EDIT:
Here is my test code.
class Program
{
static void Main(string[] args)
{
var ia = new Dummycls[] {
new Dummycls{ A = 1, B = 1 },
new Dummycls{ A = 1, B = 2 },
new Dummycls{ A = 2, B = 3 },
new Dummycls{ A = 2, B = 4 },
new Dummycls{ A = 1, B = 5 },
new Dummycls{ A = 3, B = 6 },
};
var groups = ia.GroupAdjacent(i => i.A);
foreach (var g in groups)
{
Console.WriteLine("Group {0}", g.Key);
foreach (var i in g)
Console.WriteLine(i.ToString());
Console.WriteLine();
}
Console.ReadKey();
}
}
class Dummycls
{
public int A { get; set; }
public int B { get; set; }
public override string ToString()
{
return string.Format("A={0};B={1}" , A , B);
}
}
The result is
Group 1
A=1;B=1
A=1;B=2
Group 2
A=2;B=3
A=2;B=4
Group 1
A=1;B=5
Group 3
A=3;B=6
This is the structure of a method that does what you want:
public static IEnumerable<IGrouping<TKey, TElement>> GroupWithKeyBreaks<T, TKey, TElement>(IEnumerable<T> enumerable,
Func<T, TKey> keySelector,
Func<T, TElement> itemSelector)
{
// Error handling goes here
TKey currentKey = default(TKey);
List<TElement> elements = new List<TElement>();
foreach (T element in enumerable)
{
TKey thisKey = keySelector(element);
if (thisKey == null)
{
continue;
}
if (!thisKey.Equals(currentKey) && elements.Count > 0)
{
yield return new SimpleGrouping<TKey, TElement>(currentKey, elements);
elements = new List<TElement>();
}
elements.Add(itemSelector(element));
currentKey = thisKey;
}
// Add the "last" item
if (elements.Count > 0)
{
yield return new SimpleGrouping<TKey, TElement>(currentKey, elements);
}
}
It uses the following helper class:
private class SimpleGrouping<T, U> : IGrouping<T, U>
{
private T key;
private IEnumerable<U> grouping;
T IGrouping<T, U>.Key
{
get { return key; }
}
IEnumerator<U> IEnumerable<U>.GetEnumerator()
{
return grouping.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return grouping.GetEnumerator();
}
public SimpleGrouping(T k, IEnumerable<U> g)
{
this.key = k;
this.grouping = g;
}
}
Here's a sample usage:
foreach (var grouping in data.GroupWithKeyBreaks(x => x.A, x => x.B))
{
Console.WriteLine("Key: " + grouping.Key);
foreach (var element in grouping)
{
Console.Write(element);
}
}
var groupCounter = 0;
int? prevA = null;
collection
.Select(item => {
var groupId = item.A == prevA ? groupCounter : ++groupCounter;
prevA = item.A;
return new { groupId, item.A, item.B };
})
.GroupBy(item => item.groupId)
.Select(grp => new { A = grp.First().A, Bs = grp.Select(g => g.B) });
If your collection is in o, then:
var trans = o.Aggregate
(
new {
List = new List<Tuple<int, List<int>>>(),
LastSeed = (int?)0
},
(acc, item) =>
{
if (acc.LastSeed == null || item.A != acc.LastSeed)
acc.List.Add(Tuple.Create(item.A, new List<int>()));
acc.List[acc.List.Count - 1].Item2.Add(item.B);
return new { List = acc.List, LastSeed = (int?)item.A};
},
acc => acc.List.Select(
z=>new {A = z.Item1,
B = z.Item2 as IEnumerable<int>
})
);
This produces an IEnumerable<int, IEnumerable<int>> of the required form.
var result = list.ToKeyValuePairs(x => x.A)
.Select(x => new { A = x.Key, Bs = x.Value.Select(y => y.B) });
foreach (var item in result)
{
Console.WriteLine("A = {0} Bs=[{1}]",item.A, String.Join(",",item.Bs));
}
-
public static class MyExtensions
{
public static IEnumerable<KeyValuePair<S,IEnumerable<T>>> ToKeyValuePairs<T,S>(
this IEnumerable<T> list,
Func<T,S> keySelector)
{
List<T> retList = new List<T>();
S prev = keySelector(list.FirstOrDefault());
foreach (T item in list)
{
if (keySelector(item).Equals(prev))
retList.Add(item);
else
{
yield return new KeyValuePair<S, IEnumerable<T>>(prev, retList);
prev = keySelector(item);
retList = new List<T>();
retList.Add(item);
}
}
if(retList.Count>0)
yield return new KeyValuePair<S, IEnumerable<T>>(prev, retList);
}
}
OUTPUT:
A = 1 Bs=[1,2]
A = 2 Bs=[3,4]
A = 1 Bs=[5]
A = 3 Bs=[6]