How do I get total Qty using one linq query? - c#

I have two linq queries, one to get confirmedQty and another one is to get unconfirmedQty.
There is a condition for getting unconfirmedQty. It should be average instead of sum.
result = Sum(confirmedQty) + Avg(unconfirmedQty)
Is there any way to just write one query and get the desired result instead of writing two separate queries?
My Code
class Program
{
static void Main(string[] args)
{
List<Item> items = new List<Item>(new Item[]
{
new Item{ Qty = 100, IsConfirmed=true },
new Item{ Qty = 40, IsConfirmed=false },
new Item{ Qty = 40, IsConfirmed=false },
new Item{ Qty = 40, IsConfirmed=false },
});
int confirmedQty = Convert.ToInt32(items.Where(o => o.IsConfirmed == true).Sum(u => u.Qty));
int unconfirmedQty = Convert.ToInt32(items.Where(o => o.IsConfirmed != true).Average(u => u.Qty));
//Output => Total : 140
Console.WriteLine("Total : " + (confirmedQty + unconfirmedQty));
Console.Read();
}
public class Item
{
public int Qty { get; set; }
public bool IsConfirmed { get; set; }
}
}

Actually accepted answer enumerates your items collection 2N + 1 times and it adds unnecessary complexity to your original solution. If I'd met this piece of code
(from t in items
let confirmedQty = items.Where(o => o.IsConfirmed == true).Sum(u => u.Qty)
let unconfirmedQty = items.Where(o => o.IsConfirmed != true).Average(u => u.Qty)
let total = confirmedQty + unconfirmedQty
select new { tl = total }).FirstOrDefault();
it would take some time to understand what type of data you are projecting items to. Yes, this query is a strange projection. It creates SelectIterator to project each item of sequence, then it create some range variables, which involves iterating items twice, and finally it selects first projected item. Basically you have wrapped your original queries into additional useless query:
items.Select(i => {
var confirmedQty = items.Where(o => o.IsConfirmed).Sum(u => u.Qty);
var unconfirmedQty = items.Where(o => !o.IsConfirmed).Average(u => u.Qty);
var total = confirmedQty + unconfirmedQty;
return new { tl = total };
}).FirstOrDefault();
Intent is hidden deeply in code and you still have same two nested queries. What you can do here? You can simplify your two queries, make them more readable and show your intent clearly:
int confirmedTotal = items.Where(i => i.IsConfirmed).Sum(i => i.Qty);
// NOTE: Average will throw exception if there is no unconfirmed items!
double unconfirmedAverage = items.Where(i => !i.IsConfirmed).Average(i => i.Qty);
int total = confirmedTotal + (int)unconfirmedAverage;
If performance is more important than readability, then you can calculate total in single query (moved to extension method for readability):
public static int Total(this IEnumerable<Item> items)
{
int confirmedTotal = 0;
int unconfirmedTotal = 0;
int unconfirmedCount = 0;
foreach (var item in items)
{
if (item.IsConfirmed)
{
confirmedTotal += item.Qty;
}
else
{
unconfirmedCount++;
unconfirmedTotal += item.Qty;
}
}
if (unconfirmedCount == 0)
return confirmedTotal;
// NOTE: Will not throw if there is no unconfirmed items
return confirmedTotal + unconfirmedTotal / unconfirmedCount;
}
Usage is simple:
items.Total();
BTW Second solution from accepted answer is not correct. It's just a coincidence that it returns correct value, because you have all unconfirmed items with equal Qty. This solution calculates sum instead of average. Solution with grouping will look like:
var total =
items.GroupBy(i => i.IsConfirmed)
.Select(g => g.Key ? g.Sum(i => i.Qty) : (int)g.Average(i => i.Qty))
.Sum();
Here you have grouping items into two groups - confirmed and unconfirmed. Then you calculate either sum or average based on group key, and summary of two group values. This also neither readable nor efficient solution, but it's correct.

Related

Filtering an array of objects to remove the ones that don't have the greatest value of for a property

How can I filter an array of objects to remove the ones that don't have the greatest value for Age grouped by IMCB first.
class Program
{
static void Main(string[] args)
{
Container[] containers = buildContainers();
// how can I get an array of containers that only contains the IMCB with the greatest Age.
// eg: [ {IMCB = "123456", Age = 3, Name = "third"}, {IMCB = "12345", Age = 4, Name = "fourth"} ]
}
static Container[] buildContainers()
{
List<Container> containers = new List<Container>();
containers.Add(new Container() { IMCB = "123456", Age = 1, Name = "first" });
containers.Add(new Container() { IMCB = "123456", Age = 3, Name = "third" });
containers.Add(new Container() { IMCB = "12345", Age = 2, Name = "second" });
containers.Add(new Container() { IMCB = "123456", Age = 2, Name = "second" });
containers.Add(new Container() { IMCB = "12345", Age = 4, Name = "fourth" });
return containers.ToArray();
}
}
class Container
{
public string Name { get; set; }
public string IMCB { get; set; }
public int Age { get; set; }
}
Since you want to select elements in the source array that have a Property value that matches to the maximum value of that Property in a group of elements that have a common value in another Property, you can:
Group the elements in the array specifying the Property that defines a group in this context.
Select elements in each Group where the value of another Property matches the maximum value of the same Property in the Group. Here, SelectMany() is used to flatten the results of the selection into a single sequence, otherwise you'd get an IEnumerable<Container>[] instead of a Container[].
Return an array (to match the source collection type) of the resulting elements.
Extra: the resulting elements may need to be ordered in some way, e.g., by the Property that created the groupings and/or the Property that selects the maximum value in each Group.
// [...]
Container[] containers = buildContainers();
// [...]
var filteredOnMaxAgePerGroup = containers
.GroupBy(cnt => cnt.IMCB)
.SelectMany(grp => grp
.Where(elm => elm.Age == grp.Max(val => val.Age)))
.ToArray();
To order the results by the Grouping Property (IMCB, here), add OrderBy() before ToArray():
.OrderBy(elm => elm.IMCB)
To order by the Property the defines the maximum value (Age, here), add OrderBy() or OrderByDescending() and ThenBy() or ThenByDescending() or a combination of these, depending on what better fits here, before ToArray():
.OrderBy(elm => elm.Age)
// Or in descending order
.OrderByDescending(elm => elm.Age)
// or, to define a sub-order based on the Group name
.OrderBy(elm => elm.Age).ThenBy(elm => elm.IMCB)
// or
.OrderByDescending(elm => elm.Age).ThenBy(elm => elm.IMCB)
The answer of #Jimi works, but shorter code is not always better code. For linq2objects you do not want to calculate maximum value for every item in the group, you better take it out of the loop:
var selection = containers
.GroupBy(cnt => cnt.IMCB)
.SelectMany(grp =>
{
var max = grp.Max(v => v.Age);
return grp.Where(elm => elm.Age == max);
})
.ToArray();
The difference is O(n^2) algorithm vs O(n) and that may be difference of 50 hours of calculation vs 0.5 seconds of calculation.
1 O(n): 0,0000069 seconds O(n^2): 0,0000048 seconds Test: OK
10 O(n): 0,0000211 seconds O(n^2): 0,0000319 seconds Test: OK
100 O(n): 0,0000492 seconds O(n^2): 0,0020465 seconds Test: OK
1000 O(n): 0,0004217 seconds O(n^2): 0,1992285 seconds Test: OK
10000 O(n): 0,0041992 seconds O(n^2): 19,7042282 seconds Test: OK
100000 O(n): 0,0405747 seconds O(n^2): 2012,1564200 seconds Test: OK
1000000 O(n): 0,4202187 seconds O(n^2): did not finish, estimated 200000 seconds
the test code
static void Test(int count)
{
List<Container> containers = new List<Container>();
var tmp = buildContainers();
for (int i = 0; i < count; ++i)
{
containers.AddRange(tmp);
}
Console.Write(count);
var st = new System.Diagnostics.Stopwatch();
st.Start();
var selection = containers
.GroupBy(cnt => cnt.IMCB)
.SelectMany(grp =>
{
var max = grp.Max(v => v.Age);
return grp.Where(elm => elm.Age == max);
})
.ToArray();
Console.Write("\tO(n): " + (st.ElapsedTicks / 10000000.0).ToString("0.0000000") + " seconds");
st = new System.Diagnostics.Stopwatch();
st.Start();
var result = containers
.GroupBy(cnt => cnt.IMCB)
.SelectMany(grp => grp.Where(elm => elm.Age == grp.Max(v => v.Age)))
.ToArray();
st.Stop();
Console.Write("\tO(n^2): " + (st.ElapsedTicks / 10000000.0).ToString("0.0000000") + " seconds");
Console.WriteLine("\tTest: " + (result.SequenceEqual(selection) ? "OK" : "ERROR"));
}
static void Main(string[] args)
{
Test(1);
Test(10);//warmup
Console.Clear();
Test(1);
Test(10);
Test(100);
Test(1000);
Test(10000);
Test(100000);
Test(1000000);
Console.ReadKey();
}
First, figure out what the greatest age is:
var maxAge = containers.Max( x => x.Age );
Then select non-matching items:
var result = containers.Where( x => x.Age < maxAge );

Venn Diagram style grouping in LINQ

Ok. The title might be a little confusing but here is what I am trying to do
I have a series of natural numbers
var series = Enumerable.Range(1, 100)
Now I want to use GroupBy to put numbers into these 3 groups, Prime, Even, Odd
series.Select(number => {
var type = "";
if (MyStaticMethods.IsPrime(number))
{
Type = "prime";
}
else if (number % 2 == 0)
{
type = "Even";
}
else
{
type = "Odd";
}
return new { Type=type, Number = number };
}).GroupBy(n => n.Type);
Now the above query will miss categorizing Prime numbers that are even or odd into both categories and they will just be in 'prime' group. Is there any way for the above select to yield multiple numbers?
I could try something like the following, but it requires an additional flattening of the sequence.
series.Select(number => {
var list = new List<int>();
if (MyStaticMethods.IsPrime(number))
{
list.Add(new { Type="prime", Number = number });
}
if (number % 2 == 0)
{
list.Add(new { Type="even", Number = number });
}
else
{
list.Add(new { Type="odd", Number = number });
}
return list;
})
.SelectMany(n => n)
.GroupBy(n => n.Type);
The above code solves my issue, is there any better way that could make my code look more "functional" ?
You can use linq here, but you'll need to duplicate some values that can exist in different groups. GroupBy only works for disjoint groups so you need a way to distinguish 2 the even number and 2 the prime number. The approach you did is essentially what you need to do, but it could be done a little more efficiently.
You can define a set of categories that can help classify the numbers. You don't necessarily need to define new classes to get this to work, but it helps to keep things clean and organized.
class Category<T>
{
public Category(string name, Predicate<T> predicate)
{
Name = name;
Predicate = predicate;
}
public string Name { get; }
public Predicate<T> Predicate { get; }
}
Then to group the numbers, you'd do this:
var series = Enumerable.Range(1, 100);
var categories = new[]
{
new Category<int>("Prime", i => MyStaticMethods.IsPrime(i)),
new Category<int>("Odd", i => i % 2 != 0),
new Category<int>("Even", i => i % 2 == 0),
};
var grouped =
from i in series
from c in categories
where c.Predicate(i)
group i by c.Name;
This is a good case to use Reactive Extensions, as you will avoid to duplicate values.
In the code below , "series" is parsed only once, because it's a hot source thanks to the Publish().
The actual parsing is done during the "Connect()".
using System.Reactive.Linq;
var list = new List<KeyValuePair<string, int>>();
var series= Observable.Range(1, 100).Publish();
series.Where(e => e % 2 == 0).Subscribe(e=>list.Add(new KeyValuePair<string, int>("Even",e)));
series.Where(e => e % 2 == 1).Subscribe(e => list.Add(new KeyValuePair<string, int>("Odd", e)));
series.Where(e => MyStaticMethods.IsPrime(e) ).Subscribe(e => list.Add(new KeyValuePair<string, int>("Prime", e)));
series.Connect();
var result = list.GroupBy(n => n.Key);

Count and Groupby using linq from sql query

Im trying to create a table that counts all orders and groups them in a table from sql to linq to use in a bar graph with google charts.
Table`
Orders Status
8 Created
3 Delayed
4 Enroute
sql
SELECT Count (OrderID) as 'Orders', order_status FROM [ORDER]
where order_status ='Created'OR order_status= 'Delayed' OR order_status='Enroute'
group by order_status
controller
public ActionResult GetChart()
{
var Orders = db.Order.Select(a => new { a.OrderID, a.order_status })
.GroupBy(a => a.order_status);
return Json(Orders, JsonRequestBehavior.AllowGet);
}
this is not displaying the correct results as the linq seems to be wrong.
can someone please point me in the right direction? I am relatively new to this.
thanks in advance.
This should work:-
var result = db.Order.Where(x => x.order_status == "Created"
|| x.order_status == "Delayed"
|| x.order_status == "Enroute")
.GroupBy(x => x.order_status)
.Select(x => new
{
order_status = x.Key,
Orders = x.Count()
});
Or if you prefer query syntax then:-
var result = from o in db.Order
where o.order_status == "Created" || o.order_status == "Delayed"
|| o.order_status == "Enroute"
group o by o.order_status
select new
{
orderStatus = x.Key,
Counts = x.Count()
};
I think you want to group by Status and count total number of orders in each group (I build a simple console program to demonstrate). I suppose the data is:
Orders Status
8 Created
3 Delayed
4 Enroute
2 Created
1 Delayed
Order.cs
public class Order
{
public Order(int orderId, string status)
{
OrderId = orderId;
Status = status;
}
public int OrderId { get; set; }
public string Status { get; set; }
}
Program.cs
class Program
{
static void Main(string[] args)
{
// Data
var orders = new List<Order>
{
new Order(8, "Created"),
new Order(3, "Delayed"),
new Order(4, "Enroute"),
new Order(2, "Created"),
new Order(1, "Delayed"),
};
// Query
var query = orders
.GroupBy(x => x.Status)
.Select(x => new {Status = x.Key, Total = x.Count()});
// Display
foreach (var item in query)
{
Console.WriteLine(item.Status + ": " + item.Total);
}
Console.ReadLine();
}
}
The one you need to focus in is query. After using GroupBy, you will have a list of groups. For each group, the Key is the criteria to group (here is the Status). Then, we call Count() to get the total number of element in that group.
So, from the program above, the output should be:
Created: 2
Delayed: 2
Enroute: 1

Remove from List<> duplicated items and record duplicated quantity

I have a List<Item>. Item has properties Id,Name and Amount. There are duplicated items in this list. I need to get a new List<Item> which contains only non-duplicated Items and in Item's Amount should be the quantity of how many times it duplicated in first List<Item>. I tried something like
for (int i = 0; i < list.Count; i++)
{
for (int j = 0; j < list.Count; j++)
{
if (list[i].Name == list[j].Name)
{
list.Remove(prod.Components[j]);
list[i].Amount++;
}
}
}
but there are some problems in this loop. My brains are overheated. Please, help.
A simple LINQ query can get you the unique items along with the number of times they appear:
var distinct = list.GroupBy(o => o.Name)
.Select(g => new { Count = g.Count(), Item = g.First() })
.ToList();
Then you can modify each item's Amount to the count of duplicates:
foreach (var row in distinct)
{
row.Item.Amount = row.Count;
}
And finally get back a List<Item> that contains no duplicates and has the correct amounts:
var uniqueItems = distinct.Select(r => r.Item).ToList();
Important: The code above assumes that "duplicate" items are indistinguishable from each other, but nothing else (e.g. it doesn't need Item to have a default constructor). Depending on the particulars it may be possible to write it in an even shorter form.
Additionally, the Amount property looks strange here. Since duplicates do not warrant summation of their amounts, what's the purpose of Item.Amount? I would assume that duplicate items with amount of 2 should result in one item with an amount of 4, but your code does not do that (and mine follows that lead).
Off the top of my head (haven't tested it):
list.GroupBy(x => x.Name)
.Select(x => new Item {
Name = x.Key,
Amount = x.Count()
})
.ToList();
You haven't specified what happens to the Ids, so I've left ignored them.
(Note this creates a new list, rather than modifying the original).
Try something like that:
var groups = from item in items
group item by item.Property
into grouped
select grouped;
var distinct = from g in groups
let item = g.First()
let amount = g.Count()
select new Item {Property = item.Property, Amount = amount};
After that distinct contains IEnumerable<Item> with their amount from original items list.
foreach(var item in list)
{
if(list.Count(e=>e.Id == item.Id && e.Name == item.Name)!=1)
{
list.Remove(item);
}
}
Assuming that you determine duplicates by the first two properties ID and Name.
You can implement an IEqualityComparer<Item> and use that for Enumerable.GroupBy:
var itemAmounts = items.GroupBy(i => i, new Item())
.Select(g => new Item {
ID = g.First().ID,
Name = g.First().Name,
Amount = g.Count()
});
Here's your Item class with a meaningful implementation of IEqualityComparer<Item>:
class Item : IEqualityComparer<Item>
{
public int ID;
public string Name;
public int Amount;
public bool Equals(Item x, Item y)
{
if (x == null || y == null) return false;
bool equals = x.ID == y.ID && x.Name == y.Name;
return equals;
}
public int GetHashCode(Item obj)
{
if (obj == null) return int.MinValue;
int hash = 19;
hash = hash + obj.ID.GetHashCode();
hash = hash + obj.Name.GetHashCode();
return hash;
}
}
You could also override Equals and GetHasdhCode from object, then you don't need a custom comparer at all in GroupBy:
var itemAmounts = items.GroupBy(i => i)
.Select(g => new Item {
ID = g.First().ID,
Name = g.First().Name,
Amount = g.Count()
});
You can use above already available methods:
public override bool Equals(object obj)
{
Item item2 = obj as Item;
if (item2 == null)
return false;
else
return Equals(this, item2);
}
public override int GetHashCode()
{
return GetHashCode(this);
}

Fastest way to select distinct values from list based on two properties

I have a this list:
List<myobject> list= new List<myobject>();
list.Add(new myobject{name="n1",recordNumber=1});
list.Add(new myobject{name="n2",recordNumber=2});
list.Add(new myobject{name="n3",recordNumber=3});
list.Add(new myobject{name="n4",recordNumber=3});
I'm looking for the fastest way to select distinct objects based on recordNumber, but if there is more than one object with same recordNumber(here recordNumber=3), I want to select object base on its name.(the name provided by paramater)
thanks
It looks like you are really after something like:
Dictionary<int, List<myobject>> myDataStructure;
That allows you to quickly retrieve by record number. If the List<myobject> with that dictionary key contains more than one entry, you can then use the name to select the correct one.
Note that if your list is not terribly long, an O(n) check that just scans the list checking for the recordNumber and name may be fast enough, in the sense that other things happening in your program could obscure the list lookup cost. Consider that possibility before over-optimizing lookup times.
Here's the LINQ way of doing this:
Func<IEnumerable<myobject>, string, IEnumerable<myobject>> getDistinct =
(ms, n) =>
ms
.ToLookup(x => x.recordNumber)
.Select(xs => xs.Skip(1).Any()
? xs.Where(x => x.name == n).Take(1)
: xs)
.SelectMany(x => x)
.ToArray();
I just tested this with a 1,000,000 randomly created myobject list and it produced the result in 106ms. That should be fast enough for most situations.
Are you looking for
class Program
{
static void Main(string[] args)
{
List<myobject> list = new List<myobject>();
list.Add(new myobject { name = "n1", recordNumber = 1 });
list.Add(new myobject { name = "n2", recordNumber = 2 });
list.Add(new myobject { name = "n3", recordNumber = 3 });
list.Add(new myobject { name = "n4", recordNumber = 3 });
//Generates Row Number on the fly
var withRowNumbers = list
.Select((x, index) => new
{
Name = x.name,
RecordNumber = x.recordNumber,
RowNumber = index + 1
}).ToList();
//Generates Row Number with Partition by clause
var withRowNumbersPartitionBy = withRowNumbers
.OrderBy(x => x.RowNumber)
.GroupBy(x => x.RecordNumber)
.Select(g => new { g, count = g.Count() })
.SelectMany(t => t.g.Select(b => b)
.Zip(Enumerable.Range(1, t.count), (j, i) => new { Rn = i, j.RecordNumber, j.Name}))
.Where(i=>i.Rn == 1)
.ToList();
//print the result
withRowNumbersPartitionBy.ToList().ForEach(i => Console.WriteLine("Name = {0} RecordNumber = {1}", i.Name, i.RecordNumber));
Console.ReadKey();
}
}
class myobject
{
public int recordNumber { get; set; }
public string name { get; set; }
}
Result:
Name = n1 RecordNumber = 1
Name = n2 RecordNumber = 2
Name = n3 RecordNumber = 3
Are you looking for a method to do this?
List<myobject> list= new List<myobject>();
list.Add(new myobject{name="n1",recordNumber=1});
list.Add(new myobject{name="n2",recordNumber=2});
list.Add(new myobject{name="n3",recordNumber=3});
list.Add(new myobject{name="n4",recordNumber=3});
public myobject Find(int recordNumber, string name)
{
var matches = list.Where(l => l.recordNumber == recordNumber);
if (matches.Count() == 1)
return matches.Single();
else return matches.Single(m => m.name == name);
}
This will - of course - break if there are multiple matches, or zero matches. You need to write your own edge cases and error handling!
If the name and recordNumber combination is guaranteed to be unique then you can always use Hashset.
You can then use RecordNumber and Name to generate the HashCode by using a method described here.
class myobject
{
//override GetHashCode
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + recordNumber.GetHashCode();
hash = hash * 23 + name.GetHashCode();
return hash;
}
}
//override Equals
}

Categories

Resources