I have these two classes in my code and a List of Class2. I want to group the list elements by date and ID using LINQ.
public class Class1
{
public string ID;
public int y;
public int z;
}
public class Class2
{
public List<Class1> a;
public int p, q;
public string s;
public DateTime date;
}
My list is similar to this:
List<Class2> object1 = new List<Class2>
{
new Class2 {p = 5, q = 6, date = DateTime.Now, a = new List<Class1> { new Class1 { ID = "3643746HDJ", y = 0, z = 9 }, new Class1 { ID = "746327846HDJ", y = 0, z = 9 } } },
new Class2 {p = 5, q = 6, date = DateTime.Now.AddDays(1), a = new List<Class1> { new Class1 { ID = "3643746HDJ", y = 0, z = 9 }, new Class1 { ID = "746327846HDJ", y = 0, z = 9 } } },
new Class2 {p = 5, q = 6, date = DateTime.Now.AddDays(2), a = new List<Class1> { new Class1 { ID = "3643746HDJ", y = 0, z = 9 }, new Class1 { ID = "746327846HDJ", y = 0, z = 9 } } },
new Class2 {p = 5, q = 6, date = DateTime.Now.AddDays(3), a = new List<Class1> { new Class1 { ID = "3643746HDJ", y = 0, z = 9 }, new Class1 { ID = "746327846HDJ", y = 0, z = 9 } } },
};
Sorry if this is a stupid question, but I am a beginner in C# programming and LINQ and I am not sure if this is even possible.
This is what I have tried based on examples I saw on the web but its not correct
var newList = from item in object1
group new
{
item.s,
item.a[0].x, //I think its wrong because of this
item.a[0].y //and this
}
by new
{
item.date,
item.a[0].ID //and this
}
into temp
select temp;
Since I have hardcoded index 0 in the grouping I am missing a lot of elements in my final list. How can I do this for all elements of list a in Class2?
Expected output is similar to this:
Key: {date: 19-09-2017 ID: 3643746HDJ}, Element: {{s: "abc", y = 1, z = 2}, {s: "pqr", y = 2, z = 4}, {s: "abc", y = 1, z = 2}}
Key: {date: 20-09-2017 ID: 3643746HDJ}, Element: {{s: "pop", y = 1, z = 2}, {s: "dfr", y = 2, z = 4}, {s: "abc", y = 1, z = 2}}
Key: {date: 19-09-2017 ID: 746327846HDJ}, Element: {{s: "abc", y = 7, z = 8}, {s: "asar", y = 2, z = 111}, {s: "abc", y = 1, z = 2}}
Key: {date: 20-09-2017 ID: 746327846HDJ}, Element: {{s: "abc", y = 7, z = 8}, {s: "asar", y = 2, z = 111}, {s: "abc", y = 1, z = 2}}
Your question states:
Since I have hardcoded index 0 in the grouping I am missing a lot of elements in my final list. How can I do this for all elements of list a in Class2?
As you want all the items from the nested collections you need to flatten the nested collections before grouping. In query syntax do so using another from:
var result = from item in object1
from nested in item.a
group new { item.s, nested.y, nested.z } by new { item.date, nested.ID } into g
select g;
As each nested object does not have a p and q properties but your code shows accessing the nested items I used y and z instead
This can also be done using method syntax. To flatten the nested collections use SelectMany. For the GroupBy you can use the overload where you specify both key and element selector. General idea for that:
var result = object1.SelectMany(item => item.a.Select(nested => new {item, nested }))
.GroupBy(key => new { key.item.Date, key.nested.Id },
val => new { val.item.s, val.nested.x, val.nestd.y });
Even though it seems it is only for question purposes only I recommend giving meaningful names for properties and variables and to have a look at:
MSDN Naming Conventions
Dofactory
Related
I have a big list of objects and in this object there is a category ID something like:
var list = new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 1, Value = new { }},
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 3, Value = new { }}
// and so on
};
So I am looking for making this complicated list more organized like list of lists of unique elements
something like:
var result = new List<List<Example>>
{
new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 3, Value = new { }}
},
new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }}
},
new List<Example>
{
new Example {CatId = 1, Value = new { }}
}
}
Problem is I do not what to use, group by will not fix my case, so how to do this in most efficient way.
So this is about partitioning, it's the sort of thing that is easy to do in a database query, but in c# you need to create some key with a partition number that you can then use to .GroupBy.
The partitioning itself is a grouping
var projected = list.GroupBy(x => x.CatId)
.SelectMany( g => g.Select( ( x, i ) => new { Item = x, rn = i + 1 } ) );
This gives you records that look like:
{"Item":{"CatId":1,"Value":{}},"rn":1}
{"Item":{"CatId":1,"Value":{}},"rn":2}
{"Item":{"CatId":1,"Value":{}},"rn":3}
{"Item":{"CatId":2,"Value":{}},"rn":1}
{"Item":{"CatId":2,"Value":{}},"rn":2}
{"Item":{"CatId":3,"Value":{}},"rn":1}
As you can see that rn ("row number") value can be used to group by:
var result = projected.GroupBy(x => x.rn, x => x.Item);
This gives us:
[{"CatId":1,"Value":{}},{"CatId":2,"Value":{}},{"CatId":3,"Value":{}}]
[{"CatId":1,"Value":{}},{"CatId":2,"Value":{}}]
[{"CatId":1,"Value":{}}]
So, all in 1 go:
var result = list.GroupBy(x => x.CatId)
.SelectMany( g => g.Select( ( x, i ) => new { Item = x, rn = i + 1 } ) )
.GroupBy(x => x.rn, x => x.Item);
Live example: https://dotnetfiddle.net/AlTfk8
This question already has answers here:
Is there a good LINQ way to do a cartesian product?
(3 answers)
Closed 4 years ago.
I'm looking to get the Cartesian Product of an arbitrary number of objects in c#. My situation is slightly unusual - my inputs are not lists of base types, but objects which have a property that's a list of base types.
My input and output objects are as follows:
public class Input
{
public string Label;
public List<int> Ids;
}
public class Result
{
public string Label;
public int Id;
}
Some sample input data:
var inputs = new List<Input>
{
new Input { Label = "List1", Ids = new List<int>{ 1, 2 } },
new Input { Label = "List2", Ids = new List<int>{ 2, 3 } },
new Input { Label = "List3", Ids = new List<int>{ 4 } }
};
And my expected output object:
var expectedResult = new List<List<Result>>
{
new List<Result>
{
new Result{Label = "List1", Id = 1},
new Result{Label = "List2", Id = 2},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 1},
new Result{Label = "List2", Id = 3},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 2},
new Result{Label = "List2", Id = 2},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 2},
new Result{Label = "List2", Id = 3},
new Result{Label = "List3", Id = 4}
}
};
If I knew the number of items in 'inputs' in advance I could do this:
var knownInputResult =
from id1 in inputs[0].Ids
from id2 in inputs[1].Ids
from id3 in inputs[2].Ids
select
new List<Result>
{
new Result { Id = id1, Label = inputs[0].Label },
new Result { Id = id2, Label = inputs[1].Label },
new Result { Id = id3, Label = inputs[2].Label },
};
I'm struggling to adapt this to an arbitrary number of inputs - is there a possible way to do this?
I consider this duplicate of question linked in comments, but since it was reopened and you struggle to adapt that question to your case, here is how.
First grab function by Eric Lippert from duplicate question as is (how it works is explained there):
public static class Extensions {
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] { item })
);
}
}
Then flatten your input. Basically just attach corresponding label to each id:
var flatten = inputs.Select(c => c.Ids.Select(r => new Result {Label = c.Label, Id = r}));
Then run cartesian product and done:
// your expected result
var result = flatten.CartesianProduct().Select(r => r.ToList()).ToList();
I'm not proud of the amount of time I spent messing with this, but it works.
It's basically black magic, and I would replace it the first chance you get.
public static List<List<Result>> Permutate(IEnumerable<Input> inputs)
{
List<List<Result>> results = new List<List<Result>>();
var size = inputs.Select(inp => factorial_WhileLoop(inp.Ids.Count)).Aggregate((item, carry) => item + carry) - 1;
for (int i = 0; i < size; i++) results.Add(new List<Result>());
foreach (var input in inputs)
{
for (int j = 0; j < input.Ids.Count; j++)
{
for (int i = 0; i < (size / input.Ids.Count); i++)
{
var x = new Result() { Label = input.Label, Id = input.Ids[j] };
results[(input.Ids.Count * i) + j].Add(x);
}
}
}
return results;
}
public static int factorial_WhileLoop(int number)
{
var result = 1;
while (number != 1)
{
result = result * number;
number = number - 1;
}
return result;
}
public class CoordinatePoint
{
public double x { get; set; }
public double y { get; set; }
}
var Events = new List<CoordinatePoint>();
Events.Add(new CoordinatePoint { x = 1, y = 10 });
Events.Add(new CoordinatePoint { x = 2, y = 20 });
Events.Add(new CoordinatePoint { x = 3, y = 30 });
Events.Add(new CoordinatePoint { x = 4, y = 40 });
Events.Add(new CoordinatePoint { x = 5, y = 50 });
var Data= new List<object>();
CorridorData.Add(new object[] { 1, 11});
CorridorData.Add(new object[] { 2, 21});
CorridorData.Add(new object[] { 3, 31});
CorridorData.Add(new object[] { 4, 41});
CorridorData.Add(new object[] { 5, 51 });
I need to join those two lists to calculate the sum where the primary key of List Events is x and for List Data is the first index.
You can use LINQ.Join method to accomplish what you want (despite that, I would recommend to review your architecture to make the code more clear):
var result = Events.Join(
CorridorData,
e => (int)e.x,
c => ((object[])c)[0],
(e, c) => new { x = (int)e.x, data = ((object[])c)[1] });
Please pay attention that in your Coordinate Point the coordinate is of double type and in your data you add 1 which is int. So if you want to join them you have to cast double to int (or vice versa) that could result into an unexpected behavior for you.
Update:
If you want to get sum use the next query:
var result = Events.Join(
CorridorData,
e => (int)e.x,
c => ((object[])c)[0],
(e, c) => new { x = (int)e.x, data = (int)((object[])c)[1] + e.y });
That will result exactly to what you have written in the comment:
1, 21.0
2, 41.0
3, 61.0
4, 81.0
5, 101.0
Note that there are a lot of places where it can throw an exception or just return 0 results if your list will became invalid (which compiler can't check as all of entries are objects).
Suppose I have simple "leg" Class defined by two points (actually PointIDs):
public class Leg
{
public int p1Id;
public int p2Id;
}
List of Legs are "interconnected", on the way that pt2 of one leg is same as pt1 of another leg (except first and last one). Let's initialize simple list:
var legs = new List<Leg>();
var l1 = new Leg { p1Id = 1, p2Id = 2 };
var l2 = new Leg { p1Id = 4, p2Id = 5 };
var l3 = new Leg { p1Id = 2, p2Id = 3 };
var l4 = new Leg { p1Id = 5, p2Id = 6 };
var l5 = new Leg { p1Id = 3, p2Id = 4 };
legs.Add(l1);
legs.Add(l2);
legs.Add(l3);
legs.Add(l4);
legs.Add(l5);
Fact is that this list of legs is not sorted, and of course IDs are not consecutive, like it is the case in this example.
Question is how to create function which will return list of consecutive points, between two given points, so:
var myLegs = FindMyLegs(legs, 5, 2);
.. should return following list : 5, 4, 3, 2
I guess it might be done by LINQ and inner join list of legs twice (legs1.pt2Id == legs2.pt1Id), but I am really not experienced in LINQ, and I can not find right way.
Basically I'll need some function:
List<int> FindMyLegs(List<Leg> allLegs, int startPt, int endPt) { ??? }
If I understand your problem correctly, then I don't think that Linq will be the whole solution here. I think you'll need to build up a data structure that's suited to this kind of problem, and then choose appropriate algorithms to find your paths.
Take a look at Dijkstra's algorithm as a starting point.
I hope this is what you need
private List<int> FindMyLegs(List<Leg> allLegs, int startPt, int endPt)
{
return allLegs.Where(l => l.p1Id <= startPt && l.p1Id >=endPt).Select(l => l.p1Id).OrderByDescending(x => x).ToList();
}
Hope it could help you:
public static List<int> FindMyLegs(List<Leg> allLegs, int startPt, int endPt)
{
var result = new List<int>();
var pre_result = allLegs.Where(l => (l.p1Id >= startPt && l.p2Id <= endPt) || (l.p2Id <= startPt && l.p1Id >= endPt));
foreach (var leg in pre_result)
{
result.Add(leg.p1Id);
result.Add(leg.p2Id);
}
result = (startPt > endPt ? result.OrderBy(t => t) : result.OrderByDescending(t => t)).Distinct().ToList();
return result;
}
static void Main()
{
var legs = new List<Leg>();
var l1 = new Leg { p1Id = 1, p2Id = 2 };
var l2 = new Leg { p1Id = 4, p2Id = 5 };
var l3 = new Leg { p1Id = 2, p2Id = 3 };
var l4 = new Leg { p1Id = 5, p2Id = 6 };
var l5 = new Leg { p1Id = 3, p2Id = 4 };
legs.Add(l1);
legs.Add(l2);
legs.Add(l3);
legs.Add(l4);
legs.Add(l5);
var myLegs = FindMyLegs(legs, 2, 5);
foreach (var leg in myLegs)
{
Console.WriteLine(leg);
}
}
Result:
var myLegs = FindMyLegs(legs, 2, 5);
5
4
3
2
var myLegs = FindMyLegs(legs, 4, 1);
1
2
3
4
Given this example data (in .NET classes where Po, Sku, Qty are properties):
PO, Sku, Qty
1,ABC,1
1,DEF,2
1,GHI,1
1,QWE,1
1,ASD,1
1,ZXC,5
1,ERT,1
2,QWE,1
2,ASD,11
2,ZXC,1
3,ERT,1
3,DFG,1
3,DFH,1
3,CVB,4
3,VBN,1
3,NMY,1
I need to transform it into a fixed column format, with a max of 5 SKUs per line (repeating the PO if needed for > 5):
PO, SkuA, QtyA, SkuB, QtyB, SkuC, QtyC, SkuD, QtyD, SkuE, QtyE
1, ABC, 1, DEF, 2, GHI, 1, QWE, 1, ASD, 1
1, ZXC, 5, ERT, 1, , , , , ,
2, QWE, 1, ASD, 11, ZXC, 1, , , ,
3, ERT, 1, DFG, 1, DFH, 1, CVB, 4, VBN, 1
3, NMY, 1, , , , , , , ,
Output can be CSV (which is what I'm outputting), or .NET classes - no matter there. Is there a simple way to do this in Linq by grouping by PO, then by counts of 5?
EDIT: I have no control of over the destination format. And for anyone interested, it's VendorNet and VendorBridge that require this nonsense.
Firstly, here's the query that will generate the correct hierarchy of objects. I'm using anonymous types but it's easy enough to change it to use your own proper classes.
var query = yourData
.GroupBy
(
x => x.PO
)
.SelectMany
(
x => x.Select
(
(y, i) => new { y.PO, y.Sku, y.Qty, Key = i / 5 }
)
)
.GroupBy
(
x => new { x.PO, x.Key }
);
Using LINQ to create the CSV from the query results is bit of a hack, but it gets the job done. (The "benefit" of using LINQ is that you could chain the original query and the CSV generation into a single, massive statement, should you wish.)
IEnumerable<string> csvLines = query
.Select
(
x => x.Aggregate
(
new { Count = 0, SB = new StringBuilder() },
(a, y) => new
{
Count = a.Count + 1,
SB = ((a.SB.Length == 0) ? a.SB.Append(y.PO) : a.SB)
.Append(", ").Append(y.Sku).Append(", ").Append(y.Qty)
},
a => a.SB.ToString() + string.Join(", , ", new string[6 - a.Count])
)
);
string csv = string.Join(Environment.NewLine, csvLines.ToArray());
In my opinion, creating the CSV without using LINQ makes the code much more readable:
StringBuilder sb = new StringBuilder();
foreach (var group in query)
{
int count = 0;
foreach (var item in group)
{
if (count++ == 0)
{
sb.Append(item.PO);
}
sb.Append(", ").Append(item.Sku).Append(", ").Append(item.Qty);
}
while (count++ < 5)
{
sb.Append(", , ");
}
sb.Append(Environment.NewLine);
}
string csv = sb.ToString();
Here you go. I didn't format the output the way you wanted. But this should give you an idea of how to pivot rows. Hope this helps :-)
public class MyClass
{
public int PO { get; set; }
public String SKU { get; set; }
public int Qty { get; set; }
public static IEnumerable<MyClass> GetList()
{
return new List<MyClass>()
{
new MyClass {PO = 1, SKU = "ABC", Qty = 1},
new MyClass {PO = 1, SKU = "DEF", Qty = 2},
new MyClass {PO = 1, SKU = "GHI", Qty = 1},
new MyClass {PO = 1, SKU = "QWE", Qty = 1},
new MyClass {PO = 1, SKU = "ASD", Qty = 1},
new MyClass {PO = 1, SKU = "ZXC", Qty = 5},
new MyClass {PO = 1, SKU = "ERT", Qty = 1},
new MyClass {PO = 2, SKU = "QWE", Qty = 1},
new MyClass {PO = 2, SKU = "ASD", Qty = 1},
new MyClass {PO = 2, SKU = "ZXC", Qty = 5},
};
}
}
EDIT: I've fixed the query based on Luke's comment
var lQuery =
MyClass.GetList()
.GroupBy(pArg => pArg.PO)
.Select(pArg => new
{
Test = pArg.Select((pArg1, pId) =>
new {ID = (pId / 5),
pArg1.PO, pArg1.SKU, pArg1.Qty})
.GroupBy(pArg1 => pArg1.ID)
.Select(pArg1 =>
pArg1.Aggregate(pArg.Key.ToString(),
(pSeed, pCur) =>
pSeed + pCur.SKU + ","))
});