Aggregate List Based on Child Properties - c#

I have an class structure representing an item's list of materials, with each material having a list of additional properties. Similar to the following:
MaterialList
MaterialPiece
AdditionalPropertyList
AdditionalProperties
Each class has a ChildPieces list property containing the list of items in the next level; for example, the MaterialList class has
public List<MaterialPiece> ChildPieces {get;set;}
So an example instance of this might be
WheelSet
Wheel
Wheel Properties
Quantity: 1
Partnumber: R1000
Wheel
Wheel Properties
Quantity: 1
Partnumber: R1000
What I want to do is aggregate the MaterialList to group together the MaterialPiece objects based on some of their properties - the Partnumber in the above example.
This would result in
WheelSet
Wheel
Wheel Properties
Quantity: 2
Partnumber: R1000
I want to do this in the outermost object, i.e. I want to implement
class MaterialList : BasePiece
{
public void AggregateMaterialPieces()
{
var newList = ChildPieces.Where(...)
}
}
So my question: Can I use LINQ to group and sum the MaterialPieces in the MaterialList based on some known values in the AdditionalPropertyList?

Your question isn't quite specific enough but anyway, here's some code that will give you the quantities for each partnumber.
var groupings = list.SelectMany(x => x.AdditionalPropertyList).GroupBy(x => x.PartNumber).Select(g => new { PartNumber=g.Key, Quantity=g.Sum(x => x.Quantity) } );
foreach (var g in groupings)
Console.WriteLine("PartNumer: {0} Total: {1}", g.PartNumber, g.Quantity);
I'm not positive this is correct, if it doesn't work let me know and I'll try to make time to actually test it. The basic idea is to flatten the list to all the additional properties, then you group those by partnumber, then for each of the groupings you create a new anonymous object which uses the partnumber (the group key) and calls aggregate on the list of groupings to get the sum of all the quantities.

Related

Filtering list of objects based on properties and their values

Good evening,
I'm building a basic console application to learn how to filter object list property data. I'm trying to filter a list of objects based on user selected object property and it's value, but I'm struggling to conceptualize how to connect user input to objects property, since properties do not have an index.
For example, if we have a list of cars and user selects to filter by year and enters a specific year we would return those cars.
foreach(var car in listOfCars)
{
if(...)
{
Console.WriteLine(car.Name);
Console.WriteLine(car.Year);
}
}
I can filter the data using wildcards, but how do you connect a user selected input (number) to a property?
Well, it doesn't have to be a number:
Console.WriteLine("Filter by what? You can write YEAR, MAKE or MODEL");
var byWhat = Console.ReadLine();
Console.WriteLine("And what is the search term?");
var term = Console.ReadLine();
List<Car> filtered = new List<Car>();
if(byWhat == "YEAR"){
int whatYear = int.Parse(term);
foreach(var car in cars){
if(car.Year == whatYear)
filtered.Add(car);
}
} else if(byWhat == ...) {
... foreach(...)
}
Feel free to convert that to using numbers if you want, and add some validation of input, case insensitivity.. etc..
The main point here is that you can make a variable that represents the common thing you'll get out; a filtered list of cars. You can do an if this then that, else if other then another.. and each if branch means the list of filtered cars ends up different.. But it's still a list of cars at the end and so by whatever process you did the filtering the common form of output can be treated in the same way.
Note; if you like you can flip it over and put the if inside the foreach - it's not appreciably slower

How do I get properties for each object in the array?

I'm trying to write a card game and I have this question.
I have a List<int> address1Player; which contains a set of cards with parameters.
I need to pull the parameter of each card from this sheet.
For example, List<int> address1Player contains cards with id 1, 2, 3. I need to find out what colors of cards are in List<int> address1Player. Colors are stored in int.
The color getting parameter looks like this
public int PropertyColor(int address){
return allProperties[address].GetComponent<Plot>().GetColor();
}
How do I make sure that I end up with an array with the colors of each card?
A List<int> only contains a list of integers - in your case, IDs. You want to store a data structure of colors (and ostensibly, some other values about the cards), so a List is not the collection you want. First, let's think about the data structure that our collection will hold, then we'll come back to the collection.
Our card in our game has at least two properties: ID and an integer based Color. In C#, we write classes or structs to group up logical bundles of properties into an object. It would look (pretty simply) like this:
public struct KorsunsCard
{
public int Id;
public int CardColor
}
Now, we have a "card" object that has properties that we can check and set like so:
KorsunsCard greenCard = new KorsunsCard() { Id = 1, CardColor = 6 };
greenCard.CardColor = 5; // change the color to "5"
if (greenCard.Id == 2) { /* ... do some stuff ... */ }
Then, we can have methods just return the entire card:
public KorsunsCard GetCardWithID(int Id)
{
KorsunsCard returnCard = // ...
// ... get a card ...
return returnCard;
}
Now, about those collections. Selecting a data structure to use is the heart of C#. Collections are "groups" of objects - in our case, KorsunsCards. Each collection can do different things well - Lists can get access to a card by "index" (not Id), iterate over the whole list, sort themselves, etc. Dictionarys are meant for looking up a card by a key, and while they can iterate over the whole dictionary, they aren't meant for that typically so the syntax is a little more involved. Not difficult, just not as easy as a List. You also might want a HashSet, a collection that can only have one unique item - but is unsorted, like a dictionary.
I can't suggest the best solution, because it depends on your game's rules (will your deck always have the same number of cards? one and only of each kind of card? does a user build their own deck from a pool of available cards?).
Let's start with some cards:
KorsunsCard ace = new KorsunsCard() { Id = 1, Color = 1 };
KorsunsCard deuce = new KorsunsCard() { Id = 2, Color = 2 };
KorsunsCard trey = new KorsunsCard() { Id = 3, Color = 3 };
If you wanted a List, you could declare it and add some values to it like so:
List<KorsunsCard> myDeck = new List<KorsunsCard>();
myDeck.Add(ace);
myDeck.Add(deuce);
myDeck.Add(trey)
int deuceColor = deuce.Color; // deuce's color
return myDeck[0]; // ace, but be aware the list can be shuffled/sorted!
foreach (KorsunsCard kc in myDeck) // iterate on the whole deck
{
kc.Color = 4; // set the entire decks color to 4 , one at a time
}
The generic collection types Dictionary, HashSet, Queue, Stack may all be relevant for your game, depending on how you typically interact with the deck and the game rules. Hopefully I've given you enough from List that you can go and read up on these other collection types and put them to use.

How to order list in custom Item class with linq query

I have two classes
class Variant
{
bool isOrdered;
}
class Item
{
List<Variant> Variants;
}
Then I get IQueryable<Item> from my data source. I wand to order List of variants.
e.g If execute query IQueryable<Item> we get:
{Item: Variants:{true,false,true}, Item: Variants:{false, false, true}, Item: Variants:{true,false,true,false}} and after ordering I need to get {Item: Variants:{true,true, false}, Item: Variants:{true,true,false}, Item: Variants:{true,true,false,false}}
I'm trying something like
var query =
from item in source
from variants in item.Variants
orderby variants.isOrdered
select item;
but instead of ordering variants this query order items and I have no idea how to order variants.
If what you are interested on is merely the variants, you could try:
var selections = source.Select(i => i.Variants.OrderByDescending(v => v.isOrdered));
selections will then be an IEnumerable<IOrderedEnumerable<Variant>> with three enumerations of variants (based on your sample data), ordered in this manner:
True,True,False
True,False,False
True,True,False,False
UPDATE:
OP has updated the question to require the item as well, so...
There are a few ways to go about this. The least OOP-based, least intrusive one would be to grab the item as well as the sorted variant list into an anonymous type:
var selections = source.Select(i => new
{
Item = i,
SortedVariants = i.Variants.OrderByDescending(v => v.isOrdered)
});
In this case, selections will be an IEnumerable<'a> where 'a is the anonymous type. The type will have two properties: the item that the variants belong to as well as a property called SortedVariants.
Then there is the simplest, non-reusable way. Every time you access the variants, sort them:
foreach (var item in source)
{
var variants = item.Variants.OrderByDescending(v => v.isOrdered);
//Do something with the variants
}
A more reusable way is to add another property (or method) to the Variant class that returns the variant list in the desired order:
public class Item
{
public List<Variant> Variants;
public IOrderedEnumerable<Variant> OrderedVariants
{
get { return Variants.OrderByDescending(v => v.isOrdered); }
}
//OR
public IOrderedEnumerable<Variant> GetOrderedVariants()
{
return Variants.OrderByDescending(v => v.isOrdered);
}
}
You can then use that property or method instead.
Lastly, if you have the flexibility to change the current design, you can always hide the list behind an interface and implement a method to add:
public class Item
{
private List<Variant> _variants = new List<Variant>();
public IEnumerable<Variant> Variants
{
get { return _variants.OrderByDescending(v => v.isOrdered); }
}
public void AddVariant(Variant variant)
{
_variants.Add(variant);
}
}
This would my personal favorite since it provides the variants, satisfies the requirement and hides the details of the implementation.
if you only want to sort Variant do this :
var SortedVariantItems= Items.Select(x =>new Item {Variants= x.Variants.OrderByDescending(c => c.isOrdered).ToList()})
and if you want In addition to Variant, Items also be sorted(by Variant) do this:
var SortedItems= Items.Select(x =>new Item {Variants= x.Variants.OrderByDescending(c => c.isOrdered).ToList()}).OrderByDescending(x=>x.Variants.Count(c=>c.isOrdered));
I would suggest something like the below.
var orderedItems = Items.OrderBy(item => item.Variants.Where(v => v.isOrdered).Count());
This orders the items by how many of it's variants are ordered. You could replace whatever you like in the where.

Does Linq OrderBy Not Sort Original Collection?

I am using Linq To Sql as my database layer for the first time and I have run into an issue. Basically, I have a form that allows users to create predefined jobs. A predefined job can have many predefined job items. For example, a predefined job might be something like an oil change. The predefined job items would be oil, labor, etc. In my database PredefinedJobs is a table and PredefinedJobItems is another table with a foreign key back to PredefinedJobs. I have a form for adding predefined jobs that has the Linq-to-Sql class backing the form as a class variable. There is a ListView on the form that displays all of the jobs items. A new feature has required me to track the position of an item in the ListView. For example, if my item ListView looks like below, note the order:
Qty Name Desc ItemOrder
4 Oil Penzoil 0
1 Labor 1
Because the items are added via a child form I do not want to provide access to the ListView. So, I created the method below in an attempt to both create the ItemOrder and sort the collection on the PredefinedJob Linq to Sql object. It does not appear that the OrderBy function on the List actually sorts the collection on the PredefinedJob. What would be the best way to maintain order on the Linq to Sql collection (i.e. PredefinedJob.fkJobItems)? Or, would it be better to just pass a reference to my ListView into the child form that adds the items to the jobs where I have access to the selectedIndex?
private SortAndOrderItems(List<PredefinedJobsItem> items)
{
var nextItemOrderNumber = items.Max(max => max.ItemOrder) + 1;
foreach (var item in items)
{
if (item.ItemOrder == null)
{
item.ItemOrder = nextItemOrderNumber;
nextItemOrderNumber++;
}
}
items.OrderBy(i => i.ItemOrder).ToList();
}
OrderBy creates a new query, that will, when executed, not alter your original list.
Why not just the Sort method of the List?
items.Sort((a, b) => a.ItemOrder.CompareTo(b.ItemOrder));
I think you were looking for List<>.Sort
class Cmp : IComparer<PredefinedJobsItem>
{
public int Compare(PredefinedJobsItem x, PredefinedJobsItem y)
{
return x.ItemOrder.CompareTo(y.ItemOrder);
}
}
var comparison = new Cmp();
items.Sort(comparison);

How to show the grouping data in a DataGrid? - Summary Row

I've got this class:
public enum KindOfPerson
{
Student, Teacher, ...
}
public class Person
{
// This contains only numbers between 0 and 1
public double ScorePercent { get; set; }
public KindOfPerson Type { get; set; }
}
I have a people list and then I apply this LINQ function ot get another list which is classified by KindOfPerson and calculates the average score of all the people belong to that KindOfPerson:
var groupedLists = peopleList.GroupBy(person => person.Type)
.OrderBy(group => group.Key)
.Select(group => new {
People = group.ToList(),
AverageScore = group.Average(p => p.ScorePercent)
})
.ToList();
I'd like t show this list in a DataGrid but I don't have any idea to do it.
I'm trying to do this:
[DATAGRID]
Student AVERAGE SCORE: XXX
SCORE PERCENT 1
SCORE PERCENT 2
Teacher AVERAGE SCORE: YYY
SCORE PERCENT 1
SCORE PERCENT 2
Something like that, grupping by.
UPDATE
I think this code can help us: http://leeontech.wordpress.com/2010/02/01/summary-row-in-datagrid/
Here you find an example on how grouping is done with the DataGrid. Here is another link on how implement grouping.
Principally it's done like in all groupable WPF-controls. However be aware that DataGrid loose the capability of UI virtualization if you use grouping functionality. Therefore it is generally not a
good idea to create huge lists with grouped data.
For the model, I recommend that you create a view-model that is used for each line item. This VM provides all neceessary properties such as ScorePercent1, ScorePercent2 and also the grouping title.
You either need to use a different kind of view, like a TreeView, or you'd need to have a column in your DataGrid that contains yet another DataGrid (or other data container) per row in the DataGrid.
Based on what you're trying to show, it seems like a TreeView is really what you're looking for.
If you're able to use a third part component, I would suggest the RadGrid from Telerik. It has built in grouping/detail capabilities capabilities.
If you need something free, you might check out this link:
http://www.vbknowledgebase.com/?Id=125&Desc=Asp.Net-Hierarchical-GridView

Categories