I have a class
public class RoomAvail
{
public int OCCUPANCY { get; set; }
public int ChildCount { get; set; }
public string ChildAges { get; set; }
}
I have List of RoomAvail i.e List rooms;
rooms = { new RoomAvail{OCCUPANCY =1,ChildCount =1,ChildAges ="1" }
new RoomAvail{OCCUPANCY =2,ChildCount =2,ChildAges ="1,2" }
new RoomAvail{OCCUPANCY =3,ChildCount =3,ChildAges ="1,2,3" }
}
I have populated the value of rooms.
I have List listAge = {12,13,14,14}
My requirement :
if any OCCUPANCY in the list =2 , I should append the value of listAge in ChildAges .
Final Output:
rooms = { new RoomAvail{OCCUPANCY =1,ChildCount =1,ChildAges ="1" }
new RoomAvail{OCCUPANCY =2,ChildCount =2,ChildAges ="1,2,12,13,14,15" }
new RoomAvail{OCCUPANCY =3,ChildCount =3,ChildAges ="1,2,3" }
}
I want to update rooms variable only.
I am doing:
rooms.Where(y => y.OCCUPANCY == 2)
.Select(x => x.ChildAges)
.Aggregate((i, j) => i + listAge + j);
Thanks
LINQ doesn't modify your object(s). Use a loop for that
foreach(var room in rooms.Where(y => y.OCCUPANCY == 2))
{
room.ChildAges = string.Join(",", room.ChildAges.Split(',').Concat(listAge));
}
You could try this one:
// Get the rooms with OCCUPANCY value equal to 2.
rooms = rooms.Where(x=>x.OCCUPANCY==2);
// Iterate through the selected rooms.
foreach(var room in rooms)
{
// Build a list of integers based on the room's list age.
List<int> currentListAge = room.ChildAges.Split(',').Select(int.Parse).ToList();
// Concat the currentListAge with the listAge, order the resulting list and
// then build a comma separated value list.
room.ChildAges = String.Join(",", currentListAge.Concat(listAge)
.OrderBy(x=>x)
.Select(x=>x.ToString())
.ToList());
}
Related
I have this entities
public class Counter
{
public int DocEntry { get; set; }
public int LineId { get; set; }
public string Item { get; set; }
public decimal Quantity { get; set; }
public string FromWarehouse { get; set; }
public string ToWarehouse { get; set; }
public virtual List<Batch> batchs { get; set; }
}
public class Batch
{
public string BatchNumber { get; set; }
public decimal Quantity { get; set; }
}
And I have List count,I should group the elements of the list based on the Item value, FromWarehouse, ToWarehouse, and the result should be grouped with summed quantity and the list of Batch merged, I tried cycling the Counter list using the foreach method and inserting in a new list the first element, in the subsequent iterations if the current row reflected the values of the one already in the list I summarized the quantities and added to the Batch list the elements of the Batch list of the i-th line, otherwise I added a new line, this approach I it seemed too complicated, not being very expert on linq or in any case is there an easier way to manage the group by?
This is my method:
public static List<Counter> GroupBy(List<Counter> list)
{
List<Counter> rows = new List<Counter>();
int i = 0;
foreach (Counter elem in list)
{
if (list.First() == elem)
{
rows.Add(elem);
i++;
}
else
{
if (elem.Item == rows.ElementAt(i).Item &&
elem.FromWarehouse == rows.ElementAt(i).FromWarehouse &&
elem.ToWarehouse == rows.ElementAt(i).ToWarehouse)
{
rows.First().Quantity += elem.Quantity;
rows.First().batchs.Add(elem.batchs.First());
}
else
{
rows.Add(elem);
i++;
}
}
}
return rows;
}
This is the solution:
public static List<Counter> GroupBy(List<Counter> list)
{
List<Counter> rows = new List<Counter>();
foreach (Counter elem in list)
{
if (rows.Any(x=>x.Item == elem.Item &&
x.FromWarehouse == elem.FromWarehouse &&
x.ToWarehouse == elem.ToWarehouse))
{
rows.First().Quantity += elem.Quantity;
rows.First().batchs.Add(elem.batchs.First());
}
else
{
rows.Add(elem);
}
}
return rows;
}
Let's try to write the Linq, step by step.
"I have List count":
List<Counter> count = ...
"I should group the elements of the list based on the Item value, FromWarehouse, ToWarehouse":
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
"result should be grouped with summed quantity and the list of Batch merged", i.e. you should Aggregate items within each chunk
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
.Select(chunk => new {
key = chunk.Key,
summary = chunk.Aggregate(
Tuple.Create(0m, new List<Batch>()), // initial empty
(s, a) => Tuple.Create(s.Item1 + a.Quantity, // add
s.Item2.Concat(a.batchs).ToList()) // merge
})
Finally, let's represent the result in more convenient (readable) format:
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
.Select(chunk => new {
key = chunk.Key,
summary = chunk.Aggregate(
Tuple.Create(0m, new List<Batch>()),
(s, a) => Tuple.Create(s.Item1 + a.Quantity,
s.Item2.Concat(a.batchs).ToList())
})
.Select(item => new {
Item = item.key.Item,
FromWarehouse = item.key.FromWarehouse,
ToWarehouse = item.key.ToWarehouse,
Quantity = item.summary.Item1,
Batches = item.summary.Item2
}); // Add ToArray() if you want to materialize
I have a Custom class shown below
internal class RecurringClusterModel
{
public int? From { get; set; }
public int? To { get; set; }
public string REC_Cluster_1 { get; set; }
public string REC_Cluster_2 { get; set; }
public string REC_Cluster_3 { get; set; }
public string REC_Cluster_4 { get; set; }
public string REC_Cluster_5 { get; set; }
public string REC_Cluster_6 { get; set; }
public string REC_Cluster_7 { get; set; }
public string REC_Cluster_8 { get; set; }
public string REC_Cluster_9 { get; set; }
public string REC_Cluster_10 { get; set; }
I have a List of this class
List<RecurringClusterModel> recurringRecords = new List<RecurringClusterModel>();
The data can be in the below format
recurringRecords[0].REC_Cluster_1 = "USA";
recurringRecords[0].REC_Cluster_2 = "UK";
recurringRecords[0].REC_Cluster_3 = "India";
recurringRecords[0].REC_Cluster_4 = "France";
recurringRecords[0].REC_Cluster_5 = "China";
recurringRecords[1].REC_Cluster_1 = "France";
recurringRecords[1].REC_Cluster_2 = "Germany";
recurringRecords[1].REC_Cluster_3 = "Canada";
recurringRecords[1].REC_Cluster_4 = "Russia";
recurringRecords[1].REC_Cluster_5 = "India";
....
I want to find the duplicate records between all the Cluster properties..This is just a subset I have 50 properties till REC_Cluster_50. I want to find out which countries are getting duplicated between the 50 cluster properties of the list.
So in this case India and France are getting duplicated. I can group by individual property and then find out the duplicate by getting the count but then I d have to do it for all the 50 Rec_Clusters property. Not sure if there is a better way of doing it.
Thanks
Since you want to capture the From and To, I suggest you structure your class like this:
internal class RecurringClusterModel
{
public int? From { get; set; }
public int? To { get; set; }
public IEnumerable<string> REC_Clusters { get; set; }
}
Then you can search for duplicates:
var dupes = recs
.Select(r => new
{
r.From,
r.To,
DuplicateClusters = r.REC_Clusters.GroupBy(c => c)
.Where(g => g.Count() > 1) // duplicates
.SelectMany(g => g) // flatten it back
.ToArray() // indexed
})
.Where(r => r.DuplicateClusters.Any()) //only interested in clusters with duplicates
.ToArray();
EDIT
If you want all duplicates, then it will be:
var allDupes = recs.SelectMany(r => r.REC_Clusters)
.Select(r => r.GroupBy(c => c)
.Where(g => g.Count() > 1)
.SelectMany(g => g))
.Where(r => r.Any()).ToArray();
But now you lose track of the From/To
I would add an enumerable to your class that iterates over all properties of that class:
internal class RecurringClusterModel
{
public string REC_Cluster_1 { get; set; }
public string REC_Cluster_2 { get; set; }
public string REC_Cluster_3 { get; set; }
public IEnumerable<string> Clusters => GetAllClusters();
private IEnumerable<string> GetAllClusters()
{
if (!string.IsNullOrEmpty(REC_Cluster_1))
yield return REC_Cluster_1;
if (!string.IsNullOrEmpty(REC_Cluster_2))
yield return REC_Cluster_2;
if (!string.IsNullOrEmpty(REC_Cluster_3))
yield return REC_Cluster_3;
}
}
With this you can flatten the list to the individual clusters and then group by. If you need the original object back again, you have to provide it while flattening. Here is an example:
var clusters = Enumerable
.Range(1, 10)
.Select(_ => new RecurringClusterModel
{
REC_Cluster_1 = _Locations[_Random.Next(_Locations.Count)],
REC_Cluster_2 = _Locations[_Random.Next(_Locations.Count)],
REC_Cluster_3 = _Locations[_Random.Next(_Locations.Count)],
})
.ToList();
var dictionary = clusters
// Flatten the list and preserve original object
.SelectMany(model => model.Clusters.Select(cluster => (cluster, model)))
// Group by flattened value and put original object into each group
.GroupBy(node => node.cluster, node => node.model)
// Take only groups with more than one element (duplicates)
.Where(group => group.Skip(1).Any())
// Depending on further processing you could put the groups into a dictionary.
.ToDictionary(group => group.Key, group => group.ToList());
foreach (var cluster in dictionary)
{
Console.WriteLine(cluster.Key);
foreach (var item in cluster.Value)
{
Console.WriteLine(" " + String.Join(", ", item.Clusters));
}
}
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!
In the below code, I have a list of objects Line which represent orders. Because an order can have more than one product, I can group these objects by order number. The below code groups all the line objects by order number and I create an instance of the object called ServiceOrder. ServiceOrder has the order number and I bind all the products for that order to the List<Line>.
In my code, it's a 2 step process, I first group all the Line objects, then I iterate over the group to create a List<ServiceOrder> objects.
Using linq, can this be done with 1 line statement e.g. one step?
Output
Order: A1234
1. Widget 2
2. Sproket 2
3. Wobble 2
Code:
Line o1 = new Line { OrderNumber = "A1234", Description = "Widget", Qty = 2 };
Line o2 = new Line { OrderNumber = "A1234", Description = "Sproket", Qty = 2 };
Line o3 = new Line { OrderNumber = "A1234", Description = "Wobble", Qty = 2 };
Line o4 = new Line { OrderNumber = "A98745", Description = "Cog", Qty = 2 };
List<Line> incomingOrders = new List<Line>();
incomingOrders.Add(o1);
incomingOrders.Add(o2);
incomingOrders.Add(o3);
incomingOrders.Add(o4);
List<ServiceOrder> serviceOrders = new List<ServiceOrder>();
var orderGrouped = incomingOrders.Select((value, index) => new { obj = value, idx = index })
.GroupBy(order => order.obj.OrderNumber).Select(grp => grp.Select(g => g.obj).ToList()).ToList();
foreach (var grp in orderGrouped)
{
ServiceOrder serviceOrder = new ServiceOrder();
var firstOrder = grp.First();
serviceOrder.OrderNumber = firstOrder.OrderNumber;
serviceOrder.Orders = grp;
serviceOrders.Add(serviceOrder);
}
// print out
foreach (var order in serviceOrders) {
System.Diagnostics.Debug.WriteLine("Order: " + order.OrderNumber);
int num = 1;
foreach (var line in order.Orders) {
System.Diagnostics.Debug.WriteLine(String.Format("{0}. {1} {2}", num, line.Description, line.Qty));
num++;
}
}
public class ServiceOrder
{
public List<Line> Orders { get; set; }
public String OrderNumber { get; set; }
}
public class Line {
public int Qty { get; set; }
public String Description { get; set; }
public String OrderNumber { get; set; }
}
Use IGrouping.Key property:
List<ServiceOrder> serviceOrders =
incomingOrders.GroupBy(o => o.OrderNumber)
.ToList(g => new ServiceOrder() {
OrderNumber = g.Key,
Orders = g.ToList() });
PS. Why are you introducing index into your enumeration when you're not using it later at all?
I have the following class:
class Item
{
public decimal TransactionValue { get; set; }
public string TransactionType { get; set; }
}
And I have this list:
var items = new List<Item>
{
new Item
{
TransactionValue = 10,
TransactionType = "Income"
},
new Item
{
TransactionValue = 10,
TransactionType = "Income"
},
new Item
{
TransactionValue = -5,
TransactionType = "Outgoing"
},
new Item
{
TransactionValue = -20,
TransactionType = "Outgoing"
}
};
And I am trying to get the sums based on ValueType, I have tried the below but it is adding everything and giving me one total which is -5, what I want is totals for each transaction type so I want to get a new class which is Totals class below and with this data: TotalIncoming : 20 and TotalOutgoing : - 25.
var r = items.Sum(x => x.TransactionValue);
class Totals
{
public decimal TotalIncoming { get; set; }
public decimal TotalOutgoing { get; set; }
}
Thanks
You can achieve your desired result with following query:-
Totals result = new Totals
{
TotalIncoming = items.Where(x => x.TransactionType == "Income")
.Sum(x => x.TransactionValue),
TotalOutgoing = items.Where(x => x.TransactionType == "Outgoing")
.Sum(x => x.TransactionValue)
};
But, as you can see with your Type Totals, we need to hard-code the TransactionType and we have no clue from the result that this Sum belongs to which type apart from the naming convention used.
I will create the below type instead:-
class ItemTotals
{
public string ItemType { get; set; }
public decimal Total { get; set; }
}
Here we will have the TransactionType along with its corresponding Total in the result, we can simply group by TransactionType & calculate the sum, here is the query for same:-
List<ItemTotals> query = items.GroupBy(x => x.TransactionType)
.Select(x => new ItemTotals
{
ItemType = x.Key,
Total = x.Sum(z => z.TransactionValue)
}).ToList();
Here is the Complete Working Fiddle, you can choose from both.
I'm sure there is a probably a clever way to do this in one line using Linq, but everything I could come up with was quite ugly so I went with something a bit more readable.
var results = items.GroupBy(x => x.TransactionType)
.ToDictionary(x => x.Key, x => x.Sum(y => y.TransactionValue));
var totals = new Totals
{
TotalIncoming = results["Income"],
TotalOutgoing = results["Outgoing"]
};