C# calculate total weights - c#

I have two classes as below. I get products list 'ProductBooleans' along with the category flag if they fit in it. I also get the weight for each category 'ProductWeight. I need to find the the total weight for each category.
public class ProductBooleans
{
public int Prodid;
public int Cat1;
public int Cat2;
public int Cat3;
}
public class ProductWeight
{
public string Cat;
public int Wt;
}
I need to calculate the total weight for each product in the basket and return the dict<int,int>
ProductBoolean
var pbs = new List<ProductBooleans>()
{
new ProductBooleans() { Prodid = 333, Cat1 = 1, Cat2 = 0, Cat3 = 1, },
new ProductBooleans() { Prodid = 444, Cat1 = 1, Cat2 = 1, Cat3 = 0, },
};
ProductWeight
var pws = new List<ProductWeight>()
{
new ProductWeight() { Cat = "Cat1", Wt = 10, },
new ProductWeight() { Cat = "Cat2", Wt = 20, },
new ProductWeight() { Cat = "Cat3", Wt = 30, },
};
Result should be
Prodid | totalWt
333 40
444 30
NOTE: I am currently using reflection to solve this problem but I think there has to be much simpler way to achieve this.
Is there lib to do this or a simpler way?

This seems to do the job for me:
var query =
from pb in pbs
let totalWt =
(
from x in new (string Cat, int Wt)[]
{
("Cat1", pb.Cat1),
("Cat2", pb.Cat2),
("Cat3", pb.Cat3),
}
join pw in pws on x.Cat equals pw.Cat
select x.Wt * pw.Wt
).Sum()
select new
{
pb.Prodid,
totalWt
};
That gives me:
Here's a version that uses reflection:
(string Cat, int Wt)[] GetValues(ProductBooleans pb) =>
typeof(ProductBooleans)
.GetFields()
.Where(p => p.Name.StartsWith("Cat"))
.Where(p => p.FieldType == typeof(int))
.Select(p => (p.Name, (int)p.GetValue(pbs[0])))
.ToArray();
var query =
from pb in pbs
let totalWt =
(
from x in GetValues(pb)
join pw in pws on x.Cat equals pw.Cat
select x.Wt * pw.Wt
).Sum()
select new
{
pb.Prodid,
totalWt
};
Obviously change .GetFields() to .GetProperties() if that's needed.

var _categoryDic = new Dictionary<Category, bool>();
_categoryDic.Add(new Category() { CategoryName = "cate1", CatId = 1 }, true);
_categoryDic.Add(new Category() { CategoryName = "cate2", CatId = 2 }, false);
_categoryDic.Add(new Category() { CategoryName = "cate3", CatId = 3 }, true);
productBooleans.Add(new ProductBooleans()
{
prodid = 333
,
categoryDic = _categoryDic
});
var _categoryDic2 = new Dictionary<Category, bool>();
_categoryDic2.Add(new Category() { CategoryName = "cate1", CatId = 1 }, true);
_categoryDic2.Add(new Category() { CategoryName = "cate2", CatId = 2 }, true);
_categoryDic2.Add(new Category() { CategoryName = "cate3", CatId = 3 }, false);
productBooleans.Add(new ProductBooleans()
{
prodid = 444
,
categoryDic = _categoryDic2
});
List<ProductWeight> productWeights = new List<ProductWeight>();
productWeights.Add(new ProductWeight() { category = new Category() { CategoryName = "cate1", CatId = 1 }, weight = 10 });
productWeights.Add(new ProductWeight() { category = new Category() { CategoryName = "cate2", CatId = 2 }, weight = 20 });
productWeights.Add(new ProductWeight() { category = new Category() { CategoryName = "cate3", CatId = 3 }, weight = 30 });
var _prodBool =productBooleans.Select(i => new {
prod_id = i.prodid
,
categoryList = i.categoryDic
.Where(j => j.Value)
.Select(j => j.Key)
.ToList<Category>()
}).ToList();
Dictionary<int, int> resultList=new Dictionary<int, int>();
foreach (var item in _prodBool)
{
int itemweight = 0;
foreach (var pw in productWeights)
{
itemweight += (item.categoryList.Where(i => i.CatId == pw.category.CatId).Any()) ? pw.weight : 0;
}
resultList.Add(item.prod_id, itemweight);
}
return resultList;

Related

Linq Pivot from SQL Query

I have problem with Linq, the code used to be processed in sql via stored procedure, but right now it supposed to be on code via linq, here's my database schema
SQL Fiddle
what I want is , from this data
orderNo
Type
serial
1
BN
BN 1
1
BE
BE 1
2
BN
BN 2
2
BE
BE 2
3
BN
BN 3
3
BE
BE 3
to be like this :
orderNo
be
bn
1
BE 1
BN 1
2
BE 2
BN 3
3
BE 2
BN 3
found one question and solution Source 1 - Stackoverflow , when I tried to my code, I got an issue with SelectMany
here's what I've tried
var results = data_tech.GroupBy(l => l.serial).SelectMany( g =>
new
{
Metadata = g.Key,
data = g
});
var pivoted = new List<PivotedEntity>();
foreach(var item in results)
{
pivoted.Add(
new PivotedEntity
{
Order= item.orderNo,
BE= item.data.Where(x => x.Name == "BE")
.FirstOrDefault().value,
BN= item.data.Where(x => x.Name == "BN")
.FirstOrDefault().value,
});
}
You can simply achieve this by changing the group by element serial to OrderNo. Let me give you an example,
var list = new List<Order>() {
new Order { orderNo = 1, Type = "BN", Serial = "BN 1" },
new Order { orderNo = 1, Type = "BE", Serial = "BE 1" },
new Order { orderNo = 2, Type = "BN", Serial = "BN 2" },
new Order { orderNo = 2, Type = "BE", Serial = "BE 2" },
new Order { orderNo = 3, Type = "BN", Serial = "BE 3" } ,
new Order { orderNo = 3, Type = "BE", Serial = "BN 3" } };
var results = list.GroupBy(l => l.orderNo).Select(g =>
new
{
Metadata = g.Key,
data = g
});
var pivoted = new List<PivotedEntity>();
foreach (var item in results)
{
pivoted.Add(
new PivotedEntity
{
Order = item.Metadata,
BE = item.data.Where(x => x.Type == "BE")
.FirstOrDefault().Serial,
BN = item.data.Where(x => x.Type == "BN")
.FirstOrDefault().Serial,
});
}
This will give you some output like this image.
Edit: Output PivotedEntity class =>
internal class PivotedEntity
{
public int Order { get; set; }
public string BE { get; set; }
public string BN { get; set; }
}

Group by two columns and calculate cumulative value based on one of them

Please consider this list:
List<Data> lst = new List<Data>
{
new Data() { Id = 1, Val1 = 100 },
new Data() { Id = 1, Val1 = 200 },
new Data() { Id = 1, Val1 = 300 },
new Data() { Id = 2, Val1 = 100 },
new Data() { Id = 2, Val1 = 200 },
new Data() { Id = 3, Val1 = 300 },
new Data() { Id = 3, Val1 = 300 },
new Data() { Id = 3, Val1 = 300 },
new Data() { Id = 1, Val1 = 200 },
new Data() { Id = 1, Val1 = 200 },
new Data() { Id = 1, Val1 = 200 },
new Data() { Id = 2, Val1 = 200 },
new Data() { Id = 3, Val1 = 100 },
new Data() { Id = 3, Val1 = 100 },
};
and then this code:
decimal Cumulative_Probability = 0;
var Result1 = (lst.OrderBy(o => o.Id).GroupBy(x => new { x.Val1 })
.Select(y => new
{
y.Key.Val1,
Probability = (Convert.ToDecimal(y.Count()) / lst.Count),
Cumulative_Probability = (Cumulative_Probability =
Cumulative_Probability +
(Convert.ToDecimal(y.Count()) / lst.Count))
})).OrderBy(o => o.Val1).ToList();
this code works fine and Cumulative_Probability calculated correctly.
Now please consider this code:
decimal Cumulative_Probability2 = 0;
var Result2 = (lst.OrderBy(o => o.Id).GroupBy(x => new { x.Id, x.Val1 })
.Select(y => new
{
y.Key.Id,
y.Key.Val1,
Probability = (Convert.ToDecimal(y.Count())
/ lst.Where(o => o.Id == y.Key.Id).Count()),
Cumulative_Probability = (Cumulative_Probability2 =
Cumulative_Probability2 +
(Convert.ToDecimal(y.Count()) /
lst.Where(o => o.Id == y.Key.Id).Count()))
})).OrderBy(o => o.Id).ThenBy(o => o.Val1).ToList();
this code generate this result:
As you can see Probability calculated in each group correctly, but not Cumulative_Probability. I want to calculate Cumulative_Probability in each Id group (group records first accourding Id then Val1) and Cumulative_Probability2 doesn't reset in each group. How I can calculate Cumulative_Probability in each group?
Thanks
Edit 1)
I want this result:
Id Val1 Probability Cumulative_Probability
-------------------------------------------------------------------------
1 100 0.16 0.16
1 200 0.66 0.82
1 300 0.16 0.98
2 100 0.33 0.33
2 200 0.66 0.66
...
I managed to do this with the help of an extension method which accumulates the cumulative probability, along with some nested GroupBy. I'm sure there must be an easier way, but I'm scratching my head trying to find it.
The extension is:
public static class EnumerableExtensions
{
public static IEnumerable<TResult> Accumulate<TSource, TAccumulate, TResult>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, (TAccumulate,TResult)> accumulator)
{
var acc = seed;
foreach(TSource value in source)
{
var (newSeed, newSource) = accumulator.Invoke(acc, value);
yield return newSource;
acc = newSeed;
}
}
}
And the finished code looks like:
var result = lst.GroupBy( x => x.Id)
.SelectMany( (grpId,i) => grpId.GroupBy(x => x.Val1)
.Accumulate(0M, (acc,grpVal) => (acc + (decimal)grpVal.Count()/grpId.Count(), new {
Id = grpId.Key,
Val1 = grpVal.Key,
Probability = (decimal)grpVal.Count()/grpId.Count(),
Cumulative_Probability = acc + ((decimal)grpVal.Count()/grpId.Count())
}))
)
.OrderBy(x => x.Id);
Live example: https://dotnetfiddle.net/dvW1qo
This code works:
var Result2 = (from a in lst.OrderBy(o => o.Id)
group a by new { a.Id, a.Val1 } into grp
select new
{
grp.Key.Id,
grp.Key.Val1,
Probability = (Convert.ToDecimal(grp.Count()) / lst.Where(o => o.Id == grp.Key.Id).Count()),
Cumulative_Probability = (from b in lst.Where(o => o.Id == grp.Key.Id && o.Val1 <= grp.Key.Val1)
group b by new { b.Val1 } into grp2
select new
{
Probability2 = (Convert.ToDecimal(grp2.Count()) / lst.Where(o => o.Id == grp.Key.Id).Count())
}).Sum(o => o.Probability2)
}).OrderBy(o => o.Id).ThenBy(o => o.Val1).ToList();

Linq query - List within List [duplicate]

This question already has answers here:
Group by in LINQ
(11 answers)
Closed 5 years ago.
I'm trying to select a list that contains Fund.Name and List<Investment>.
var funds = new List<Fund>
{
new Fund { Id = 1 , Name = "good" },
new Fund { Id = 2, Name = "bad" }
};
var investments = new List<Investment>
{
new Investment { Fund = funds[0], Value = 100 },
new Investment { Fund = funds[0], Value = 200 },
new Investment { Fund = funds[1], Value = 300 }
};
Then I'm trying to create the query with this:
var query = from f in funds
join i in investments
on f.Id equals i.Fund.Id
select new { f.Name, i };
I wanted something like this:
{ Name = good, {{ Id = 1, Value = 100 }, { Id = 1, Value = 200 }}},
{ Name = bad, { Id = 2, Value = 300 }}
But I'm getting something like this:
{ Name = good, { Id = 1, Value = 100 }},
{ Name = good, { Id = 1, Value = 200 }},
{ Name = bad, { Id = 2, Value = 300 }}
Try using GroupJoin.
var query = funds.GroupJoin(investments, f => f.Id, i => i.Fund.Id, (f, result) => new { f.Name, result });

Convert Objects into Hierarchy based on Number Sequence

I have following data Structure and i want to put covert it in a Hierarchy based on RelationId. This is sorted on RelationId.
Id = 2 has relationId =2 and following two rows has realtionId =0 . That represent the Id=3 and Id=4 are child of Id = 2
Id Name RelationId SortOrder
1 A 1 1
2 B 2 2
3 C 0 3
4 D 0 4
5 E 3 5
6 F 0 6
7 G 0 7
8 H 4 8
End Result would be like following
Id = 1
|
Id = 2
|___ Id = 3 , Id = 4
Id = 5
|___ Id= 6 , Id=7
Id = 8
The desired result is as following (for simplicity representing it as List). This would be a List<Something> in C#
Result =
[
{ Id = 1, Name = A, Children = Null },
{ Id = 2, Name = B, Children = [{ Id = 3, Name = C }, {Id = 4, Name = D }] },
{ Id = 5, Name = E, Children = [{ Id = 6, Name = F }, {Id = 7, Name = G }] },
{ Id = 8, Name = H}
]
My unsuccessful attempt is as following
var finalResult = new List<sampleDataClass>();
var sampleData = GetMeSampleData();
var count = sampleData.Count();
foreach (var item in sampleData)
{
var alreadyExist = finalResult.Any(x => x.Id == item.Id);
var newObject = new sampleDataClass();
if (!alreadyExist && item.RelationId!= 0)
{
newObject = item;
}
for (int i = item.SortOrder; i < count; i++)
{
if (sampleData[i].RelationId== 0)
{
newObject.Children.Add(sampleData[i]);
}
}
finalResult.Add(newObject );
}
Since your RelationId decides whether it is a root or nested element, you could form a group based on these relation and do this. I would suggest using Linq
List<SomeData> somedata = ... // your data.
int index=0;
var results = somedata
.Select(x=> new {gid = x.RelationId ==0? index: ++index, item=x})
.GroupBy(x=> x.gid)
.Select(x=> {
var first = x.FirstOrDefault();
return new
{
Id = first.item.Id,
Name = first.item.Name,
Children = x.Skip(1).Select(s=> new {
Id = s.item.Id,
Name = s.item.Name,
})
};
})
.ToList();
Output :
Id=1, Name=A
Id=2, Name=B
Id=3, Name=C
Id=4, Name=D
Id=5, Name=E
Id=6, Name=F
Id=7, Name=G
Id=8, Name=H
Check this Working Code
I have done it like this. Don't know if there can be some more elegant solution
var data = new List<MyDataObject>();
var SampleData = GetMeSampleData;
var count = SampleData.Count();
for (int i=0;i<count;i++)
{
var rootAdded = false;
var relationId = SampleData[i].relationId;
var alreadyExist = data.Any(x => x.Id == SampleData[i].Id);
var mydataObject = new MyDataObject();
if (!alreadyExist && SampleData[i].RelationId != 0)
{
mydataObject = SampleData[i];
rootAdded = true;
}
for(int j=i+1;j<count;j++)
{
if ((SampleData[j].RelationId == 0 && rootAdded))
{
mydataObject.Children.Add(SampleData[j]);
}
if (SampleData[j].SubjectId != 0)
break;
}
if (rootAdded)
{
data.Add(mydataObject);
}

Select top N records after filtering in each group

I am an old bee in .NET but very new to Linq! After some basic reading I have decided to check my skill and I failed completely! I don't know where I am making mistake.
I want to select highest 2 order for each person for while Amount % 100 == 0.
Here is my code.
var crecords = new[] {
new {
Name = "XYZ",
Orders = new[]
{
new { OrderId = 1, Amount = 340 },
new { OrderId = 2, Amount = 100 },
new { OrderId = 3, Amount = 200 }
}
},
new {
Name = "ABC",
Orders = new[]
{
new { OrderId = 11, Amount = 900 },
new { OrderId = 12, Amount = 800 },
new { OrderId = 13, Amount = 700 }
}
}
};
var result = crecords
.OrderBy(record => record.Name)
.ForEach
(
person => person.Orders
.Where(order => order.Amount % 100 == 0)
.OrderByDescending(t => t.Amount)
.Take(2)
);
foreach (var record in result)
{
Console.WriteLine(record.Name);
foreach (var order in record.Orders)
{
Console.WriteLine("-->" + order.Amount.ToString());
}
}
Can anyone focus and tell me what would be correct query?
Thanks in advance
Try this query:
var result = crecords.Select(person =>
new
{
Name = person.Name,
Orders = person.Orders.Where(order => order.Amount%100 == 0)
.OrderByDescending(x => x.Amount)
.Take(2)
});
Using your foreach loop to print the resulting IEnumerable, the output of it is:
XYZ
-->200
-->100
ABC
-->900
-->800
This has already been answered but if you didn't want to create new objects and simply modify your existing crecords, the code would look like this alternatively. But you wouldn't be able to use anonymous structures like shown in your example. Meaning you would have to create People and Order classes
private class People
{
public string Name;
public IEnumerable<Order> Orders;
}
private class Order
{
public int OrderId;
public int Amount;
}
public void PrintPeople()
{
IEnumerable<People> crecords = new[] {
new People{
Name = "XYZ",
Orders = new Order[]
{
new Order{ OrderId = 1, Amount = 340 },
new Order{ OrderId = 2, Amount = 100 },
new Order{ OrderId = 3, Amount = 200 }
}
},
new People{
Name = "ABC",
Orders = new Order[]
{
new Order{ OrderId = 11, Amount = 900 },
new Order{ OrderId = 12, Amount = 800 },
new Order{ OrderId = 13, Amount = 700 }
}
}
};
crecords = crecords.OrderBy(record => record.Name);
crecords.ToList().ForEach(
person =>
{
person.Orders = person.Orders
.Where(order => order.Amount%100 == 0)
.OrderByDescending(t => t.Amount)
.Take(2);
}
);
foreach (People record in crecords)
{
Console.WriteLine(record.Name);
foreach (var order in record.Orders)
{
Console.WriteLine("-->" + order.Amount.ToString());
}
}
}

Categories