I have an object (Details) which is having employee details. That object has properties (sal and Level).
I just want to get the sum of sal for each and every Level.
foreach (var emp in Details)
{
int empsal=emp.sal;
int empLevel= emp.Level;
}
example: I want the sum of all employee sal into different variables based on the levels.
var sumByLevels = Details.GroupBy(employee => employee.Level)
.ToDictionary(g => g.Key, g => g.Sum(x => x.sal));
Now assume you want to get total salary of level 1:
var result = sumByLevels[1];
You can do that using LINQ's GroupBy method:
Dictionary<int, int> sumsByLevel =
Details.GroupBy(employee => employee.Level, employee => employee.sal).
ToDictionary(group => group.Key, group => group.Sum());
This results in a dictionary with the Levels as keys and the sum of the corresponding sals as value.
The first answer is good. For example you have class:
public class Details
{
public int Level { get; set; }
public int Sal { get; set; }
}
and you have data:
var data = new List<Details>()
{
new Details() {Level = 1, Sal = 1},
new Details() {Level = 1, Sal = 1},
new Details() {Level = 1, Sal = 1},
new Details() {Level = 2, Sal = 2},
new Details() {Level = 2, Sal = 2},
new Details() {Level = 3, Sal = 3},
new Details() {Level = 3, Sal = 3},
new Details() {Level = 3, Sal = 3},
};
if we calculate, we get:
level(1) = 3
level(2) = 4
level(3) = 9
Solution one (good):
var sum = data.GroupBy(x => x.Level).ToDictionary(x => x.Key, a => a.Sum(b => b.Sal));
Console.WriteLine(sum[1]); // result 3
Console.WriteLine(sum[2]); // result 4
Console.WriteLine(sum[3]); // result 9
Solution two (not very good):
public int GetSumOfLevel(int level, List<Details> details)
{
return details.Where(x => x.Level == level).Sum(x => x.Sal);
}
...
Console.WriteLine(GetSumOfLevel(1, data)); // result 3
Console.WriteLine(GetSumOfLevel(2, data)); // result 4
Console.WriteLine(GetSumOfLevel(3, data)); // result 9
May be choose the first option?
Related
Suffering sadly from brain fade. I have the following scenario:
void Main()
{
List<CaseBase> caseList = new List<UserQuery.CaseBase>();
caseList.Add(new CaseBase() {CaseID = 1, CaseSequence = 1, CaseStatus = 1});
caseList.Add(new CaseBase() {CaseID = 1, CaseSequence = 2, CaseStatus = 2});
caseList.Add(new CaseBase() {CaseID = 2, CaseSequence = 1, CaseStatus = 1});
var cases = caseList.Where(x => new List<int> {2}.Contains(x.CaseStatus));
}
// Define other methods and classes here
public class CaseBase
{
public int CaseID {get;set;}
public int CaseSequence {get;set;}
public int CaseStatus {get;set;}
}
Which returns the expected
CaseID
CaseSequence
CaseStatus
1
2
2
What I want are all cases with the same ID where one of them has a status of 2.
CaseID
CaseSequence
CaseStatus
1
1
1
1
2
2
Which should be simple but I'm struggling for a simple solution.
There are a couple of ways to proceed:
You can combine the cases by CaseID and select the matching groups and then break them apart:
var cases = caseList
.GroupBy(c => c.CaseID)
.Where(cg => cg.Any(c => new List<int> { 2 }.Contains(c.CaseStatus)))
.SelectMany(cg => cg);
You can find the desired CaseIDs and then get all matching cases:
var wantedCaseIDs = caseList
.Where(c => new List<int> { 2 }.Contains(c.CaseStatus))
.Select(c => c.CaseID)
.ToHashSet();
var cases = caseList.Where(c => wantedCaseIDs.Contains(c.CaseID));
Or you might want to do it like this:
void Main()
{
List<CaseBase> caseList = new List<UserQuery.CaseBase>();
caseList.Add(new CaseBase() { CaseID = 1, CaseSequence = 1, CaseStatus = 1 });
caseList.Add(new CaseBase() { CaseID = 1, CaseSequence = 2, CaseStatus = 2 });
caseList.Add(new CaseBase() { CaseID = 2, CaseSequence = 1, CaseStatus = 1 });
var cases = caseList.Where(x => new List<int> { 2 }.Contains(x.CaseStatus))
.Join(caseList,x => x.CaseID,y => y.CaseID,(x,y) => new {x,y})
.Select(z => z.y)
.Dump();
}
I have 3 collections
List<Brand> brands= new List<Brand>
{
new Brand { ID = 5, Name = "Toyota"},
new Brand { ID = 6, Name = "Mazda"},
new Brand { ID = 7, Name = "Seat"}
};
ModelParam par1 = new ModelParam() { Id = 1, fuelType = "Petrol", enginePower =110, engineTorque = 130 };
ModelParam par2 = new ModelParam() { Id = 2, fuelType = "Petrol", enginePower = 170, engineTorque = 290 };
ModelParam par3 = new ModelParam() { Id = 3, fuelType = "Diesel", enginePower = 140, engineTorque = 280 };
ModelParam par4 = new ModelParam() { Id = 4, fuelType = "Diesel", enginePower = 190, engineTorque = 320 };
List<Model> models = new List<Model>
{
new Model { Id = 1, Name = "CX5", brandId = 6, parameters = new List<ModelParam> {par1, par3} },
new Model { Id = 2, Name = "Corolla", brandId = 5, parameters = new List<ModelParam> {par2, par3} },
new Model { Id = 3, Name = "Leon", brandId = 7, parameters = new List<ModelParam> {par2, par4} }
};
As a result I want to have a query with combination of:
Brand - Model - FuelType - EnginePower.
I wrote this to join brands and model lists:
var result = models.Join(brands,
mod => mod.brandId,
b => b.ID,
(mod, b) => new { b.Name, mod.Name })
.ToList();
The problem is I can't extract fuelType and enginePower from ModelParams from params property in models List. Any tips?
Can this approach work for you?
var result = models.Join(
brands,
mod => mod.brandId,
b => b.ID,
(mod, b) => mod.parameters.Select(parameter => new {
Brand = b.Name,
Model = mod.Name,
FuelType = parameter.fuelType,
EnginePower = parameter.enginePower
}))
.SelectMany(pr => pr)
.ToList();
You should update your result selector Func a little bit and get the required values fuelType and enginePower from parameters collection in Model class. The following snippet will select only the first fuelType from parameters and max enginePower.
var result = models
.Join(brands, mod => mod.brandId, b => b.ID,
(mod, b) => new
{
Brand = b.Name,
Model = mod.Name,
FuelType = mod.parameters.Select(p => p.fuelType).FirstOrDefault(),
EnginePower = mod.parameters.Max(p => p.enginePower)
})
.ToList();
However, there a situation when model can have multiple values for fuelType and enginePower.
If you need to choose all combinations, just look through the parameters collection and then flatten a result using SelectMany
var result = models
.Join(brands, mod => mod.brandId, b => b.ID,
(mod, b) => mod.parameters.Select(p => new
{
Brand = b.Name,
Model = mod.Name,
FuelType = p.fuelType,
EnginePower = p.enginePower
}))
.SelectMany(_ => _)
.ToList();
Have a list with deviceIds and corresponding actions to be taken on device.
var results= new List<Result>
{
new Result{ DeviceId= 1, ActionType = 1 },
new Result{ DeviceId= 1, ActionType = 2 },
new Result{ DeviceId= 1, ActionType = 3 },
new Result{ DeviceId= 2, ActionType = 1 },
new Result{ DeviceId= 3, ActionType = 1 },
new Result{ DeviceId= 4, ActionType = 1 },
new Result{ DeviceId= 5, ActionType = 1 },
new Result{ DeviceId= 6, ActionType = 1 },
new Result{ DeviceId= 6, ActionType = 2 },
};
How do I filter deviceIds unique in the list(no DeviceId 1), and assign it back to var "results"
results = List<Result>
{
new Result{ DeviceId= 2, ActionType = 1 },
new Result{ DeviceId= 3, ActionType = 1 },
new Result{ DeviceId= 4, ActionType = 1 },
new Result{ DeviceId= 5, ActionType = 1 },
};
Have tried using groupby and couldn't move forward
results = from result in results
group result by result.DeviceId
into groupedResultsByDevice
where groupedResultsByDevice.Count() == 1
select ????
Besides answer with query syntax, in method syntax LINQ query it will be:
results = results.GroupBy(r => r.DeviceId)
.Where(g => g.Key != 1 && g.Count() == 1)
.Select(g => g.First())
.ToList();
After grouping you can select the first (and only element of the group):
results = from result in results
group result by result.DeviceId
into groupedResultsByDevice
where groupedResultsByDevice.Count() == 1
select groupedResultsByDevice.First(); // <---
results = from r in results
group r by r.DeviceId into g
where g.Count() == 1
select g.First()
You can make it a little bit more efficient replacing g.Count() with !g.Skip(1).Any():
results = from r in results
group r by r.DeviceId into g
where !g.Skip(1).Any()
select g.First()
It will return false as soon as second element is found, instead of counting all items in the group.
Check this out
public static void Main()
{
var results = new List<Result>
{
new Result {DeviceId = 1, ActionType = 1},
new Result {DeviceId = 1, ActionType = 2},
new Result {DeviceId = 1, ActionType = 3},
new Result {DeviceId = 2, ActionType = 1},
new Result {DeviceId = 3, ActionType = 1},
new Result {DeviceId = 4, ActionType = 1},
new Result {DeviceId = 5, ActionType = 1},
new Result {DeviceId = 6, ActionType = 1},
new Result {DeviceId = 6, ActionType = 2},
};
List<Result> result = results
.GroupBy(x => x.DeviceId)
.Where(x => x.Count() == 1)
.SelectMany(x => x)
.Distinct()
.ToList();
result.ForEach(Console.WriteLine);
Console.ReadLine();
}
public sealed class Result : IEqualityComparer<Result>
{
public int DeviceId { get; set; }
public int ActionType { get; set; }
public bool Equals(Result x, Result y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return x.DeviceId == y.DeviceId && x.ActionType == y.ActionType;
}
public int GetHashCode(Result obj)
{
unchecked
{
return (obj.DeviceId*397) ^ obj.ActionType;
}
}
public override string ToString()
{
return string.Format("DeviceId: {0}, ActionType: {1}", DeviceId, ActionType);
}
}
Result output:
DeviceId: 2, ActionType: 1
DeviceId: 3, ActionType: 1
DeviceId: 4, ActionType: 1
DeviceId: 5, ActionType: 1
I want to do a query with linq (list of objects) and I really don't know how to do it, I can do the group and the sum but can't select rest of the fields.
Example:
ID Value Name Category
1 5 Name1 Category1
1 7 Name1 Category1
2 1 Name2 Category2
3 6 Name3 Category3
3 2 Name3 Category3
I want to group by ID, SUM by Value and return all fields like this.
ID Value Name Category
1 12 Name1 Category1
2 1 Name2 Category2
3 8 Name3 Category3
Updated :
If you're trying to avoid grouping for all the fields, you can group just by Id:
data.GroupBy(d => d.Id)
.Select(
g => new
{
Key = g.Key,
Value = g.Sum(s => s.Value),
Name = g.First().Name,
Category = g.First().Category
});
But this code assumes that for each Id, the same Name and Category apply. If so, you should consider normalizing as #Aron suggests. It would imply keeping Id and Value in one class and moving Name, Category (and whichever other fields would be the same for the same Id) to another class, while also having the Id for reference. The normalization process reduces data redundancy and dependency.
void Main()
{
//Me being lazy in init
var foos = new []
{
new Foo { Id = 1, Value = 5},
new Foo { Id = 1, Value = 7},
new Foo { Id = 2, Value = 1},
new Foo { Id = 3, Value = 6},
new Foo { Id = 3, Value = 2},
};
foreach(var x in foos)
{
x.Name = "Name" + x.Id;
x.Category = "Category" + x.Id;
}
//end init.
var result = from x in foos
group x.Value by new { x.Id, x.Name, x.Category}
into g
select new { g.Key.Id, g.Key.Name, g.Key.Category, Value = g.Sum()};
Console.WriteLine(result);
}
// Define other methods and classes here
public class Foo
{
public int Id {get;set;}
public int Value {get;set;}
public string Name {get;set;}
public string Category {get;set;}
}
If your class is really long and you don't want to copy all the stuff, you can try something like this:
l.GroupBy(x => x.id).
Select(x => {
var ret = x.First();
ret.value = x.Sum(xt => xt.value);
return ret;
}).ToList();
With great power great responsibility comes. You need to be careful. Line ret.value = x.Sum(xt => xt.value) will change your original collection, as you are passing reference, not new object. If you want to avoid it, you need to add some Clone method into your class like MemberwiseClone (but again, this will create shallow copy, so be careful). Afer that just replace the line with: var ret = x.First().Clone();
try this:
var objList = new List<SampleObject>();
objList.Add(new SampleObject() { ID = 1, Value = 5, Name = "Name1", Category = "Catergory1"});
objList.Add(new SampleObject() { ID = 1, Value = 7, Name = "Name1", Category = "Catergory1"});
objList.Add(new SampleObject() { ID = 2, Value = 1, Name = "Name2", Category = "Catergory2"});
objList.Add(new SampleObject() { ID = 3, Value = 6, Name = "Name3", Category = "Catergory3"});
objList.Add(new SampleObject() { ID = 3, Value = 2, Name = "Name3", Category = "Catergory3"});
var newList = from val in objList
group val by new { val.ID, val.Name, val.Category } into grouped
select new SampleObject() { ID = grouped.ID, Value = grouped.Sum(), Name = grouped.Name, Category = grouped.Category };
to check with LINQPad:
newList.Dump();
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 + ","))
});