LINQ Group inside a group - c#

I have a datatable like this:
date
Type
Agency
TotalCount
ABC_Count
DEF_Count
GHI_Count
JAN-2022
X
B2X
5
5
2
3
JAN-2022
X
C4A
7
5
7
2
FEB-2022
X
B2X
3
2
3
1
FEB-2022
X
C4A
9
1
9
4
MAR-2022
X
B2X
8
3
1
8
MAR-2022
X
C4A
7
1
1
7
JAN-2022
Y
D5Y
6
6
4
3
JAN-2022
Y
E7T
7
3
7
2
FEB-2022
Y
D5Y
4
2
4
1
FEB-2022
Y
E7T
9
2
9
4
MAR-2022
Y
D5Y
8
3
1
8
MAR-2022
Y
E7T
8
1
1
8
Code:
public class GroupModel {
public string LetterGroupName { get; set; }
public List<LetterGroupData> LetterGroupData { get; set; }
}
public class LetterGroupData {
public string Date { get; set; }
public double TypeX_Ave { get; set; }
public double TypeY_Ave { get; set; }
public int Total { get; set; }
}
My desired output would be a list of GroupModel List<GroupModel> and it should be something like this:
"GroupModel" :
[
{
"LetterGroupName" : "ABC",
"LetterGroupData" : [
{
"Date": "JAN-2022",
"TypeX_Ave": 40.0, //Sum ABC_Count for Type X / (Sum of TotalCount for JAN-2022 Type X + Y)
"TypeY_Ave": 36.0,//Sum ABC_Count for Type Y / (Sum of TotalCount for JAN-2022 Type X + Y)
"Total": 25 //sum of TotalCount for JAN-2022 Type X + Y
},
{
"Date": "FEB-2022",
"TypeX_Ave": 12.0, //Sum ABC_Count for Type X / (Sum of TotalCount for FEB-2022 Type X + Y)
"TypeY_Ave": 16.0,//Sum ABC_Count for Type Y / (Sum of TotalCount for FEB-2022 Type X + Y)
"Total": 25 //sum of TotalCount for FEB-2022 Type X + Y
},//..and so on for MAR-2022
]
},
{
"LetterGroupName" : "DEF",
"LetterGroupData" : [
{
"Date": "JAN-2022",
"TypeX_Ave": 36.0, //Sum DEF_Count for Type X / (Sum of TotalCount for JAN-2022 Type X + Y)
"TypeY_Ave": 44.0,//Sum DEF_Count for Type Y / (Sum of TotalCount for JAN-2022 Type X + Y)
"Total": 25 //sum of TotalCount for JAN-2022 Type X + Y
},
{
"Date": "FEB-2022",
"TypeX_Ave": 48.0, //Sum DEF_Count for Type X / (Sum of TotalCount for FEB-2022 Type X + Y)
"TypeY_Ave": 52.0,//Sum DEF_Count for Type Y / (Sum of TotalCount for FEB-2022 Type X + Y)
"Total": 25 //sum of TotalCount for FEB-2022 Type X + Y
},//..and so on for MAR-2022
]
},//...and so on for LetterGroupName GHI
]
How do I achieve this result using only a single LINQ query?

Implementation:
private static IReadOnlyCollection<GroupModel> GetGroups(IReadOnlyCollection<Dictionary<string, object>> input) =>
(
from groupName in new[] { "ABC", "DEF", "GHI" }
select
new GroupModel
{
LetterGroupName = groupName,
LetterGroupData = (
from item in input
group item by (string)item["date"] into itemGroup
let total = itemGroup.Sum(item => (int)item["TotalCount"])
select new LetterGroupData
{
Date = itemGroup.Key,
TypeX_Ave = (double)itemGroup
.Where(item => (string)item["Type"] == "X")
.Sum(item => (int)item[groupName + "_Count"])
/ total * 100,
TypeY_Ave = (double)itemGroup
.Where(item => (string)item["Type"] == "Y")
.Sum(item => (int)item[groupName + "_Count"])
/ total * 100,
Total = total,
}).ToList()
}
).ToList();
Usage:
var input =
new Dictionary<string, object>[]
{
new()
{
["date"] = "JAN-2022",
["Type"] = "X",
["Agency"] = "B2X",
["TotalCount"] = 5,
["ABC_Count"] = 5,
["DEF_Count"] = 2,
["GHI_Count"] = 3,
},
new()
{
["date"] = "JAN-2022",
["Type"] = "X",
["Agency"] = "C4A",
["TotalCount"] = 7,
["ABC_Count"] = 5,
["DEF_Count"] = 7,
["GHI_Count"] = 2,
},
new()
{
["date"] = "JAN-2022",
["Type"] = "Y",
["Agency"] = "B2X",
["TotalCount"] = 6,
["ABC_Count"] = 6,
["DEF_Count"] = 4,
["GHI_Count"] = 3,
},
new()
{
["date"] = "JAN-2022",
["Type"] = "Y",
["Agency"] = "C4A",
["TotalCount"] = 7,
["ABC_Count"] = 3,
["DEF_Count"] = 7,
["GHI_Count"] = 2,
},
// ...
};
var groups = GetGroups(input);
// groups.Dump();
.NET Fiddle

I think this is what you really want getting data from a datatable :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("date", typeof(string));
dt.Columns.Add("Type", typeof(string));
dt.Columns.Add("Agency", typeof(string));
dt.Columns.Add("TotalCount", typeof(int));
dt.Columns.Add("ABC_Count", typeof(int));
dt.Columns.Add("DEF_Count", typeof(int));
dt.Columns.Add("GHI_Count", typeof(int));
dt.Rows.Add(new object[] { "JAN - 2022", "X", "B2X", 5, 5, 2, 3 });
dt.Rows.Add(new object[] { "JAN - 2022", "X", "C4A", 7, 5, 7, 2 });
dt.Rows.Add(new object[] { "FEB - 2022", "X", "B2X", 3, 2, 3, 1 });
dt.Rows.Add(new object[] { "FEB - 2022", "X", "C4A", 9, 1, 9, 4 });
dt.Rows.Add(new object[] { "MAR - 2022", "X", "B2X", 8, 3, 1, 8 });
dt.Rows.Add(new object[] { "MAR - 2022", "X", "C4A", 7, 1, 1, 7 });
dt.Rows.Add(new object[] { "JAN - 2022", "Y", "D5Y", 6, 6, 4, 3 });
dt.Rows.Add(new object[] { "JAN - 2022", "Y", "E7T", 7, 3, 7, 2 });
dt.Rows.Add(new object[] { "FEB - 2022", "Y", "D5Y", 4, 2, 4, 1 });
dt.Rows.Add(new object[] { "FEB - 2022", "Y", "E7T", 9, 2, 9, 4 });
dt.Rows.Add(new object[] { "MAR - 2022", "Y", "D5Y", 8, 3, 1, 8 });
dt.Rows.Add(new object[] { "MAR - 2022", "Y", "E7T", 8, 1, 1, 8 });
List<GroupModel> groups = dt.AsEnumerable().GroupBy(x => x.Field<string>("Agency"))
.Select(x => new GroupModel() {
LetterGroupName = x.Key,
LetterGroupData = x.Select(y => new LetterGroupData() {
Date = y.Field<string>("date"),
TypeX_Ave = x.Where(z => z.Field<string>("Type") == "X").Select(a => a.Field<int>("ABC_Count")).Sum(),
TypeY_Ave = x.Where(z => z.Field<string>("Type") == "Y").Select(a => a.Field<int>("ABC_Count")).Sum(),
Total = x.Sum(a => a.Field<int>("ABC_Count"))
}).ToList(),
}).ToList();
}
}
public class GroupModel
{
public string LetterGroupName { get; set; }
public List<LetterGroupData> LetterGroupData { get; set; }
}
public class LetterGroupData
{
public string Date { get; set; }
public double TypeX_Ave { get; set; }
public double TypeY_Ave { get; set; }
public int Total { get; set; }
}
}
If you really mean that you want totals by date you have to go back to the datatable like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("date", typeof(string));
dt.Columns.Add("Type", typeof(string));
dt.Columns.Add("Agency", typeof(string));
dt.Columns.Add("TotalCount", typeof(int));
dt.Columns.Add("ABC_Count", typeof(int));
dt.Columns.Add("DEF_Count", typeof(int));
dt.Columns.Add("GHI_Count", typeof(int));
dt.Rows.Add(new object[] { "JAN - 2022", "X", "B2X", 5, 5, 2, 3 });
dt.Rows.Add(new object[] { "JAN - 2022", "X", "C4A", 7, 5, 7, 2 });
dt.Rows.Add(new object[] { "FEB - 2022", "X", "B2X", 3, 2, 3, 1 });
dt.Rows.Add(new object[] { "FEB - 2022", "X", "C4A", 9, 1, 9, 4 });
dt.Rows.Add(new object[] { "MAR - 2022", "X", "B2X", 8, 3, 1, 8 });
dt.Rows.Add(new object[] { "MAR - 2022", "X", "C4A", 7, 1, 1, 7 });
dt.Rows.Add(new object[] { "JAN - 2022", "Y", "D5Y", 6, 6, 4, 3 });
dt.Rows.Add(new object[] { "JAN - 2022", "Y", "E7T", 7, 3, 7, 2 });
dt.Rows.Add(new object[] { "FEB - 2022", "Y", "D5Y", 4, 2, 4, 1 });
dt.Rows.Add(new object[] { "FEB - 2022", "Y", "E7T", 9, 2, 9, 4 });
dt.Rows.Add(new object[] { "MAR - 2022", "Y", "D5Y", 8, 3, 1, 8 });
dt.Rows.Add(new object[] { "MAR - 2022", "Y", "E7T", 8, 1, 1, 8 });
List<GroupModel> groups = dt.AsEnumerable().GroupBy(x => x.Field<string>("Agency"))
.Select(x => new GroupModel() {
LetterGroupName = x.Key,
LetterGroupData = x.Select(y => new LetterGroupData() {
Date = y.Field<string>("date"),
TypeX_Ave = dt.AsEnumerable().Where(z => z.Field<string>("Type") == "X" && z.Field<string>("date") == y.Field<string>("date")).Select(a => a.Field<int>("ABC_Count")).Sum(),
TypeY_Ave = dt.AsEnumerable().Where(z => z.Field<string>("Type") == "Y" && z.Field<string>("date") == y.Field<string>("date")).Select(a => a.Field<int>("ABC_Count")).Sum(),
Total = dt.AsEnumerable().Where(z => (z.Field<string>("Type") == "X" || z.Field<string>("Type") == "Y") && z.Field<string>("date") == y.Field<string>("date")).Select(a => a.Field<int>("ABC_Count")).Sum(),
}).ToList(),
}).ToList();
}
}
public class GroupModel
{
public string LetterGroupName { get; set; }
public List<LetterGroupData> LetterGroupData { get; set; }
}
public class LetterGroupData
{
public string Date { get; set; }
public double TypeX_Ave { get; set; }
public double TypeY_Ave { get; set; }
public int Total { get; set; }
}
}

Related

What is the best way to find the most numerous duplicates present in a dictionary using LINQ?

I have a dictionary that looks approximately like this:
{ 1, Value1 }
{ 2, Value1 }
{ 3, Value1 }
{ 4, Value1 }
{ 5, Value2 }
{ 6, Value2 }
{ 7, Value2 }
{ 8, Value3 }
{ 9, Value3 }
{ 10, Value3 }
{ 11, Value3 }
{ 12, Value3 }
{ 13, Value3 }
I am trying to find a way, using LINQ or otherwise, to isolate the most numerous duplicate. e.g., in this case it is going to be Value3 - and I am trying to obtain the number of times it has been duplicated.
My progress so far consists of the following implementation, so I was wondering if there is any better way:
var SortedDict = OriginalDict.OrderBy(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
//Deduce the most common duplicate
var List1 = SortedDict.Values.ToList().FindAll(x => x == "Value1");
var List2 = SortedDict.Values.ToList().FindAll(x => x == "Value2");
var List3 = SortedDict.Values.ToList().FindAll(x => x == "Value3");
int MostCommon = new List<int>() { List1.Count, List2.Count, List3.Count }.Max();
In this case, my expected result would be 6 (Value3). Value2 would be 3 and Value1 would be 4.
Another problem I see is that if there is the same number of duplicates - in which case it would be best
Try following :
Dictionary<int, string> dict = new Dictionary<int, string>()
{
{ 1, "Value1" },
{ 2, "Value1" },
{ 3, "Value1" },
{ 4, "Value1" },
{ 5, "Value2" },
{ 6, "Value2" },
{ 7, "Value2" },
{ 8, "Value3" },
{ 9, "Value3" },
{ 10, "Value3" },
{ 11, "Value3" },
{ 12, "Value3" },
{ 13, "Value3" }
};
var results = dict
.GroupBy(x => x.Value)
.Select(x => new { value = x.Key, count = x.Count() })
.OrderByDescending(x => x.count)
.FirstOrDefault();

Removing Primary Key and Joining Rows in Datatable

I'm trying to sort a DataTable to be able to get the best selling items by order in C#. The DataTable's fields are orderId, productId, size, and amount (amount = how much of it was bought). The current primary key in the table is comprised of orderId, productId, and size. I would like to get the best selling items by sorting the table in a way that would remove orderId, and have productId and size as the new primary key, and amount as the sum of all amount's for orders with this productId and size.
What would be the best way to do this? I'm attaching my current table data for reference. My table data for reference.
If anything is unclear please let me know, this is my first time using Stack Overflow.
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication20
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("orderid", typeof(int));
dt.Columns.Add("productid", typeof(int));
dt.Columns.Add("size", typeof(int));
dt.Columns.Add("amount", typeof(int));
dt.Rows.Add(new object[] { 1, 1, 38, 12 });
dt.Rows.Add(new object[] { 1, 1, 41, 6 });
dt.Rows.Add(new object[] { 1, 2, 36, 8 });
dt.Rows.Add(new object[] { 1, 2, 38, 5 });
dt.Rows.Add(new object[] { 1, 3, 46, 2 });
dt.Rows.Add(new object[] { 4, 3, 40, 7 });
dt.Rows.Add(new object[] { 8, 3, 40, 7 });
dt.Rows.Add(new object[] { 9, 2, 40, 9 });
dt.Rows.Add(new object[] { 12, 2, 37, 5 });
dt.Rows.Add(new object[] { 13, 2, 37, 4 });
dt.Rows.Add(new object[] { 14, 2, 38, 3 });
dt.Rows.Add(new object[] { 15, 3, 41, 4 });
dt.Rows.Add(new object[] { 16, 2, 36, 7 });
dt.Rows.Add(new object[] { 16, 3, 41, 5 });
dt.Rows.Add(new object[] { 17, 2, 38, 4 });
dt.Rows.Add(new object[] { 18, 3, 40, 3 });
dt.Rows.Add(new object[] { 19, 5, 38, 9 });
dt.Rows.Add(new object[] { 20, 2, 36, 2 });
dt.Rows.Add(new object[] { 21, 1, 40, 3 });
dt.Rows.Add(new object[] { 22, 1, 38, 8 });
dt.Rows.Add(new object[] { 23, 1, 40, 9 });
dt.Rows.Add(new object[] { 24, 2, 37, 1 });
dt.Rows.Add(new object[] { 25, 5, 39, 4 });
dt.Rows.Add(new object[] { 2034, 3, 40, 3 });
dt.Rows.Add(new object[] { 2035, 2, 37, 6 });
dt.Rows.Add(new object[] { 2035, 3, 40, 5 });
dt.Rows.Add(new object[] { 2036, 2, 36, 2 });
dt.Rows.Add(new object[] { 2037, 2, 37, 3 });
dt.Rows.Add(new object[] { 2037, 3, 41, 7 });
dt.Rows.Add(new object[] { 2038, 1, 39, 3 });
dt.Rows.Add(new object[] { 2038, 5, 37, 4 });
var results = dt.AsEnumerable()
.GroupBy(x => new { productid = x.Field<int>("productid"), size = x.Field<int>("size") })
.Select(x => new { productid = x.Key.productid, size = x.Key.size, count = x.Count(), total = x.Sum(y => y.Field<int>("amount")) })
.OrderByDescending(x => x.count)
.ToList();
}
}
}

Left Outer Join with multiple Data tables

I have 3 DataTables
DataTable1
Id Version URL Owner
1 1 "xx" "alice"
2 1 "yy" "bob"
3 1 "zz" "Mike"
4 1 "ww" "Rob"
5 1 "ww" "Bick"
DataTable2
Id Version DomainID Region Type
1 1 aa asia 1
2 1 bb europe 2
3 1 cc africa 1
4 1 dd aus1 0
DataTable3
Id Size FreeSpace
aa 2500 2000
bb 3300 3000
cc 5500 50
Expected Join
Id Version URL Owner DomainID Region Type Size Freespace
1 1 "xx" "alice" aa asia 1 2500 2000
2 1 "yy" "bob" bb europe 2 3300 3000
3 1 "zz" "Mike" cc africa 1 5500 50
4 1 "ww" "sean" dd aus1 0 null null
5 1 "ww" "Bick" null null null null null
I am doing a Join Operation on these tables using Linq as follows:
// Datatable1 joins with Datatable2 on Id and version (datatable1) --> Id and version (datatable2)
// Datatable2 joins with Datatable3 on DomainId(datatable2) --> Id(datatable3)
var result = from dataRows1 in DataTable1.AsEnumerable()
join dataRows2 in DataTable2.AsEnumerable() on
new
{
Id = dataRows1.Field<long>("Id"),
Version = dataRows1.Field<long>("version")
} equals new
{
Id = dataRows2.Field<long>("Id"),
Version = dataRows2.Field<long>("version")
}
into tempJoin
from datarowc in tempJoin.DefaultIfEmpty()
join dataRows3 in DataTable3.AsEnumerable() on
dataRowsc.Field<long>("DomainId") equals dataRows3.Field<long>("Id")
select new
{
datarow1,
datarowc,
datarow3
}
I am getting an exception of datarowc to be null.
Not quite sure why datarowc is null here and how to achieve the expected join.
using System.Data;
using System.Linq;
namespace CodeWars
{
class Program
{
static void Main(string[] args)
{
var result = datarows1.AsEnumerable()
.Select(x => new
{
Tab1Row = x,
Tab2Row = datarows2.AsEnumerable().FirstOrDefault(
y => x.Field<int>("Id") == y.Field<int>("Id") &&
x.Field<int>("Version") == y.Field<int>("Version")
)
}
)
.Select(x => new
{
Tab1Row = x.Tab1Row,
Tab2Row = x.Tab2Row,
Tab3Row = datarows3.AsEnumerable().FirstOrDefault(
y => x?.Tab2Row?.Field<string>("DomainId") == y.Field<string>("Id")
)
}
);
}
static DataTable datarows1 = new DataTable
{
Columns = {
{ "Id", typeof(int) },
{ "Version", typeof(int) },
{ "URL", typeof(string) },
{ "Owner", typeof(string) },
},
Rows = {
{ 1, 1, "xx", "alice" },
{ 2, 1, "yy", "bob" },
{ 3, 1, "vv", "mike" },
{ 4, 1, "ww", "rob" },
{ 5, 1, "zz", "bick" },
}
};
static DataTable datarows2 = new DataTable
{
Columns = {
{ "Id", typeof(int) },
{ "Version", typeof(int) },
{ "DomainID", typeof(string) },
{ "Region", typeof(string) },
{ "Type", typeof(int) },
},
Rows = {
{ 1, 1, "aa", "asia", 1 },
{ 2, 1, "bb", "europe", 2},
{ 3, 1, "cc", "asia", 1},
{ 4, 1, "dd", "aus1", 0},
}
};
static DataTable datarows3 = new DataTable
{
Columns = {
{ "Id", typeof(string) },
{ "Size", typeof(int) },
{ "FreeSpace", typeof(int) },
},
Rows = {
{ "aa", 2500, 2000 },
{ "bb", 3300, 3000 },
{ "cc",5500, 50},
}
};
}
}
.Join() performs inner join, but you want left outer join, so forget about .Join()
Code I've provided gives you the result you expect. But maybe you need to add one more Select to form datastructure you need.

Linq join two List<T> using multiples flags

Hello everyone today i got confused trying to join two List using ArticleId and CellarId as Flag, if the both Flags match then subtract and display the result. This is my code:
Articles Class:
public class Articles
{
public Int32 ArticleId { get; set; }
public Int32 CellarId { get; set; }
public Double ArticleQuantity { get; set; }
}
Array data and display results:
List<Articles> arr1 = new List<Articles>();
arr1.Add(new Articles { ArticleId = 1, CellarId = 2, ArticleQuantity = 10.50 });
arr1.Add(new Articles { ArticleId = 2, CellarId = 2, ArticleQuantity = 5.00 });
arr1.Add(new Articles { ArticleId = 5, CellarId = 1, ArticleQuantity = 2.00 });
arr1.Add(new Articles { ArticleId = 1, CellarId = 1, ArticleQuantity = 4.00 });
List<Articles> arr2 = new List<Articles>();
arr2.Add(new Articles { ArticleId = 5, CellarId = 2, ArticleQuantity = 1.00 });
arr2.Add(new Articles { ArticleId = 3, CellarId = 2, ArticleQuantity = 0.50 });
arr2.Add(new Articles { ArticleId = 1, CellarId = 1, ArticleQuantity = 5.00 });
foreach (var a in GetArrayDifferences3D(arr1, arr2))
{
Console.WriteLine("ArticleId: {0}, CellarId: {1}, Qty: {2}",
a.ArticleId, a.CellarId, a.ArticleQuantity);
}
Join using linq function:
public static List<Articles> GetArrayDifferences3D(
List<Articles> Array1,
List<Articles> Array2)
{
List<Articles> ArrayDifferences = new List<Articles>();
ArrayDifferences = Array1.Concat(Array2)
.GroupBy(g => new { g.ArticleId, g.CellarId })
.Select(s => new Articles
{
ArticleId = s.Select(articles => articles.ArticleId).First(),
CellarId = s.Select(cellars => cellars.CellarId).First(),
ArticleQuantity = (s.Select(a => a.ArticleQuantity).Sum())
}).ToList();
return ArrayDifferences;
}
I need to return this result:
ArticleId: 1, CellarId: 2, Qty: -10.50
ArticleId: 2, CellarId: 2, Qty: -5.00
ArticleId: 5, CellarId: 1, Qty: -2.00
ArticleId: 1, CellarId: 1, Qty: 1.00
ArticleId: 5, CellarId: 2, Qty: 1.00
ArticleId: 3, CellarId: 2, Qty: 0.50
My Current result:
ArticleId: 1, CellarId: 2, Qty: 10.5
ArticleId: 2, CellarId: 2, Qty: 5
ArticleId: 5, CellarId: 1, Qty: 2
ArticleId: 1, CellarId: 1, Qty: 9
ArticleId: 5, CellarId: 2, Qty: 1
ArticleId: 3, CellarId: 2, Qty: 0.5
any help is appreciated, Thanks
The way your code is currently written, you would need ArticleQuantity in arr1 to be negative rather than positive for the Sum() to give you the expected result. I suppose that array represents quantity sold, to be subtracted from quantity on hand.
A simple solution would be to create a new version of arr1:
var arr1Negative = arr1.Select(a =>
new Articles()
{
ArticleId = a.ArticleId,
CellarId = a.CellarId,
ArticleQuantity = -a.ArticleQuantity
});
and use that in place of arr1.
You could also move similar logic into GetArrayDifferences3D().

Sort a list of objects into groups in C#

I have a list of 'tickets' and each 'ticket' contains three numbers. I would to sort all the tickets into groups so each group contains tickets which share at least one number in common. How do I sort this data into a final list of grouped tickets?
In short here is the initial list of tickets:
ticketA = { 1, 2, 3 }
ticketB = { 3, 4, 1 }
ticketC = { 5, 6, 7 }
ticketD = { 7, 8, 5 }
ticketE = { 9, 10, 11 }
ticketF = { 11, 1, 9 }
The resulting output would be (broken into seperate lines for ease of reading visually:
GroupedTickets = {
<List>( ticketA, ticketB, ticketF ticketE )
<List>( ticketC, ticketD )
}
Below is the snippet of code I've been using to figure out a solution for...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CrowdTool
{
class Ticket
{
public string Name { get; set; }
public List<int> Numbers { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Sort();
}
public void Sort()
{
List<Ticket> allTickets = new List<Ticket>();
Ticket ticketA = new Ticket();
ticketA.Numbers = new List<int> { 1, 2, 3 };
allTickets.Add(ticketA);
Ticket ticketB = new Ticket();
ticketB.Numbers = new List<int> { 3, 4, 1 };
allTickets.Add(ticketB);
Ticket ticketC = new Ticket();
ticketC.Numbers = new List<int> { 5, 6, 7 };
allTickets.Add(ticketC);
Ticket ticketD = new Ticket();
ticketD.Numbers = new List<int> { 7, 8, 5 };
allTickets.Add(ticketD);
Ticket ticketE = new Ticket();
ticketE.Numbers = new List<int> { 9, 10, 11 };
allTickets.Add(ticketE);
Ticket ticketF = new Ticket();
ticketF.Numbers = new List<int> { 11, 1, 9 };
allTickets.Add(ticketF);
// variable to store groups of tickets
List <List<Ticket>> GroupedTickets = new List<List<Ticket>>();
foreach (var ticket in allTickets)
{
Console.WriteLine(ticket);
}
}
}
}
So, I've taken the approach of making all the groups - for all ticket numbers. The final results can then be queried to get you what you want.
I had to change the data into a form that suited the processing. I started with this:
var tickets = new Dictionary<string, int[]>()
{
{ "TicketA", new [] { 1, 2, 3 } },
{ "TicketB", new [] { 3, 4, 1 } },
{ "TicketC", new [] { 5, 6, 7 } },
{ "TicketD", new [] { 7, 8, 5 } },
{ "TicketE", new [] { 9, 10, 11 } },
{ "TicketF", new [] { 11, 1, 9 } },
};
Now I can do this query:
var groupedTickets =
tickets
.SelectMany(t => t.Value, (t, n) => new { t, n })
.ToLookup(x => x.n, x => x.t)
.OrderBy(x => x.Key)
.Select(x => new
{
number = x.Key,
tickets = x.Select(y => new
{
ticket = y.Key,
numbers = y.Value
}).ToList()
})
.ToList();
Now that gave me the results like this:
But that's not terribly easy to see the whole thing, so I reformatted like this:
1: TicketA = {1, 2, 3}, TicketB = {3, 4, 1}, TicketF = {11, 1, 9}
2: TicketA = {1, 2, 3}
3: TicketA = {1, 2, 3}, TicketB = {3, 4, 1}
4: TicketB = {3, 4, 1}
5: TicketC = {5, 6, 7}, TicketD = {7, 8, 5}
6: TicketC = {5, 6, 7}
7: TicketC = {5, 6, 7}, TicketD = {7, 8, 5}
8: TicketD = {7, 8, 5}
9: TicketE = {9, 10, 11}, TicketF = {11, 1, 9}
10: TicketE = {9, 10, 11}
11: TicketE = {9, 10, 11}, TicketF = {11, 1, 9}
You should be able to query against groupedTickets to get precisely what you want.
For example, you could do this:
var output =
groupedTickets
.Where(x => x.tickets.Skip(1).Any())
.Select(x => String.Join(", ", x.tickets.Select(y => y.ticket)))
.OrderBy(x => x)
.Distinct();
Which will give you this output:
TicketA, TicketB
TicketA, TicketB, TicketF
TicketC, TicketD
TicketE, TicketF
And this is quite similar to the output requested, but formatted for display purposes.
Based on the question edit and the comments below here is an updated solution.
var lookup =
tickets
.SelectMany(t => t.Value, (t, n) => new { t, n })
.ToLookup(x => x.n, x => x.t.Value);
var groupedTickets =
tickets
.SelectMany(t => t.Value, (t, n) => new { t, n })
.OrderBy(x => x.n)
.ToLookup(x => x.n, x => x.t)
.SelectMany(
x => x.SelectMany(y => y.Value),
(x, y) => new []
{
Tuple.Create(x.Key, y),
Tuple.Create(y, x.Key)
})
.SelectMany(t => t)
.Where(t => t.Item1 != t.Item2)
.Distinct();
Func<
IEnumerable<Tuple<int, int>>,
IEnumerable<Tuple<int, int>>,
int,
IEnumerable<Tuple<int, int>>> fold = null;
fold = (ts0, ts1, n) =>
n == 0
? ts0
: ts0
.Concat(fold(
ts0.Join(
ts1,
t0 => t0.Item2,
t1 => t1.Item1,
(t0, t1) => Tuple.Create(t0.Item1, t1.Item2)),
ts1,
n - 1))
.Distinct()
.ToArray();
var pairs = tickets.SelectMany(t => t.Value).Distinct().Count();
var final =
fold(groupedTickets, groupedTickets, pairs)
.OrderBy(x => x.Item1)
.ThenBy(x => x.Item2)
.GroupBy(x => x.Item1, x => x.Item2)
.GroupBy(x => String.Join(",", x), x => x.Key)
.Select(x => x.SelectMany(y => lookup[y]).Distinct());
This produces the two distinct sets:
{ { 1, 2, 3 }, { 3, 4, 1 }, { 11, 1, 9 }, { 9, 10, 11 } }
{ { 5, 6, 7 }, { 7, 8, 5 } }
Not very optimized, but this will do the job and you can improve upon it for efficiency (most obviously with the .Clear() and .AddRange()).
var tickets = new Dictionary<string, List<int>>()
{
{ "TicketA", new List<int> { 1, 2, 3 } },
{ "TicketB", new List<int> { 3, 4, 1 } },
{ "TicketC", new List<int> { 5, 6, 7 } },
{ "TicketD", new List<int> { 7, 8, 5 } },
{ "TicketE", new List<int> { 9, 10, 11 } },
{ "TicketF", new List<int> { 11, 1, 9 } },
};
var newDict = new Dictionary<string, List<int>>(tickets);
foreach(var ticket in newDict)
{
bool madeChange = true;
while(madeChange)
{
var groupTickets = newDict.Where(t => t.Key != ticket.Key && t.Value.Intersect(ticket.Value).Any() && t.Value.Except(ticket.Value).Any()).ToList();
madeChange = false;
if (groupTickets.Any())
{
var newSet = groupTickets.SelectMany (t => t.Value).Union(ticket.Value).Distinct().ToList();
ticket.Value.Clear();
ticket.Value.AddRange(newSet);
foreach(var groupTicket in groupTickets)
{
groupTicket.Value.Clear();
groupTicket.Value.AddRange(newSet);
}
madeChange = true;
}
}
}
newDict.GroupBy (t => String.Join(",", t.Value)).Dump();
Essentially, it will look for all tickets with a matching number. It will then insert the numbers into all tickets which match. It repeats this until no new numbers are found.

Categories

Resources