Dynamic linq - Group by interval (DateTime, Numeric) - linq

I search everywhere and didn`t find anwser for this question. I want to group by intervals (DateTime, Numeric) in Dynamic linq (the data will be crated dynamically so i must use dynamic linq)
Lets assume that we have such data:
ID|Date|Price
1|2010-11-01|100
2|2010-11-01|120
3|2010-11-02|50
4|2010-12-01|30
5|2010-12-01|220
6|2011-01-01|400
How to get this data grouped by like this
-(Group by Day) following groups
->2010-11-01 = 2 elements
->2010-11-02 = 1 elements
->2010-12-01 = 2 elements
->2011-01-01 = 1 elements
-(Group by Month) following groups
->2010-11 = 3 elements
->2010-12 = 2 elements
->2011-01 = 1 elements
-(Group by Quarter) following groups
->2010 q.03 = 5 elements
->2011 q.01 = 1 elements
-(Group by Year) following groups
->2010 = 5 elements
->2011 = 1 element
-(Group by Price (From 0, Each 50)) following groups
-> <0-50) = 1 elements
-> <50-100) = 1 elements
-> <100-150) = 2 elements
-> <200-250) = 1 elements
-> <400-450) = 1 elements
-(ideally it would be Group by Price (From 0-50,From 50-150, From 150-500)) following groups
-> <0-50) = 1 elements
-> <50-150) = 3 elements
-> <150-500) = 2 elements
Any Ideas? I stress again - it must be DYNAMIC LINQ or eventually some sophisticated lambda expression? I should been able to "group" it by column name that will be in string. e.g.
GroupBy("Date"), GroupBy("Price");

Here's how to do it:
For instance:
Group by Month
public Item[] data =
{
new Item { Date = new DateTime(2011, 11, 6), Price = 103, Name = "a" },
new Item { Date = new DateTime(2011, 11, 16), Price = 110, Name = "b" },
new Item { Date = new DateTime(2011, 12, 4), Price = 200, Name = "c" },
new Item { Date = new DateTime(2011, 12, 4), Price = 230, Name = "d" },
new Item { Date = new DateTime(2012, 1, 15), Price = 117, Name = "e" }
};
var groups = data.AsQueryable().GroupBy("Date.Month", "it").Cast<IGrouping<int, Item>>();
You can use "Date.Day" and "Date.Year" and for something like a price range you could use a function which maps everything in the range onto the same value e.g. using integer division "(it.Price / 50)"

Related

Default values for empty groups in Linq GroupBy query

I have a data set of values that I want to summarise in groups. For each group, I want to create an array big enough to contain the values of the largest group. When a group contains less than this maximum number, I want to insert a default value of zero for the empty key values.
Dataset
Col1 Col2 Value
--------------------
A X 10
A Z 15
B X 9
B Y 12
B Z 6
Desired result
X, [10, 9]
Y, [0, 12]
Z, [15, 6]
Note that value "A" in Col1 in the dataset has no value for "Y" in Col2. Value "A" is first group in the outer series, therefore it is the first element that is missing.
The following query creates the result dataset, but does not insert the default zero values for the Y group.
result = data.GroupBy(item => item.Col2)
.Select(group => new
{
name = group.Key,
data = group.Select(item => item.Value)
.ToArray()
})
Actual result
X, [10, 9]
Y, [12]
Z, [15, 6]
What do I need to do to insert a zero as the missing group value?
Here is how I understand it.
Let say we have this
class Data
{
public string Col1, Col2;
public decimal Value;
}
Data[] source =
{
new Data { Col1="A", Col2 = "X", Value = 10 },
new Data { Col1="A", Col2 = "Z", Value = 15 },
new Data { Col1="B", Col2 = "X", Value = 9 },
new Data { Col1="B", Col2 = "Y", Value = 12 },
new Data { Col1="B", Col2 = "Z", Value = 6 },
};
First we need to determine the "fixed" part
var columns = source.Select(e => e.Col1).Distinct().OrderBy(c => c).ToList();
Then we can process with the normal grouping, but inside the group we will left join the columns with group elements which will allow us to achieve the desired behavior
var result = source.GroupBy(e => e.Col2, (key, elements) => new
{
Key = key,
Elements = (from c in columns
join e in elements on c equals e.Col1 into g
from e in g.DefaultIfEmpty()
select e != null ? e.Value : 0).ToList()
})
.OrderBy(e => e.Key)
.ToList();
It won't be pretty, but you can do something like this:
var groups = data.GroupBy(d => d.Col2, d => d.Value)
.Select(g => new { g, count = g.Count() })
.ToList();
int maxG = groups.Max(p => p.count);
var paddedGroups = groups.Select(p => new {
name = p.g.Key,
data = p.g.Concat(Enumerable.Repeat(0, maxG - p.count)).ToArray() });
You can do it like this:-
int maxCount = 0;
var result = data.GroupBy(x => x.Col2)
.OrderByDescending(x => x.Count())
.Select(x =>
{
if (maxCount == 0)
maxCount = x.Count();
var Value = x.Select(z => z.Value);
return new
{
name = x.Key,
data = maxCount == x.Count() ? Value.ToArray() :
Value.Concat(new int[maxCount - Value.Count()]).ToArray()
};
});
Code Explanation:-
Since you need to append default zeros in case when you have less items in any group, I am storing the maxCount (which any group can produce in a variable maxCount) for this I am ordering the items in descending order. Next I am storing the maximum count which the item can producr in maxCount variable. While projecting I am simply checking if number of items in the group is not equal to maxCount then create an integer array of size (maxCount - x.Count) i.e. maximum count minus number of items in current group and appending it to the array.
Working Fiddle.

How can I bind group by query to DataGridview in linq?

I want bind the below query to DataGridview.
how can I it?
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var numberGroups =
from num in numbers
group num by num % 5 into numGroup
select new { Remainder = numGroup.Key, Numbers = numGroup };
How do you want it represented in the DGV since Numbers could contain more than one item how would this go into a DGV row?
Alternatively this should work:
var numberGroups = from num in numbers
group num by num % 5 into numGroup
select new { Remainder = numGroup.Key, Count = numGroup.Count() };
yourGridView.DataSource = numberGroups.ToList();
yourGridView.DataBind(); //don't use if WinForm DGV

Linq: Select 1st item from other group

I am trying to write a query to find just one item from the group, where they grouped by Industry and Weight, then from this one I have to get where Weight is Max and Balance is Max too
This is the example:
var data = new[] {
new {ID = 1, Industry = 2, Weight = 2, Balance = 500},
new {ID = 2, Industry = 2, Weight = 2, Balance = 300},
new {ID = 3, Industry = 2, Weight = 1, Balance = 100},
new {ID = 5, Industry = 4, Weight = 1, Balance = 100},
new {ID = 6, Industry = 4, Weight = 2, Balance = 150},
new {ID = 7, Industry = 4, Weight = 1, Balance = 300},
};
var res = from a in data group a by new {a.Industry, a.Weight} into g
let ID = g.First().ID
let Balance = g.Max(a => a.Balance)
select new { ID, g.Key.Industry, g.Key.Weight, Balance};
Console.WriteLine(res);
So as the result I should get just two records
ID Industry Weight Balance
1 2 2 500
6 4 2 150
but with query above I got 4 records
Any advice?
Regards,
Dmitriy
There are probably many different solutions, but one would be to only group by Industry and sort each group according to your "first element to pick" criteria and then pick the first item in each group.
var res = from item in data
group item by item.Industry into g
let first = g.OrderByDescending(x => x.Weight)
.ThenByDescending(x => x.Balance).First()
select first;
data.GroupBy(x=>new {x.Industry,x.Weight})
.ToDictionary(y=>y.Key,y=>y.ToList().Max(x=>x.Balance));
If you dont want a dictionary then you can as well Select a new DTO or a dynamic object as follows:
data.GroupBy(x=>new {x.Industry,x.Weight})
.Select(x=>new {x.Key.Industry,x.Key.Weight,x.ToList().Max(y=>y.Balance)});
Hopefully that is what you need.

Using LINQ to join [n] collections and find matches

Using LINQ I can find matching elements between two collections like this:
var alpha = new List<int>() { 1, 2, 3, 4, 5 };
var beta = new List<int>() { 1, 3, 5 };
return (from a in alpha
join b in beta on a equals b
select a);
I can increased this to three collections, like so:
var alpha = new List<int>() { 1, 2, 3, 4, 5 };
var beta = new List<int>() { 1, 3, 5 };
var gamma = new List<int>() { 3 };
return (from a in alpha
join b in beta on a equals b
join g in gamma on a equals g
select a);
But how can I construct a LINQ query that will return the matches between N number of collections?
I'm thinking if each collection was added to a parent collection, then the parent collection was iterated through using a recursive loop, it may work?
There's no need to recurse - you can just iterate. However, you may find it best to create a set and intersect that each time:
List<List<int>> collections = ...;
HashSet<int> values = new HashSet<int>(collections[0]);
foreach (var collection in collections.Skip(1)) // Already done the first
{
values.IntersectWith(collection);
}
(Like BrokenGlass, I'm assuming you've got distint values, and that you really just want to find the values which are in all the collections.)
If you prefer the immutable and lazy approach, you could use:
List<List<int>> collections = ...;
IEnumerable<int> values = collections[0];
foreach (var collection in collections.Skip(1)) // Already done the first
{
values = values.Intersect(collection);
}
If you have only unique values you can use Intersect:
var result = alpha.Intersect(beta).Intersect(gamma).ToList();
If you need to preserve multiple values that are not unique you can just exclude non-intersecting items from the original collection as an additional step:
alpha = alpha.Where(x => result.Contains(x)).ToList();
To generalize the Intersect approach you can just use a loop to do all intersections one by one:
IEnumerable<List<int>> collections = new [] { alpha, beta, gamma };
IEnumerable<int> result = collections.First();
foreach (var item in collections.Skip(1))
{
result = result.Intersect(item);
}
result = result.ToList();

Linq/lambda question about .Select (newby learning 3.0)

I am playing with the new stuff of C#3.0 and I have this code (mostly taken from MSDN) but I can only get true,false,true... and not the real value :
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var oddNumbers = numbers.Select(n => n % 2 == 1);
Console.WriteLine("Numbers < 5:");
foreach (var x in oddNumbers)
{
Console.WriteLine(x);
}
How can I fix that to show the list of integer?
Change your "Select" to a "Where"
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var oddNumbers = numbers.Where(n => n % 2 == 1);
Console.WriteLine("Odd Number:");
foreach (var x in oddNumbers)
{
Console.WriteLine(x);
}
The "Select" method is creating a new list of the lambda result for each element (true/false). The "Where" method is filtering based on the lambda.
In C#, you could also use this syntax, which you may find clearer:
var oddNumbers = from n in numbers
where n % 2 == 1
select n;
which the compiler translates to:
var oddNumbers = numbers.Where(n => n % 2 == 1).Select(n => n);
numbers.Select(n => n % 2 == 1);
Change this to
numbers.Where(n => n % 2 == 1);
What select does is "convert" one thing to another. So in this case, it's "Converting" n to "n % 2 == 1" (which is a boolean) - hence you get all the true and falses.
It's usually used for getting properties on things. For example if you had a list of Person objects, and you wanted to get their names, you'd do
var listOfNames = listOfPeople.Select( p => p.Name );
You can think of this like so:
Convert the list of people into a list of strings, using the following method: ( p => p.Name)
To "select" (in the "filtering" sense of the word) a subset of a collection, you need to use Where.
Thanks Microsoft for the terrible naming

Resources