I have the following classes:
public class Item
{
public int Id { get; set; }
public string Sku { get; set; }
public List<Price> Prices { get; set; }
}
public class Price
{
public int Id { get; set; }
public double Cost { get; set; }
public PriceList List { get; set; }
}
public class PriceList
{
public int Id { get; set; }
public string FileName { get; set; }
public DateTime ImportDateTime { get; set; }
}
Essentially, it's for a list of items [Item] in which each item has a list of prices [List<Price> Prices] (one-to-many), and each price has one price list [PriceList List] (one-to-one).
What I need is a list of all price lists.
It seems like what I need is a group to a "grandchild", basically, grouping by the PriceList's Id (which is under Price, which in turn is under Item in the list).
So, as an example, if I have five price lists, it should return five rows.
I achieved it doing the following long way:
List<PriceList> priceLists = new List<PriceList>();
foreach (Item item in items)
{
foreach (Price price in item.Prices)
{
PriceList list = price.List;
if (!priceLists.Any(x => x.Id == list.Id))
{
priceLists.Add(list);
}
}
}
How can it be achieved using LINQ?
UPDATE:
Here's a basic sample set:
PriceList priceList1 = new PriceList { Id = 1, FileName = "Price List 1.csv" };
PriceList priceList2 = new PriceList { Id = 2, FileName = "Price List 2.csv" };
Price price1 = new Price { Id = 1, Cost = 2.65, List = priceList1 };
Price price2 = new Price { Id = 2, Cost = 14.23, List = priceList2 };
Price price3 = new Price { Id = 3, Cost = 29.01, List = priceList1 };
Price price4 = new Price { Id = 4, Cost = 1, List = priceList2 };
Price price5 = new Price { Id = 5, Cost = 56.12, List = priceList1 };
Item item1 = new Item { Id = 1, Sku = "item1", Prices = new List<Price> { price1, price2 } };
Item item2 = new Item { Id = 2, Sku = "item2", Prices = new List<Price> { price3, price4 } };
Item item3 = new Item { Id = 3, Sku = "item3", Prices = new List<Price> { price5 } };
List<Item> itemsList = new List<Item> { item1, item2, item3 };
Let's start from the bottom upwards:
I have a list of Item (itemsList), which contains three Items.
Each Item has a list of Price.
Each Price has one PriceList.
To clarify the reasoning behind all this: The user imports a spreadsheet of prices (price list) they get periodically from their supplier, prices fluctuate and each price list has different prices for the same items. Hence the reason of having an item with many prices and each price has its price list which was used to upload it (there's more information included in the PriceList class which I omitted to keep it simple).
I hope I'm clear.
This seems to be what you want, using an extension to do Distinct by a lambda expression:
var ans = itemsList.SelectMany(item => item.Prices.Select(price => price.List)).DistinctBy(price => price.Id);
The extension is as follows:
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> src, Func<T, TKey> keyFun) {
var seenKeys = new HashSet<TKey>();
foreach (T e in src)
if (seenKeys.Add(keyFun(e)))
yield return e;
}
If you changed your approach to use a HashSet<Int> to track the seen ids you would probably be a tiny bit more efficient than LINQ.
It looks like you can combine a SelectMany (to get all the Prices lists) with a Select (to get all the associated PriceList properties):
List<PriceList> priceLists = items.SelectMany(i => i.Prices).Select(p => p.List).ToList();
Edit
The above will return all the PriceLists, but since many Items could reference the same PriceList, then you need a way to filter on the PriceList.Id field.
One way to do that would be to override the Equals and GetHashCode property of the PriceList object. If you truly consider two PriceList objects to be equal if they have the same Id, then you could do the following:
public class PriceList
{
public int Id { get; set; }
public string FileName { get; set; }
public DateTime ImportDateTime { get; set; }
public override bool Equals(object obj)
{
var other = obj as PriceList;
return Id == other?.Id;
}
public override int GetHashCode()
{
return Id;
}
}
This allows you to use the Linq method, Distinct on your prices (it will call the Equals method to distinguish between PriceLists in the results:
List<PriceList> priceLists = items
.SelectMany(i => i.Prices)
.Select(i => i.List)
.Distinct()
.ToList();
Related
I have al list of Purches items
I want to add new item to my list, that sum all the items in my list
this is my code:
public class Purches
{
public int Id { get; set; }
public int Items { get; set; }
public int TotalPrice { get; set; }
}
List<Purches> purchesList = new List<Purches>() {
new Purches() {
Id = 1,
Items = 3,
TotalPrice = 220
},
new Purches() {
Id = 2,
Items = 5,
TotalPrice = 300
}
};
now, I want to add the list new item that sum the Items and the TotalPrice properties
the result will be something like that:
List<Purches> purchesList = new List<Purches>() {
new Purches() {
Id = 1,
Items = 3,
TotalPrice = 220
},
new Purches()
{
Id = 2,
Items = 5,
TotalPrice = 300
},
new Purches()
{
Id = 0,
Items = 8,
TotalPrice = 550
}
};
I have to do it via linq / Lambda in c#
I would not recommend adding a summary item of the same type. That is just likely to lead to confusion. A better solution would be to to use either a separate object for the total, or use different types with a shared interface, for example:
public class PurchaceSummary{
public List<Purches> Purchases {get;}
public TotalItemCount => Items.Sum(p => p.Items);
public TotalPrice => Items.Sum(p => p.TotalPrices);
}
Or
public interface IPurchaseLineItem{
public int Items { get; }
public int TotalPrice { get; }
}
public interface Purchase : IPurchaseLineItem{
public int Id { get; set; }
public int Items { get; set; }
public int TotalPrice { get; set; }
}
public interface PurchaseSummary : IPurchaseLineItem{
public int Items { get; set; }
public int TotalPrice { get; set; }
}
// Use the LINQ methods from the previous example to create your totals for the summary
In either case it should be immediately obvious for everyone what each value represents.
Purches totalSum = new Purches
{
Id = 0,
Items = purchesList.Sum(p => p.Items),
TotalPrices = purchesList.Sum(p => p.TotalPrices)
};
// now add it to your list if desired
How to sum the value of child items from a hierarchical list in a loop.
I need that during the loop in the list the values of the amount and price property contain the sum of these properties of the children.
Below are the two classes to be used to help me solve my problem.
namespace SGP.Dto.Custo
{
public class PlanilhaCusto
{
public int id{ get; set; }
public int parenteId{ get; set; }
public string name { get; set; }
public decimal amount{ get; set; }
public decimal price{ get; set; }
public PlanilhaCusto(int pId, int pParenteId, pName, decimal pAmount, decimal pPrice)
{
id = pId;
parentId = pParentId;
name = pName;
amount = pAmount;
price = pPrice;
}
}
}
namespace SGP.Dto.Custo
{
public class ShowList
{
List<Dto.Custo.PlanilhaCusto> myList = new List<PlanilhaCusto>();
public void Show()
{
myList.Add(new PlanilhaCusto(1, null, "Projetos", 0, 0));
myList.Add(new PlanilhaCusto(2, 1, "Arquitetura", 5,10));
myList.Add(new PlanilhaCusto(3, 1, "Estrutura", 0, 0));
myList.Add(new PlanilhaCusto(4, 3, "Civil", 1, 50));
myList.Add(new PlanilhaCusto(5, 3, "Infra", 3, 75));
myList.Add(new PlanilhaCusto(6, null, "Pessoal", 0, 0));
myList.Add(new PlanilhaCusto(7, 6, "Mão de Obra", 20, 5700));
/*In this loop the value of the parent items must be updated
(calculated). The hierarchy of the list can be unlimited,
like a tree. I tried using a recursive method but I could
not do it.*/
foreach (var itemList in myList)
{
}
}
}
}
Assuming the Elements in the list are sorted the way they are in your example, the fastest way to do this should be to iterate through the list in reverse order and add the amount to the list entry with the correct id.
Iterate backwards through the list
When the current element has a parent add amount and amount*price to it
EDIT:
After reading your comment in your source, i assume you already knew what i just wrote.
My approach would be the following:
for (int i = myList.Count - 1; i > 1; i--)
{
var temp = myList.ElementAt(i);
if (temp.parentId != null)
{
var parent = myList.ElementAt(temp.parentId - 1);
parent.amount += temp.amount;
parent.price += (temp.amount * temp.price);
}
}
for creating a hierarchical structure, i modified your PlanilhaCusto to resemble a sort of a Node in tree, having both parent and children members, for ease the traverse
public class PlanilhaCusto
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public decimal Amount { get; set; }
public decimal Price { get; set; }
public IEnumerable<PlanilhaCusto> Children { get; set; }
}
Given this structure and an initial input
you can build the entire tree structure regardless of how many levels there are relying on some recursion
public IEnumerable<PlanilhaCusto> Descendants(PlanilhaCusto parent, IEnumerable<PlanilhaCusto> source)
{
var query = from node in source
where node.ParentId == parent.Id
let children = Descendants(node, source)
select new PlanilhaCusto
{
Id = node.Id,
ParentId = node.ParentId,
Name = node.Name,
Amount = node.Amount,
Price = node.Price,
Children = children,
};
return query.ToList();
}
var hierarchy = from node in source
let children = Descendants(node, source)
where node.ParentId == null
select new PlanilhaCusto
{
Id = node.Id,
ParentId = node.ParentId,
Name = node.Name,
Price = node.Price,
Children = children,
};
that would project the initial data source into something similar
and from here you just need to traverse the hierarchy and compose the total price
public decimal? Total(PlanilhaCusto node)
{
decimal? price = node.Price * node.Amount;
if (node.Children != null)
{
foreach (var child in node.Children)
{
price += Total(child);
}
}
return price;
}
var totals = from node in hierarchy
select new
{
Id = node.Id,
Name = node.Name,
Total = Total(node),
};
Trying to figure out how to query an IEnumerable<T> using LINQ. The following simple example without IEnumerable works fine:
class Category
{
public string Title { get; set; }
public NameValue SubCategory { get; set; }
}
class NameValue
{
public string Name { get; set; }
public string Value { get; set; }
}
private static void testLinq()
{
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
foreach (Category c in q)
{
Console.WriteLine("{0} - {1}", c.Title, c.SubCategory.Name);
}
}
When I change the signature to have an IENumerable<NameValue> instead then I cannot access c.SubCategory.Name:
class Category
{
public string Title { get; set; }
public IEnumerable<NameValue> SubCategory { get; set; }
}
// For example, below does not compile:
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
// Also, this initialization of course won't work either
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
The error is:
IEnumerable' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?)
Do I need to do a cast of some sort?
Update:
Output should be something like:
Abc (category)
A (sub)
B (sub)
C (...)
Xyz
B
K
M
Xyz2
A
Q
Z
In SQL I would do like something like this:
SELECT c.Title, s.Name, s.Value FROM Categories c
INNER JOIN SubCategory s ON
c.CategoryID = s.CategoryID
ORDER BY c.Title, s.Name -- sorting first on Category.Title, then on SubCategory.Name
Your SubCategory will be a collection so you cannot do it using ThenBy. You need to order the Category(s) and then order their SubCategory(s) like this. Note I added two SubCategory(s) to the first Category but their order is not correct. After we order them, then they will be correct:
Category[] categories = {
new Category { Title ="Abc", SubCategory = new List<NameValue>
{ new NameValue { Name = "B", Value = "5"},
new NameValue { Name = "A", Value = "5"} } },
new Category { Title ="Xyz", SubCategory = new List<NameValue>
{ new NameValue { Name = "A", Value = "10" } } }};
// First order by categories
var cats = categories.OrderBy(c => c.Title)
// Then create a new category and order by sub categories
.Select(x => new Category { Title = x.Title,
SubCategory = x.SubCategory.OrderBy(y => y.Name) });
If you can get away with only sorting the children when you need to use them, sorting by the parent and then sorting the children upon use like this would be fairly efficient:
public void DisplayA(A value)
{
Console.WriteLine(value.Name);
foreach (var child in value.Children.OrderBy(c => c.Name))
{
Console.WriteLine(string.Format("- {0}", child.Name));
}
}
Or if you want to avoid that, you could add a sorted property to the class. Since it's Linq, it will only be evaluated when you iterate through the list.
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
public IEnumerable<B> SortedChildren { get { return Children.OrderBy(ca => ca.Name); } }
}
public class B
{
public string Name { get; set; }
}
If they don't work for you, you could try these, but they won't be so efficient since you're creating new objects.
// This will flatten it into a single object, sorted by one field and the the other. Since this is Linq, it will create these new flattened objects each time you iterate through the IEnumerable.
public IEnumerable<FlattenedA> GetSortedFlattened(IEnumerable<A> collection)
{
var flattened = collection.SelectMany(a => a.Children.Select(ca => new FlattenedA() { Name = a.Name, SubName = ca.Name }));
var sorted = flattened.OrderBy(f => f.Name).ThenBy(f => f.SubName);
return sorted;
}
// This will return objects of A, where the child enumerable has been replaced with an OrderBy. Again this will return new objects each time you iterate through. Only when you iterate through the children will they be sorted.
public IEnumerable<A> GetSortedNonFlattened(IEnumerable<A> collection)
{
var withSortedChildren = collection.Select(a => new A() { Name = a.Name, Children = a.Children.OrderBy(ca => ca.Name) });
var sorted = withSortedChildren.OrderBy(a => a.Name);
return sorted;
}
public class FlattenedA
{
public string Name { get;set }
public string SubName { get; set; }
}
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
}
when you are setting it as IEnumerable you can't do this
SubCategory = new NameValue { Name = "A", Value = "5"}
you should use some implementation of IEnumerable,
like List<>
so it should be something like this
SubCategory = new List<NameValue>{new NameValue { Name = "A", Value = "5"}, addmore here};
and for your order linq, i would do this,
var OrderedCategories = categories.select(g =>
new Category{ Name = g.Name, subcategories = g.subcategories.orderby(h => h.Name) });
That's because your SubCategory now is no longer a simple single instance of NameValue, but rather an enumeration of those. So now you need to specify how to .ThenBy over a collection of .Names.
I have a list of items {Id, Name, CategoryId} and a list of categories {Id, Name, IsActive}.
How to get a list {CategoryId, Count} including categories that have zero items.
Currently I have such index:
public class CategoryCountIndex : AbstractIndexCreationTask<Item, CategoryCountIndex.Result>
{
public class Result
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public CategoryCountIndex()
{
Map = items => from item in items
select new Result
{
CategoryId = item.CategoryId,
Count = 1
};
Reduce = results => from result in results
group result by result.CategoryId
into c
select new Result
{
CategoryId = c.Key,
Count = c.Sum(x => x.Count)
};
}
}
What is the best way to improve/change my solution in order to have categories with no items?
I removed my earlier answer as it proved to be incorrect. In this case you can actually use a MultiMap/Reduce index to solve your problem.
Try using the following index:
public class Category_Items : AbstractMultiMapIndexCreationTask<Category_Items.ReduceResult>
{
public class ReduceResult
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public Category_Items()
{
AddMap<Item>(items =>
from item in items
select new
{
CategoryId = item.CategoryId,
Count = 1
});
AddMap<Category>(categories =>
from category in categories
select new
{
CategoryId = category.Id,
Count = 0
});
Reduce = results =>
from result in results
group result by result.CategoryId into g
select new ReduceResult
{
CategoryId = g.Key,
Count = g.Sum(x => x.Count)
};
}
}
This will result in the following (three categories, but one without items):
Now you can use a Result Transformer if you want to display the Category Name.
Hope this helps!
I have a list of objects (call them type salesItems) Lets say these items have 50 properties, with Name, price, quantity being 3 of them). I would like to know how to merge the list together combining any salesItems by name using the following logic:
If there are multiple salesOrders that have the same Name:
Combine them into one SalesOrder with the same Name
Set the quantity to the sum of the quantities
Set the price, and all of the other properties using the values of the first
I would like to do with linq. I realize i could use a big for each c# loop instead.
If there are additional items in the list I would like to follow similar logic for those as well.
EX: A salesOrder list with (A,B,C,D,E)
A: Name=ball Price= 2.24 Quantity=1 (other values = bla bla)
B: Name= ball Price = 15.33 Quantity=3 (other values)
c: Name= bat Price = 22.14 Quantity=3 (other values)
D: Name= bat Price= 19.22 Quantity=2 (other values)
E: Name = ball Price=4.32 Quantity=2 (other values)
Result list I want 2 Sales orders in list (A,C) A: Name=ball Price=
2.24 Quantity=6 (other values = bla bla from a's properties) c: Name= bat Price = 22.14 Quantity=5 (other values from c's
properties)
You want linq's .GroupBy method!!!
I've defined your class as:
public class SalesOrder
{
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; }
public SalesOrder(string Name, double Price, int Quantity)
{
this.Name = Name;
this.Price = Price;
this.Quantity = Quantity;
}
}
then I have created a list of your orders like this:
List<SalesOrder> Orders = new List<SalesOrder>()
{
new SalesOrder("Ball", 2.24, 1),
new SalesOrder("Ball", 15.33, 3),
new SalesOrder("Bat", 22.14, 3),
new SalesOrder("Bat", 19.22, 2),
new SalesOrder("Ball", 4.32, 2)
};
and grouped them by the name before selecting the values you want for each group into a new instance of the SalesOrder class like this:
List<SalesOrder> Combined_Orders = Orders
.GroupBy (o => o.Name)
.Select (o => new SalesOrder(o.Key, o.Select (x => x.Price).First(), o.Sum(x => x.Quantity)))
.ToList();
UPDATE: In response to OP's comment
As the real SalesOrder will have hundreds of properties, you can avoid typing them all out in the linq query by adding a constructor to the SalesOrder class that accepts the result of the group by as an argument, then do all the work in the constructor. While it doesn't stop you from having to type out all the properties, it does mean that its neatly abstracted away. Also this way it forces/enables you to decide on what to do with each of the properties (first/sum/average).
To do this you will need a second constructor that looks like this:
public SalesOrder(IGrouping<string, SalesOrder> Group)
{
this.Name = Group.Key;
this.Price = Group.First().Price;
this.Quantity = Group.Sum(g => g.Quantity);
// do all other properties here too
}
Then update the group by to look like this (note that only the result of the grouping "g" is passed into the constructor now):
List<SalesOrder> Combined_Orders = Orders
.GroupBy (o => o.Name)
.Select (g => new SalesOrder(g))
.ToList();
Hi You can use the following code,
class SalesItem
{
public string Name { get; set; }
public int Price { get; set; }
public int Quantity { get; set; }
}
class SalesOrder
{
public void LoadItems()
{
List<SalesItem> SalesItems = new List<SalesItem>();
SalesItem salesitem = new SalesItem()
{
Name = "Ball",
Price = 12,
Quantity = 1
};
SalesItems.Add(salesitem);
salesitem = new SalesItem()
{
Name = "Ball",
Price = 36,
Quantity = 3
};
SalesItems.Add(salesitem);
salesitem = new SalesItem()
{
Name = "Bat",
Price = 50,
Quantity = 1
};
SalesItems.Add(salesitem);
salesitem = new SalesItem()
{
Name = "Ball",
Price = 84,
Quantity = 7
};
SalesItems.Add(salesitem);
salesitem = new SalesItem()
{
Name = "Bat",
Price = 150,
Quantity = 3
};
SalesItems.Add(salesitem);
GroupOrders(SalesItems);
}
public List<SalesItem> GroupOrders(List<SalesItem> SalesItems)
{
var list = from item in SalesItems
group item by item.Name into orders
select new SalesItem
{
Name = orders.Key,
Price = orders.Sum(X=>X.Price),
Quantity = orders.Sum(X=>X.Quantity)
};
List<SalesItem> resultList = new List<SalesItem>();
foreach (SalesItem saleitem in list)
{
resultList.Add(saleitem);
}
return resultList;
}
}