How to break an object into chunks based on some property? - c#

public class InvestorMailing
{
public string To { get; set; }
public IEnumerable<string> Attachments { get; set; }
public int AttachmentCount { get; set; }
public long AttachmentSize { get; set; }
}
i have an IList<InvestorMailing> mailingList. if the attachment size is greater than x, then i need to split my object into chunks. is there an easy linq-y way to do this?
Edited:
this is how i'm generating my mailings:
var groupedMailings = mailingList.GroupBy(g => g.GroupBy);
var investorMailings = groupedMailings.Select(
g => new DistinctInvestorMailing
{
Id = g.Select(x => x.Id).FirstOrDefault(),
To = g.Key.Trim(),
From = g.Select(x => x.From).FirstOrDefault(),
FromName = g.Select(x => x.FromName).FirstOrDefault(),
Bcc = g.Select(x => x.Bcc).FirstOrDefault(),
DeliveryCode = g.Select(x => x.DeliveryCode).FirstOrDefault(),
Subject = g.Select(x => x.Subject).FirstOrDefault(),
Body = g.Select(x => x.Body).FirstOrDefault(),
CommentsOnStatus = g.Select(x => x.CommentsOnStatus).FirstOrDefault(),
Attachments = g.Select(x => x.AttachmentPath),
AttachmentCount = g.Select(x => x.AttachmentPath).Count(),
AttachmentSize = g.Sum(x => x.AttachmentSize),
MailType = g.Select(x => x.MessageType).FirstOrDefault()
}
).ToList();

It should be pretty simple to do it with a standard method. Consider this example:
class Foo
{
public Foo(int weight) { Weight = weight; }
public int Weight { get; set; }
}
...
IEnumerable<IList<Foo>> GroupFoosByWeight(IList<Foo> foos, int weightLimit)
{
List<Foo> list = new List<Foo>();
int sumOfWeight = 0;
foreach (Foo foo in foos)
{
if (sumOfWeight + foo.Weight > weightLimit)
{
yield return list;
sumOfWeight = 0;
list.Clear();
}
list.Add(foo);
sumOfWeight += foo.Weight;
}
if (list.Count > 0)
yield return list;
}
...
List<Foo> foos = new List<Foo>()
{
new Foo(15), new Foo(32), new Foo(14), new Foo(19), new Foo(27)
};
foreach (IList<Foo> list in GroupFoosByWeight(foos, 35))
{
Console.WriteLine("{0}\t{1}", list.Count, list.Sum(f => f.Weight));
}
Edit
I worked on it a bit and produced a LINQ version. It doesn't really save much code in this case, but it's a start.
int weightLimit = 35;
int fooGroup = 0;
int totalWeight = 0;
Func<Foo, int> groupIncrementer = f =>
{
if (totalWeight + f.Weight > weightLimit)
{
fooGroup++;
totalWeight = 0;
}
totalWeight += f.Weight;
return fooGroup;
};
var query = from foo in foos
group foo by new { Group = groupIncrementer(foo) }
into g
select g.AsEnumerable();
foreach (IList<Foo> list in query)
{
Console.WriteLine("{0}\t{1}", list.Count, list.Sum(f => f.Weight));
}

Here's a way to do it using some LINQ to find a chunk that has enough space left to add the attachment:
var chunks = new List<List<InvestorMailing>>();
int maxAttachmentsSize = 10;
foreach (InvestorMailing mail in mailingList)
{
var chunkWithSpace = chunks
.Where(list => list.Sum(x => x.AttachmentSize) +
mail.AttachmentSize <= maxAttachmentsSize)
.FirstOrDefault();
if (chunkWithSpace != null)
{
chunkWithSpace.Add(mail);
} else {
chunks.Add(new List<InvestorMailing> { mail });
}
}
The result is stored in chunks.

Yes:
var highs = mailingList.Where(i => i.AttachmentSize > 10000).ToList();
var lows = mailingList.Where(i => i.AttachmentSize <= 10000).ToList();
How do you need to break them apart aside from this?
HTH.

Related

Display count of task for each id

I have page where i display list of tasks (with Jquery DataTable) and i want display Employee name with count of thire tasks. for example:
Smith (2) | John (1) | Thomas (1)
Tables:
AssignName:
Tasks (AssignId as ForeignKey):
Here what i did :
Here i get List of tasks and select some properties:
var Taskslist = db.Tasks.Select(g => new ServicetasksVM.ItemGroup
{
OpgaveServicesId = g.Id,
Opgaver = g.Opgaver,
Opgaveid = g.Id,
Opretteaf = g.Opretteaf,
OpretteDato = g.OpretteDato,
}).AsEnumerable();
Her iterating over the results to get count each id:
foreach (var item in result)
{
var AssignCount = db.AssignName.Where(c => c.Id == item.Assingid)
.GroupBy(x => x.Name)
.Select(b => new ServicetasksVM.ItemGroup { Assingid = b.Count(), EmployeeNames= b.Key });
}
Put all code together (for simplicity i remove unneeded code):
public JsonResult ListofTasks() {
var Taskslist = db.Tasks.Select(g => new ServicetasksVM.ItemGroup
{
OpgaveServicesId = g.Id,
Opgaver = g.Opgaver,
Opgaveid = g.Id,
Opretteaf = g.Opretteaf,
OpretteDato = g.OpretteDato,
}).AsEnumerable();
var result = Taskslist.Skip(start).Take(length).ToList();
foreach (var item in result)
{
var AssignCount = db.AssignName.Where(c => c.Id == item.Assingid)
.GroupBy(x => x.Name)
.Select(b => new ServicetasksVM.ItemGroup { Assingid = b.Count(), EmployeeNames= b.Key });
}
JsonResult json = Json(new { data = result, draw = Request["draw"], recordsTotal = totalrows, recordsFiltered = totalrowsefterfiltering }, JsonRequestBehavior.AllowGet);
json.MaxJsonLength = int.MaxValue;
return json;
ViewModel:
public List<ItemGroup> ItemGroups { get; set; }
public class ItemGroup
{
public ItemGroup()
{
}
public int OpgaveServicesId { get; set; }
public string Opgaver { get; set; }
public string Opretteaf { get; set; }
public DateTime? OpretteDato { get; set; }
public int Opgaveid { get; set; }
public int OpgaveSmartid { get; set; }
public string Opgavestatus { get; set; }
public int? Assingid { get; set; }
public string EmployeeNames { get; set; }
}
In the end when I check the results both Assingid and EmployeeNames are null. Can anyone please help me :)
Scope of AssignCount variable is limited to foreach loop, that is the reason you are getting Assingid and EmployeeNames as a null at the end of program.
To fix this issue, store each AssignCount to the list, so that you can access all counts out of foreach loop
...
//Define list of List of ItemGroup, as AssignCount is of type List<ItemGroup>
List<List<ServicetasksVM.ItemGroup>> itemGroups = new List<List<ServicetasksVM.ItemGroup>>();
foreach (var item in result)
{
var AssignCount = db.AssignName.Where(c => c.Id == item.Assingid)
.GroupBy(x => x.Name)
.Select(b => new ServicetasksVM.ItemGroup { Assingid = b.Count(), EmployeeNames= b.Key })
.ToList(); //Convert To List
itemGroups.Add(AssignCount); //Add each `AssignCount` to itemGroups
}
//Now list of AssignCount(s) is(are) accessible outside foreach loop
...
Your for loop is doing the counting and assigning it to the variable AssignCount which only exists in the scope of the for loop. You need to capture that result in a variable that is outside the scope of the loop to pass that along to your view.
Something like:
var itemGroups = new List<ItemGroup>();
foreach (var item in result)
{
var AssignCount = db.AssignName.Where(c => c.Id == item.Assingid)
.GroupBy(x => x.Name)
.Select(b => new ServicetasksVM.ItemGroup { Assingid = b.Count(), EmployeeNames= b.Key });
itemGroups.Add(AssignCount);
}
variable AssignCount is local in the foreach loop. better create collection outside of the loop and keep on adding to it inside the loop.

Count elements of an array only using Linq?

I have the 3 classes Item, Order and Management.
Order has an array with ordered Items, Management has a List of different Orders. How can I display the number of ordered items for all PurchaseOrders with the given name?
For example: Item1 is ordered 2 times with quantity = 5 and 2 times with quantity = 7, so the total number is 2*5+2*7=24.
I can solve the task, but only Linq should be used without loops, etc.
class MainClass
{
public static void Main(string[] args)
{
Management Management = new Management();
Management.PrintQuantityForSingleItem("Item1");
}
}
class Item
{
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public Item(string Name, decimal Price, int Quantity)
{
this.Name = Name;
this.Price = Price;
this.Quantity = Quantity;
}
}
class Order
{
public int Id { get; set; }
public Item[] Items { get; set; }
public Order(int Id, Item[] Items)
{
this.Id = Id;
this.Items = Items;
}
}
class Management
{
public List<Order> Orders { get; set; }
public Management()
{
Item i1 = new Item("Item1", 2.0M, 5);
Item i2 = new Item("Item2", 3.0M, 6);
Item i3 = new Item("Item1", 2.0M, 7);
Orders = new List<Order>()
{
new Order(1, new Item[]{i1, i2}),
new Order(2, new Item[]{i3}),
new Order(3, new Item[]{i1, i3}),
};
}
//displays the total number of ordered items for all Orders with the given name on the console.
public void PrintQuantityForSingleItem(string itemName)
{
var result = (from x in Orders
select x.Items).ToList();
int counter = 0;
for (int i = 0; i < result.Count(); i++)
{
for (int a = 0; a < result[i].Count(); a++)
{
if (result[i][a].Name == itemName)
{
counter = counter + result[i][a].Quantity;
}
Console.WriteLine(result[i][a].Name);
}
}
Console.WriteLine(itemName + " " + counter);//for example: shows 24 for item1
}
}
You'll need to select all order items, group them by name and then calculate the sum value for the Quantity. The final step is to get the calculated sum value by item name and display it
public void PrintQuantityForSingleItem(string itemName)
{
var results = Orders
.SelectMany(o => o.Items)
.GroupBy(i => i.Name)
.Select(g => new { Item = g.Key, Sum = g.Sum(i => i.Quantity) });
var item = results.FirstOrDefault(r => r.Item.Equals(itemName, StringComparison.OrdinalIgnoreCase));
Console.WriteLine(itemName + " " + item?.Sum);//shows 24 for item1
}
Welcome to SO. It is obviously not advisable to look up items by name, but ignoring aspects of the design, here is one way of doing it:
var result = Orders.SelectMany(i => i.Items)
.Where(i => i.Name == itemName)
.Sum(i => i.Quantity);
Console.WriteLine($"{itemName}: {result}"); //24
Output:
Item1: 24
You can try the following,
public void PrintQuantityForSingleItem(string itemName)
{
var res = Orders.Select(x=>x.Items).Sum(y => y.Where(z => z.Name == itemName).Sum(t => t.Quantity));
Console.WriteLine(res);
Console.ReadKey();
}
It prints 24
You can use Linq :
decimal counter = Orders
.SelectMany(o => o.Items)
.Where(i => i.Name == itemName)
.Sum(i => i.Quantity);
I hope you find this helpful.

How to OrderBy nested Object value Linq

Object
namespace Example
{
public class ContractorAddValue
{
public Member Member { get; set; }
public List<Addresses> Addresses { get; set; }
public ICommand AddAddress { get; set; }
}
public class Addresses
{
public MemberAddress MemberAddress { get; set; }
public ICommand EditAddress { get; set; }
}
}
Query
public ObservableCollection<ContractorAddValue> GetContractorsOrderByCity()
{
var allContractors = (from c in db.Member where c.IsContrator == true select c).ToList();
//var allContractors2 = db.Member .Include(c => c.MemberAddress).SelectMany(c => c.MemberAddress).OrderBy(c => c.City).Select(c => c.Member ).ToList();
//var allContractors = (from c in db.Member where c.IsContrator == true select c).OrderBy(c => c.MemberAddress.OrderBy(x => x.City)).ToList(); <= dosent work
var listContractorAddValue = new ObservableCollection<ContractorAddValue>();
foreach (var i in allContractors)
{
var adressList = db.MemberAddress.Where(x => x.MemberId == i.MemberId).OrderBy(x => x.City).ToList();
ContractorAddValue contractorAddValue = new ContractorAddValue();
contractorAddValue.Member = i;
contractorAddValue.AddAddress = new BaseCommand(() => ContractorsViewModel.SendAddress(i.MemberId ));
contractorAddValue.Addresses = new List<Addresses>();
foreach (var a in adressList)
{
Addresses memberAdress = new Addresses();
memberAdress.MemberAddress = a;
memberAdress.EditAddress = new BaseCommand(() => ContractorsViewModel.SendEditAddress(a.MemberAddressId , i.MemberId ));
contractorAddValue.Addresses.Add(memberAdress);
}
listContractorAddValue.Add(contractorAddValue);
}
return listContractorAddValue;
}
allContractors2 - the order by works, but I retrieve repeating Members. In this approach I tried to use .Distinct() after Select(c => c.Member) but it doesn't work (the whole query stops working).
My goal is to make an order by MemberAddress.City
Thanks in advance!
I think that this code will work but you need to redefine the Equals method of the ContractorAddValue class.
I added one if statement when you want to add contractorAddValue to the list. First you need to check if your list contains that object. If not you add the object to the list. If yes you need to find that object and merge its addresses list with addresses list from the object you want to add.
public ObservableCollection<ContractorAddValue> GetContractorsOrderByCity()
{
var allContractors = (from c in db.Member where c.IsContrator == true select c).ToList();
//var allContractors2 = db.Member .Include(c => c.MemberAddress).SelectMany(c => c.MemberAddress).OrderBy(c => c.City).Select(c => c.Member ).ToList();
//var allContractors = (from c in db.Member where c.IsContrator == true select c).OrderBy(c => c.MemberAddress.OrderBy(x => x.City)).ToList(); <= dosent work
var listContractorAddValue = new ObservableCollection<ContractorAddValue>();
foreach (var i in allContractors)
{
var adressList = db.MemberAddress.Where(x => x.MemberId == i.MemberId).OrderBy(x => x.City).ToList();
ContractorAddValue contractorAddValue = new ContractorAddValue();
contractorAddValue.Member = i;
contractorAddValue.AddAddress = new BaseCommand(() => ContractorsViewModel.SendAddress(i.MemberId ));
contractorAddValue.Addresses = new List<Addresses>();
foreach (var a in adressList)
{
Addresses memberAdress = new Addresses();
memberAdress.MemberAddress = a;
memberAdress.EditAddress = new BaseCommand(() => ContractorsViewModel.SendEditAddress(a.MemberAddressId , i.MemberId ));
contractorAddValue.Addresses.Add(memberAdress);
}
if(!listContractorAddValue.Contains(contractorAddValue)){
listContractorAddValue.Add(contractorAddValue);
} else {
var contAddValue = listContractorAddValue.First(l => l.Equals( contractorAddValue));
contAddValue.Addresses.AddRange(contractorAddValue.Addresses);
}
}
return listContractorAddValue;
}

Dynamic linq OrderBy isn't working for me

I am having trouble dynamically specifying the column and direction to sort on.
I currently have the following code:
if (sort != "OrderID")
{
if (sort == "EmployeeName")
{
sort = "Employee.FirstName"; //sort by Employee FirstName
}
else
{
sort = "Customer." + sort; //Customer.CompanyName sort
}
}
var sortCriteria = string.Format("{0} {1}", sort, sortDir);
var res1 = nwd.Orders //response
.OrderBy(o => sort+" "+sortDir)
.ThenBy(o => o.OrderID)
.Skip((page - 1) * rowsPerPage)
.Take(rowsPerPage)
.Select(o => new
{
o.OrderID,
o.Customer.CompanyName,
o.Customer.ContactName,
o.Employee.FirstName,
o.Employee.LastName,
o.Order_Details
}).ToList();
Any help would be much appreciated!
How about this?
var res1 = nwd.Orders //response
IOrderedQueryable<Orders> result;
if (sort != "OrderID")
{
if (sort == "EmployeeName")
{
result = res1.OrderBy(o => o.Employee.FirstName);
}
else
{
result= res1.OrderBy(o => o.Customer.CompanyName);
}
}
result = result.ThenBy(o => o.OrderID)
.Skip((page - 1) * rowsPerPage)
.Take(rowsPerPage)
.Select(o => new
{
o.OrderID,
o.Customer.CompanyName,
o.Customer.ContactName,
o.Employee.FirstName,
o.Employee.LastName,
o.Order_Details
}).ToList();
Ok, here's a working example with a dummy class:
internal class Program
{
private static void Main(string[] args)
{
var list_people = new List<Person> {
new Person() {Age = 4, Name = "yo"},
new Person() {Age = 5, Name = "a"}
};
var dynamic_propretry = typeof (Person).GetProperty("Name");
var sorted = list_people.OrderBy(person => dynamic_propretry.GetValue(person, null));
foreach (var person in sorted)
{
Console.Out.WriteLine(person);
}
Console.ReadLine();
}
}
public class Person{
public int Age { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name + ":" + Age;
}
}
So, all you have to do is store the property you want in a string, and just use that string where I used "Name". It should work.

GroupBy in lambda expressions

from x in myCollection
group x by x.Id into y
select new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
};
How would you write the above as a lambda expression? I'm stuck on the group into part.
Query continuations (select...into and group...into, but not join...into) are equivalent to just splitting up the query expression. So I like to think of your example as:
var tmp = from x in myCollection
group x by x.Id;
var result = from y in tmp
select new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
};
Change those into dot notation:
var tmp = myCollection.GroupBy(x => x.Id);
var result = tmp.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});
Then you could combine them back:
var tmp = myCollection.GroupBy(x => x.Id)
.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});
Once you work out what the C# compiler does with query expressions, the rest is relatively straightforward :)
myCollection
.GroupBy(x => x.Id)
.Select(x =>
new
{
Id = x.Key,
Quantity = x.Sum(y => x.Quantity
});
myCollection.GroupBy(x => x.Id)
.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});
var mostFrequent =
lstIn.Where(i => !string.IsNullOrEmpty(i))
.GroupBy(s => s)
.OrderByDescending(g => g.Count())
.Select(s => s.Key)
.FirstOrDefault();
So, for most of the answers here, everyone seems to be dealing with getting a simple object of Id made from count of the group, and the Key itself which is group.Key.
Although thats probably the main useage of this. Didn't really satisfy my needs.
For my own case, I basically wanted to group by some object property, then fetch a specific object from that group. Here's a sample code.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var response = new List<ResponseClass>();
var listOfStudents = new List<Student>();
// Insert some objects into listOfStudents object.
listOfStudents.GroupBy(g => g.Class).ToList()
.ForEach(g => response.Add(g.OrderByDescending(s => s.CreatedOn).Select(a =>
new ResponseClass
{
SName = a.StudentName,
SAge = a.Age,
SClass = a.Class,
SCreatedOn = a.CreatedOn,
RandomProperty = Guid.NewGuid().ToString()
})
.First()));
Console.WriteLine("This compiles and should work just fine");
}
class Student
{
public string StudentName { get; set; }
public int Age { get; set; }
public string Class { get; set; }
public DateTime CreatedOn { get; set; }
}
class ResponseClass
{
public string SName { get; set; }
public int SAge { get; set; }
public string SClass { get; set; }
public DateTime SCreatedOn { get; set; }
public string RandomProperty { get; set; }
}
}
If you would rather use a foreach loop (I prefer lambda as I find it easier to read), but if you do, you could do it like so.
foreach (IGrouping<string, Student> groupedStudents in listOfStudents.GroupBy(g => g.Class))
{
response.Add(groupedStudents.OrderByDescending(x => x.CreatedOn).Select(a =>
new ResponseClass
{
SName = a.StudentName,
SAge = a.Age,
SClass = a.Class,
SCreatedOn = a.CreatedOn,
RandomProperty = Guid.NewGuid().ToString()
}).First());
}
Hope this helps someone. :)

Categories