C# Aggregation on custom objects - c#

I have a List<Fields> collection where the class Fields has the following properties
(Name, Currency, Amount, Date, OtherList) where OtherList is a List
Sample values
Name Currency Amount Date F1 F2 F3
ABC XX 12 12 Jan 2013 X Y Z
BCA YY 11 12 Jan 2013 A B C
ABC XX 10 13 Jan 2013 X Y Z
Now OtherList object will have the values (X, Y, Z) for record1 and (A, B, C) for record2 et.el
What I need to do is apply an aggregation on the above list on the fields Name, Currency, F1, F2 and F3 so that the result is as follows.
Output
Name Currency Amount Date F1 F2 F3
ABC XX 22 13 Jan 2013 X Y Z
BCA YY 11 12 Jan 2013 A B C
Any idea how I can do this?
Thanks.

You can use LINQ:
var aggrList = list
.GroupBy(x => new { x.Name, x.Currency, x.F1, x.F2, x.F3 })
.Select(grp => new {
Name = grp.First().Name,
Currency = grp.First().Currency,
Amount = grp.Sum(x=>x.Amount),
Date = grp.Max(x=>x.Date),
F1 = grp.First().F1,
F2 = grp.First().F2,
F3 = grp.First().F3,
});

Try to group this way:
list.Where(x => x.OtherList != null && x.OtherList.Count >= 3 )
.GroupBy(x => new {F1 = x.OtherList[0], F2 = x.OtherList[1], F3 = x.OtherList[2]})
.Select(g => new
{
Name = g.First().Name,
Currency = g.First().Currency,
Amount = g.Sum(x => x.Amount),
Date = g.Max(x => x.Date),
F1 = g.Key.F1,
F2 = g.Key.F2,
F3 = g.Key.F3,
});
I guess for Date you will apply Max() function, for Amount - Sum(), but how you plan to apply aggregation for Name and Currency fields?

Related

How to group grid with one item in the group in c#

I am using c#.net desktop application
I have gridview that looks like this
ibengin iend code preferredText Affirmation tag codeScheme Value
1 10 Kitkat Yes Choc
11 15 Mars Yes Choc
16 20 Bounty Yes Choc
21 27 A1 Kitkat Yes Choc USA
28 32 Bounty Yes Choc
33 47 Bounty No Choc
48 61 A1 Kitkat Yes Choc USA
62 65 B7 Mars Yes Choc UK
66 77 Kitkat Yes Choc USA
78 81 Kitkat Yes Choc
I want it to be grouped as follows
Affirmation PreferredText Count Value CodingScheme Code Positions
Yes Kitkat 5 USA A1 1:10,21:27,48:61,66:77,78:81
Yes Mars 2 UK B7 11:15,62:65
Yes Bounty 2 16:20,28:32
No Bounty 1 33:47
I have almost finished it, but one problem remained.
I want to use the first value of codeScheme and code as shown above
Here is the code that I have done so far.
appreciate any help
var lstInfo = grdBreakDown.Rows.Cast<DataGridViewRow>()
.Where(x => !x.IsNewRow) // either..
.Where(x => x.Cells["Tag"].Value.ToString() == Tag) //..or or both
.GroupBy(x => new
{
grpAffirmation = x.Cells["Affirmation"].Value.ToString(),
grpPreferredText = x.Cells["preferredText"].Value.ToString(),
grpValue = (cbxValue.Checked ? x.Cells["Value"].Value.ToString() : "")
})
.Select(y => new
{
Affirmation = y.Key.grpAffirmation,
PreferredText = y.Key.grpPreferredText,
Count = y.Count(),//.ToString(),
Value = y.Key.grpValue,
Positions = string.Join(",", y.Select(i => i.Cells["ibegin"].Value.ToString() + ":" + i.Cells["iend"].Value.ToString()))
})
.OrderByDescending(y => y.Count)
.ToList();
I found the answer
var lstInfo = grdBreakDown.Rows.Cast<DataGridViewRow>()
.Where(x => !x.IsNewRow) // either..
.Where(x => x.Cells["Tag"].Value.ToString() == Tag) //..or or both
.GroupBy(x => new
{
grpAffirmation = x.Cells["Affirmation"].Value.ToString(),
//grpCodingScheme = x.Cells["codingScheme"].Value.ToString(),
//grpCode = x.Cells["code"].Value.ToString(),
grpPreferredText = x.Cells["preferredText"].Value.ToString(),
grpValue = (cbxValue.Checked ? x.Cells["Value"].Value.ToString() : "")
})
.Select(y => new
{
Affirmation = y.Key.grpAffirmation,
CodingScheme = string.Join(",", y.Select(i => i.Cells["codingScheme"].Value.ToString()).Distinct()).TrimEnd(','),
Code = string.Join(",", y.Select(i => i.Cells["code"].Value.ToString()).Distinct()).TrimEnd(','),
PreferredText = y.Key.grpPreferredText,
Count = y.Count(),//.ToString(),
Value = y.Key.grpValue,
Positions = string.Join(",", y.Select(i => i.Cells["ibegin"].Value.ToString() + ":" + i.Cells["iend"].Value.ToString()))
})
.OrderByDescending(y => y.Count)
.ToList();

Find out if a list of strings contains permutations of words from another string (counter for each combination)

I didn't know exactly how to ask this question better so I will try to explain it as best as I can.
Let's say I have one list of 20 strings myList1<string> and I have another string string ToCompare. Now each of the strings in the list as well as the string ToCompare have 8 words divided by empty spaces. I want to know how many times combination of any three words from string ToCompare in any possible order is to be found in the strings of myList1<string>. For an example:
This is the list (short version - example):
string1 = "AA BB CC DD EE FF GG HH";
string2 = "BB DD EE AA HH II JJ MM";
.......
string20 = "NN OO AA RR EE BB FF KK";
string ToCompare = "BB GG AA FF CC MM RR II";
Now I want to know how many times any combination of 3 words from ToCompare string is to be found in myList1<string>. To clarify futher three words from ToCompare "BB AA CC" are found in string1 of the list thus the counter for these 3 words would be 1. Another 3 words from ToCompare "BB AA II" are found in the string2 of myList1<string> but the counter here would be also 1 because it's not the same combination of words (I have "AA" and "BB" but also "II". They are not equal). Order of these 3 words doesn't matter, that means "AA BB CC" = "BB AA CC" = "CC BB AA". I want to know how many combinations of all (any) 3 words from ToCompare are found in myList1<string>. I hope it's clear what I mean.
Any help would be appreciated, I don't have a clue how to solve this. Thanks.
Example from Vanest:
List<string> source = new List<string>();
source.Add("2 4 6 8 10 12 14 99");
source.Add("16 18 20 22 24 26 28 102");
source.Add("33 6 97 38 50 34 87 88");
string ToCompare = "2 4 6 15 20 22 28 44";
The rest of the code is exacty the same, and the result:
Key = 2 4 6, Value = 2
Key = 2 4 20, Value = 1
Key = 2 4 22, Value = 1
Key = 2 4 28, Value = 1
Key = 2 6 20, Value = 1
Key = 2 6 22, Value = 1
Key = 2 6 28, Value = 1
Key = 2 20 22, Value = 1
Key = 2 20 28, Value = 1
Key = 2 22 28, Value = 1
Key = 4 6 20, Value = 1
Key = 4 6 22, Value = 1
Key = 4 6 28, Value = 1
Key = 4 20 22, Value = 1
Key = 4 20 28, Value = 1
Key = 4 22 28, Value = 1
Key = 6 20 22, Value = 1
Key = 6 20 28, Value = 1
Key = 6 22 28, Value = 1
Key = 20 22 28, Value = 1
As you can see there are combinations which not exist in the strings, and the value of the first combination is 2 but it comes only one time in the first string
I think this should suffice your ask,
List<string> source = new List<string>();
source.Add("AA BB CC DD EE FF GG HH");
source.Add("BB DD EE AA HH II JJ MM");
source.Add("NN OO AA RR EE BB FF KK");
string ToCompare = "BB GG AA FF CC MM RR II";
string word1, word2, word3, existingKey;
string[] compareList = ToCompare.Split(new string[] { " " }, StringSplitOptions.None);
Dictionary<string, int> ResultDictionary = new Dictionary<string, int>();
for (int i = 0; i < compareList.Length - 2; i++)
{
word1 = compareList[i];
for (int j = i + 1; j < compareList.Length - 1; j++)
{
word2 = compareList[j];
for (int z = j + 1; z < compareList.Length; z++)
{
word3 = compareList[z];
source.ForEach(x =>
{
if (x.Contains(word1) && x.Contains(word2) && x.Contains(word3))
{
existingKey = ResultDictionary.Keys.FirstOrDefault(y => y.Contains(word1) && y.Contains(word2) && y.Contains(word3));
if (string.IsNullOrEmpty(existingKey))
{
ResultDictionary.Add(word1 + " " + word2 + " " + word3, 1);
}
else
{
ResultDictionary[existingKey]++;
}
}
});
}
}
}
ResultDictionary will have the 3 word combinations that occur in myList1<string> with their count of occurrences. To get the total count, retrieve and add all the value fields from ResultDictionary.
EDIT:
Below snippet produces correct result with the given input,
List<string> source = new List<string>();
source.Add("2 4 6 8 10 12 14 99");
source.Add("16 18 20 22 24 26 28 102");
source.Add("33 6 97 38 50 34 87 88");
string ToCompare = "2 4 6 15 20 22 28 44";
string word1, word2, word3, existingKey;
string[] compareList = ToCompare.Split(new string[] { " " }, StringSplitOptions.None);
string[] sourceList, keywordList;
Dictionary<string, int> ResultDictionary = new Dictionary<string, int>();
source.ForEach(x =>
{
sourceList = x.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < compareList.Length - 2; i++)
{
word1 = compareList[i];
for (int j = i + 1; j < compareList.Length - 1; j++)
{
word2 = compareList[j];
for (int z = j + 1; z < compareList.Length; z++)
{
word3 = compareList[z];
if (sourceList.Contains(word1) && sourceList.Contains(word2) && sourceList.Contains(word3))
{
existingKey = ResultDictionary.Keys.FirstOrDefault(y =>
{
keywordList = y.Split(new string[] { " " }, StringSplitOptions.None);
return keywordList.Contains(word1) && keywordList.Contains(word2) && keywordList.Contains(word3);
});
if (string.IsNullOrEmpty(existingKey))
{
ResultDictionary.Add(word1 + " " + word2 + " " + word3, 1);
}
else
{
ResultDictionary[existingKey]++;
}
}
}
}
}
});
Hope this helps...
I think this will do what you're asking for:
void Main()
{
var list =
new List<String>
{
"AA BB CC DD EE FF GG HH",
"BB DD EE AA HH II JJ MM",
"NN OO AA RR EE BB FF KK"
};
var toCompare = "BB GG AA FF CC MM RR II";
var permutations = CountPermutations(list, toCompare);
}
public Int32 CountPermutations(List<String> list, String compare)
{
var words = compare.Split(' ');
return list
.Select(l => l.Split(' '))
.Select(l => new { String = String.Join(" ", l), Count = l.Join(words, li => li, wi => wi, (li, wi) => li).Count()})
.Sum(x => x.Count - 3);
}
[edit: 2/20/2019]
You can use the following to get all the matches to each list item with the total number of unique combinations
void Main()
{
var list =
new List<String>
{
"AA BB CC DD EE FF GG HH",
"BB DD EE AA HH II JJ MM",
"NN OO AA RR EE BB FF KK",
"AA AA CC DD EE FF GG HH"
};
list.Select((l, i) => new { Index = i, Item = l }).ToList().ForEach(x => Console.WriteLine($"List Item{x.Index + 1}: {x.Item}"));
var toCompare = "BB GG AA FF CC MM RR II";
Console.WriteLine($"To Compare: {toCompare}");
Func<Int32, Int32> Factorial = x => x < 0 ? -1 : x == 0 || x == 1 ? 1 : Enumerable.Range(1, x).Aggregate((c, v) => c * v);
var words = toCompare.Split(' ');
var matches = list
// Get a list of the list items with all their parts
.Select(l => new { Parts = l.Split(' '), Original = l })
// Join each part from the to-compare item to each part of the list item
.Select(l => new { String = String.Join(" ", l), Matches = l.Parts.Join(words, li => li, wi => wi, (li, wi) => li), l.Original })
// Only consider items with at least 3 matches
.Where(l => l.Matches.Count() >= 3)
// Get the each item including how many parts matched and how many unique parts there are of each part
.Select(l => new { l.Original, Matches = String.Join(" ", l.Matches), Count = l.Matches.Count(), Groups = l.Matches.GroupBy(m => m).Select(m => m.Count()) })
// To calculate the unique combinations for each match use the following mathematical equation: match_count! / (frequency_part_1! * frequency_part_2! * ... * frequency_part_n!)
.Select(l => new { l.Original, l.Matches, Combinations = Factorial(l.Count) / l.Groups.Aggregate((c, v) => c * Factorial(v)) })
.ToList();
matches.ForEach(m => Console.WriteLine($"Original: {m.Original}, Matches: {m.Matches}, Combinations: {m.Combinations}"));
var totalUniqueCombinations = matches.Sum(x => x.Combinations);
Console.WriteLine($"Total Unique Combinations: {totalUniqueCombinations}");
}

Group data having unique keys and values

I have a following set of numbers:
1 137
1 143
11 37
11 46
11 132
46 65
46 139
69 90
Now, I need to group the data by the first value in a way that no group key is present in the group values. So, for instance, if I were to simply group data, I'd get this result:
1 137
143
11 37
46
132
46 65
139
69 90
46 here is a group key in the third group and a group value in the second group. In this case I need to merge the group values of the third group into a second group and remove the third group.
The end result of the grouping should look like this:
1 137
143
11 37
46
132
65
139
69 90
I'm relatively new to C#, so I was wondering if there's a fancy way to do it using LINQ.
Try this LINQ solution:
var numbers = new List<Number>
{
new Number {X = 1, Y = 137},
new Number {X = 1, Y = 143},
new Number {X = 11, Y = 37},
new Number {X = 11, Y = 46},
new Number {X = 11, Y = 132},
new Number {X = 46, Y = 65},
new Number {X = 46, Y = 139},
new Number {X = 69, Y = 90}
};
var result = numbers.GroupBy(c => c.X);
var result2 = numbers.FirstOrDefault(c => result.Select(d => d.Key).Contains(c.Y));
var finalResult = numbers.Where(x => x.X == result2?.Y)
.Select(x => { x.X = result2.X;x.Y = x.Y; return x; } )
.Union(numbers.Where(c => c.X != result2?.Y)).GroupBy(c => c.X ,
(key, element) => new
{
Key = key,
Element = element.Select(c => c.Y).ToList()
});
The result:
This could work for you
public static List<Numbers> setNumbers()
{
List<Numbers> num = new List<Numbers>();
num.Add(new Numbers() { Column1 = 1, Column2 = 137 });
num.Add(new Numbers() { Column1 = 1, Column2 = 143 });
num.Add(new Numbers() { Column1 = 11, Column2 = 37 });
num.Add(new Numbers() { Column1 = 11, Column2 = 46 });
num.Add(new Numbers() { Column1 = 11, Column2 = 132 });
num.Add(new Numbers() { Column1 = 46, Column2 = 65 });
num.Add(new Numbers() { Column1 = 46, Column2 = 139 });
num.Add(new Numbers() { Column1 = 69, Column2 = 90 });
return num;
}
public static void group()
{
List<Numbers> numbers = setNumbers();
var grouppedNumbers = numbers
.GroupBy(x => x.Column1).ToList();
grouppedNumbers.AddRange(grouppedNumbers.FirstOrDefault(x => x.First().Column1.Equals(46)).Select(s => new Numbers() { Column1 = 11, Column2 = s.Column2 }).GroupBy(g => g.Column1).ToList());
grouppedNumbers.Remove(grouppedNumbers.FirstOrDefault(x => x.First().Column1.Equals(46)));
foreach (var groups in grouppedNumbers)
{
Console.WriteLine(groups.First().Column1);
foreach(var i in groups)
{
Console.WriteLine(i.Column1+" "+ i.Column2);
}
}
}

Linq for rank() equivalent in SQL Server -

How can order my list using Linq equals rank() over in SQL ?
For example rank is my List<Player>
class Player
{
public int Id;
public int RankNumber;
public int Points;
public int Name;
}
Original Rank list:
RankNumber Points Name Id
1 100 James 01
2 80 Mike 50
3 80 Jess 22
4 50 Jack 11
5 50 Paul 03
6 10 Monik 13
I need this Rank:
RankNumber Points Name Id
1 100 James 01
2 80 Mike 50
2 80 Jess 22
4 50 Jack 11
4 50 Paul 03
6 10 Monik 13
I don't think there is a good way to convert this directly to Linq to SQL but you could do this:
var rankedPlayers = players
.OrderByDescending(p => p.Points)
.Select((p, r) => new Player
{
Id = p.Id,
RankNumber = players.Where(pl => pl.Points > p.Points).Count() + 1,
Points = p.Points,
Name = p.Name
});
It gives you the correct output but will convert horribly and inefficiently to SQL. So I would suggest this modification which materialises the data to a list before creating the ranks:
var rankedPlayers = players
.OrderByDescending(p => p.Points)
.ToList() //<--- Add this
.Select((p, r) => new Player
{
Id = p.Id,
RankNumber = players.Where(pl => pl.Points > p.Points).Count() + 1,
Points = p.Points,
Name = p.Name
});
You can try below expression:
var newData = players
.OrderByDescending(x => x.Points)
.GroupBy(x => x.Points)
.SelectMany((x, index) => x.Select(y => new Player
{
Name = y.Name,
Points = y.Points,
RankNumber = index + 1,
Id = y.Id
}));
players contains IEnumerable of objects of type Player and newData contains ordered data with rank.

LINQ merge 2 query results

The datatable has 5 columns
Name Class Course Month Score
Alex C1 Math 12 90
Bob C1 Chem 11 91
Alex C2 Math 11 91
Alex C1 Math 11 89
Bob C1 Chem 12 97
Alex C1 Math 10 94
Alex C2 Chem 12 92
Bob C2 Math 12 94
And I wanna group (name, class) and fetch the max math score in just Nov and Dec, and the max chem score. Heres my query code
DataRow[] dr1 = dt.Select("Course = 'Math' AND Month > 10");
var result_one = dr1.AsEnumerable()
.GroupBy(r => new { Name = r.Field<string>("Name"), Class = r.Field<string>("Class") })
.Select(g => new
{
Name = g.Key.Name,
Class = g.Key.Class,
Max = g.Max(r => r.Field<int>("Score")),
Max_Month = g.FirstOrDefault(gg => gg.Field<int>("Score") == g.Max(r => r.Field<int>("Score"))).Field<int>("Month"),
}
).Distinct().ToList();
DataRow[] dr2 = dt.Select("Course = 'Chem'");
var result_two = dr2.AsEnumerable()
.GroupBy(r => new { Name = r.Field<string>("Name"), Class = r.Field<string>("Class") })
.Select(g => new
{
Name = g.Key.Name,
Class = g.Key.Class,
Max = g.Max(r => r.Field<int>("Score")),
Max_Month = g.FirstOrDefault(gg => gg.Field<int>("Score") == g.Max(r => r.Field<int>("Score"))).Field<int>("Month"),
}
).Distinct().ToList();
And I could output these 2 query results as this:
Name Class Math_Max_Month Math_Max
Alex C1 12 90
Alex C2 11 91
Bob C2 12 94
Name Class Chem_Max_Month Chem_Max
Bob C1 12 97
Alex C2 12 92
But how can I merge these 2 results into 1 output such as this:
Name Class Math_Max_Month Math_Max Chem_Max_Month Chem_Max
Alex C1 12 90 null null
Alex C2 11 91 12 92
Bob C1 null null 12 97
Bob C2 12 94 null null
I've tried to use result_one.Concat(result_two) and result_one.Union(result_two), but both are incorrect.
Alright, seems a bit complicated in your example. So i'll give you an answer on a int[] instead of DataRow[]
int[] first = new int[] { 3, 5, 6, 9, 12, 14, 18, 20, 25, 28 };
int[] second = new int[] { 30, 32, 34, 36, 38, 40, 42, 44, 46, 48 };
int[] result = first
.Concat(second)
.OrderBy(x => x)
.ToArray();
Output will be
// 3, 5, 6, 9, 12, 14, 18, 20, 25, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48
Console.Write(String.Join(", ", result));
theoretically this should work in your case, sense we're only dealing with arrays.
This works perfectly well for your code.,
DataRow[] dr1 = dtt.Select("Course = 'Math' AND Month > 10");
var result_one = dr1.AsEnumerable()
.GroupBy(r => new { Name = r.Field<string>("Name"), Class = r.Field<string>("Class") })
.Select(g => new
{
Name = g.Key.Name,
Class = g.Key.Class,
Max = g.Max(r => r.Field<int>("Score")),
Max_Month = g.FirstOrDefault(gg => gg.Field<int>("Score") == g.Max(r => r.Field<int>("Score"))).Field<int>("Month"),
}
).Distinct().ToList();
DataRow[] dr2 = dtt.Select("Course = 'Chem'");
var result_two = dr2.AsEnumerable()
.GroupBy(r => new { Name = r.Field<string>("Name"), Class = r.Field<string>("Class") })
.Select(g => new
{
Name = g.Key.Name,
Class = g.Key.Class,
Chem_Max = g.Max(r => r.Field<int>("Score")),
Chem_Max_Month = g.FirstOrDefault(gg => gg.Field<int>("Score") == g.Max(r => r.Field<int>("Score"))).Field<int>("Month"),
}
).Distinct().ToList();
Left Join...
var lstLeftJoin = (from a in result_one
join b in result_two
on new { a.Name, a.Class } equals new { b.Name, b.Class }
into gj
from subpet in gj.DefaultIfEmpty()
select new { a.Name, a.Class, Math_Max_Month = a.Max_Month, Math_Max = a.Max, Chem_Max_Month = (subpet == null ? 0 : subpet.Chem_Max_Month), Chem_Max = (subpet == null ? 0 : subpet.Chem_Max) }).ToList();
Right Join...
var lstRightJoin = (from a in result_two
join b in result_one
on new { a.Name, a.Class } equals new { b.Name, b.Class }
into gj
from subpet in gj.DefaultIfEmpty()
select new { a.Name, a.Class, Math_Max_Month = (subpet == null ? 0 : subpet.Max_Month), Math_Max = (subpet == null ? 0 : subpet.Max), a.Chem_Max_Month, a.Chem_Max }).ToList();
Finaly the Union...
var lstUnion = lstLeftJoin.Select(s => new { Name = s.Name, Class = s.Class, Math_Max_Month = s.Math_Max_Month, Math_Max = s.Math_Max, Chem_Max_Month = s.Chem_Max_Month, Chem_Max = s.Chem_Max }).Union(lstRightJoin.Select(s => new { Name = s.Name, Class = s.Class, Math_Max_Month = s.Math_Max_Month, Math_Max = s.Math_Max, Chem_Max_Month = s.Chem_Max_Month, Chem_Max = s.Chem_Max })).OrderBy(o => o.Name).ThenBy(c => c.Class).ToList();
RESULT
Name Class Math_Max_Month Math_Max Chem_Max_Month Chem_Max
Alex C1 12 90 null null
Alex C2 11 91 12 92
Bob C1 null null 12 97
Bob C2 12 94 null null

Categories

Resources