Anyone know how I can set a default value for an average? I have a line like this...
dbPlugins = (from p in dbPlugins
select new { Plugin = p, AvgScore = p.DbVersions.Average(x => x.DbRatings.Average(y => y.Score)) })
.OrderByDescending(x => x.AvgScore)
.Select(x => x.Plugin).ToList();
which throws an error becase I have no ratings yet. If I have none I want the average to default to 0. I was thinking this should be an extension method where I could specify what the default value should be.
There is: DefaultIfEmpty.
I 'm not sure about what your DbVersions and DbRatings are and which collection exactly has zero items, but this is the idea:
var emptyCollection = new List<int>();
var average = emptyCollection.DefaultIfEmpty(0).Average();
Update: (repeating what's said in the comments below to increase visibility)
If you find yourself needing to use DefaultIfEmpty on a collection of class type, remember that you can change the LINQ query to project before aggregating. For example:
class Item
{
public int Value { get; set; }
}
var list = new List<Item>();
var avg = list.Average(item => item.Value);
If you don't want to/can not construct a default Item with Value equal to 0, you can project to a collection of ints first and then supply a default:
var avg = list.Select(item => item.Value).DefaultIfEmpty(0).Average();
My advice would to create a reusable solution instead of a solution for this problem only.
Make an extension method AverageOrDefault, similar to FirstOrDefault. See extension methods demystified
public static class MyEnumerableExtensions
{
public static double AverageOrDefault(this IEnumerable<int> source)
{
// TODO: decide what to do if source equals null: exception or return default?
if (source.Any())
return source.Average();
else
return default(int);
}
}
There are 9 overloads of Enumerable.Average, so you'll need to create an AverageOrDefault for double, int?, decimal, etc. They all look similar.
Usage:
// Get the average order total or default per customer
var averageOrderTotalPerCustomer = myDbContext.Customers
.GroupJoin(myDbContext.Orders,
customer => customer.Id,
order => order.CustomerId,
(customer, ordersOfThisCustomer) => new
{
Id = customer.Id,
Name = customer.Name,
AverageOrder = ordersOfThisCustomer.AverageOrDefault(),
});
I don't think there's a way to select default, but how about this query
dbPlugins = (from p in dbPlugins
select new {
Plugin = p, AvgScore =
p.DbVersions.Any(x => x.DbRatings) ?
p.DbVersions.Average(x => x.DbRatings.Average(y => y.Score)) : 0 })
.OrderByDescending(x => x.AvgScore)
.Select(x => x.Plugin).ToList();
Essentially the same as yours, but we first ask if there are any ratings before averaging them. If not, we return 0.
Related
Let's suppose I receive a collection of strings from user. I need to convert them to GUID sequences for further processing. There is a chance, that user may enter invalid data (not correct GUID sequence), so I need to validate input. Additionally, I can run business-process if only all uploaded data are correct GUID values. Here is my code:
IEnumerable<string> userUploadedValues = /* some logic */;
bool canParseUserInputToGuid = userUploadedValues.All(p => Guid.TryParse(p, out var x));
if(canParseUserInputToGuid)
var parsedUserInput = userUploadedValues.Select(p=> Guid.Parse(p));
This logic works pretty well, but I don't like it as actually I am doing work twice. In second line, Guid.TryParse(p, out var x) already writing parsed GUID sequence to the X variable. Is there an approach to combine validating and mapping logic - if sequence elements satisfy for condition (All) then map this elements to a new collection (Select) in one query? It is important for me also in terms of performance, as it is possible that client will send large amount of data (1, 000, 000+ elements) and doing twice work here is a bit inefficient.
You can do something like this in one Select:
var parsedUserInput = userUploadedValues.Select(p => Guid.TryParse(p, out var x) ? x : default)
.Where(p => p != default);
For this one, you need to be sure if there is no Guid.Empty input from the user.
Otherwise, you can return a nullable Guid if parsing doesn't succeed:
var parsedUserInput = userUploadedValues.Select(p => Guid.TryParse(p, out var x) ? x : default(Guid?))
.Where(p => p != null);
Another solution by creating an extension method, for example:
public static class MyExtensions
{
public static Guid? ToGuid(this string arg)
{
Guid? result = null;
if(Guid.TryParse(arg, out Guid guid))
result = guid;
return result;
}
}
and usage:
var parsedUserInput2 = userUploadedValues.Select(p => p.ToGuid())
.Where(p => p != null);
But keep in mind that in this cases, you will have a collection of nullable Guids.
Your out var x variable will be Guid.Empty in the case where it is not a valid Guid. So you can just do this:
IEnumerable<string> userUploadedValues = new[]
{
"guids.."
};
var maybeGuids = userUploadedValues.Select( x => {
Guid.TryParse( x, out var #guid );
return #guid;
} );
if ( maybeGuids.All( x => x != Guid.Empty ) )
{
//all the maybe guids are guids
}
You can optimize the validation and conversion like below,
IEnumerable<string> userUploadedValues = /* some logic */;
var parsedGuids = userUploadedValues.Where(p => Guid.TryParse(p, out var x));
if(userUploadedValues.Count() != parsedGuids.Count())
{
//Some conversion failed,
}
If the count of both the lists same, then you have all the converted GUIDs in the parsedGuids.
Sometimes the non-LINQ method is just easier to read and no longer.
var parsedUserInput = new List<string>();
foreach(var value in userUploadedValues)
{
if (Guid.TryParse(value, out var x)) parsedUserInput.Add(x);
else...
}
I have a list based on some ID or string value I want to change calculation type over group by
var result = from r in mlist
group r by new
{
r.ParameterId,
r.WId,
} into g
select new
{
g.Key.ParameterId,
g.Key.WId,
Value = g.Sum(x => x.Value)
};
I want to replace this linq Sum with a custom method, which will return calculated result based on some calculation type like avg, sum etc.
May be something like this:
var result = from r in mlist
group r by new
{
r.ParameterId,
r.WId,
} into g
select new
{
g.Key.ParameterId,
g.Key.WId,
Value = g.CustomMethod(x => x.Value, x.calctype)
};
You can extend the set of methods that you can use for LINQ queries by adding extension methods to the IEnumerable interface. For example, in addition to the standard average or maximum operations, you can create a custom aggregate method to compute a single value from a sequence of values. You can also create a method that works as a custom filter or a specific data transform for a sequence of values and returns a new sequence.
public static class LINQExtension
{
public static double Median(this IEnumerable<double> source)
{
if (source.Count() == 0)
{
throw new InvalidOperationException("Cannot compute median for an empty set.");
}
var sortedList = from number in source
orderby number
select number;
int itemIndex = (int)sortedList.Count() / 2;
if (sortedList.Count() % 2 == 0)
{
// Even number of items.
return (sortedList.ElementAt(itemIndex) + sortedList.ElementAt(itemIndex - 1)) / 2;
}
else
{
// Odd number of items.
return sortedList.ElementAt(itemIndex);
}
}
}
Source: Add Custom Methods for LINQ Queries
You would also like to view Create the function using Expression linq
I think you should write your own extention method, somethig like this:
public static class MyEnumerable
{
public static int CustomMethod<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector, Func<TSource, int> type)
{
var sum = 0;
source.ToList().ForEach(x => sum += selector(x) * type(x));
return sum;
}
}
And you will execute it this way, your second code listing will not be compiled:
Value = g.CustomMethod(x => x.Value, x => x.calctype)
If you want one calctype for all items you can write this:
Value = g.CustomMethod(x => x.Value, x => 123);
I am confused by the groupby behavior in LINQ to objects. Lets assume for this I have the following class;
public class person
{
public string name { get; set; }
public int age { get; set; }
}
Lets say I have a list or type person; List<person> people
Now, I want to produce an IEnumerable<T> with an IGrouping or anonymous type that has two properties 1) the name (the key) and 2) the sum of all of the ages for people with that name.
Here are a couple of examples of things I've tried (unsuccessfully);
people.GroupBy(x => x.name, x => x, (key, value) => value.Aggregate((c, n) => c + n));
That won't compile with the error cannot convert type "int" to "namespace.person"
Before that I was trying something more along the lines of;
people.GroupBy(x => x.name).Select(g => new { g.Key, g.Aggregate((c, n) => c + n)) } );
Which essentially gives the same error. I'm basically struggling to understand what the value returned by GroupBy really is. At first I thought that basic overload was giving me a key value pair where x.Key was the key I specified with my delegate and x.Value would be an IEnumerable<T> where the typeof T would be the type of x. Of course, if that were the case my second example would work like a charm. My question is somewhat open ended but can someone explain 2 things, firstly; How do I accomplish my end goal with LINQ? And Secondly, why isn't the result of GroupBy more along the lines of what I describe here? What is it? I feel like a key value pair where the value is a collection of objects that match that key is far more intuitive than what is actually returned.
var grouped = people.GroupBy(x => x.name)
.Select(x => new
{
Name = x.Key,
Age = x.Sum(v => v.age),
Result = g.Aggregate(new Int32(), (current, next) => next.age + next.age)
});
If you want you can group the result of that again by Name and it will be a grouping with Key as the name and Age as the value
you can do it with expression syntax
var results = from p in persons
group p.car by p.name into g
select new { name = g.Key, age = g.Sum(c=>.age };
How can I using c# and Linq to get a result from the next list:
var pr = new List<Product>()
{
new Product() {Title="Boots",Color="Red", Price=1},
new Product() {Title="Boots",Color="Green", Price=1},
new Product() {Title="Boots",Color="Black", Price=2},
new Product() {Title="Sword",Color="Gray", Price=2},
new Product() {Title="Sword",Color="Green",Price=2}
};
Result:
{Title="Boots",Color="Red", Price=1},
{Title="Boots",Color="Black", Price=2},
{Title="Sword",Color="Gray", Price=2}
I know that I should use GroupBy or Distinct, but understand how to get what is needed
List<Product> result = pr.GroupBy(g => g.Title, g.Price).ToList(); //not working
List<Product> result = pr.Distinct(...);
Please help
It's groups by needed properties and select:
List<Product> result = pr.GroupBy(g => new { g.Title, g.Price })
.Select(g => g.First())
.ToList();
While a new anonymous type will work, it might make more sense, be more readable, and consumable outside of your method to either create your own type or use a Tuple. (Other times it may simply suffice to use a delimited string: string.Format({0}.{1}, g.Title, g.Price))
List<Product> result = pr.GroupBy(g => new Tuple<string, decimal>(g.Title, g.Price))
.ToList();
List<Product> result = pr.GroupBy(g => new ProductTitlePriceGroupKey(g.Title, g.Price))
.ToList();
As for getting the result set you want, the provided answer suggests just returning the first, and perhaps that's OK for your purposes, but ideally you'd need to provide a means by which Color is aggregated or ignored.
For instance, perhaps you'd rather list the colors included, somehow:
List<Product> result = pr
.GroupBy(g => new Tuple<string, decimal>(g.Title, g.Price))
.Select(x => new Product()
{
Title = x.Key.Item1,
Price = x.Key.Item2,
Color = string.Join(", ", x.Value.Select(y => y.Color) // "Red, Green"
})
.ToList();
In the case of a simple string property for color, it may make sense to simply concatenate them. If you had another entity there, or simply don't want to abstract away that information, perhaps it would be best to have another entity altogether that has a collection of that entity type. For instance, if you were grouping on title and color, you might want to show the average price, or a range of prices, where simply selecting the first of each group would prevent you from doing so.
List<ProductGroup> result = pr
.GroupBy(g => new Tuple<string, decimal>(g.Title, g.Price))
.Select(x => new ProductGroup()
{
Title = x.Key.Item1,
Price = x.Key.Item2,
Colors = x.Value.Select(y => y.Color)
})
.ToList();
If you want to abstract away some of the logic into a reusable extension method, you can add the following:
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (!seenKeys.Contains(keySelector(element)))
{
seenKeys.Add(keySelector(element));
yield return element;
}
}
}
This will work for both single properties and composite properties and return the first
matching element
// distinct by single property
var productsByTitle = animals.DistinctBy(a => a.Title);
// distinct by multiple properties
var productsByTitleAndColor = animals.DistinctBy(a => new { a.Title, a.Color} );
One benefit to this approach (instead of group by + first) is you can return a yieldable enumerable in case you have later criteria that don't force you to loop through the entire collection
Further Reading: linq query to return distinct field values from a list of objects
The error message I receive is:
At least one object must implement IComparable
The code causing this is below:
private static IEnumerable<Result> setOrderBy(IEnumerable<Result> value, string order)
{
if (order.Equals("ASC"))
{
//value = value.OrderBy(c => c, new SearchService.ResultComparer<Attribute>());
value = value.OrderBy<Result>(o => o.StringAttributes.Where(p => p.AttributeName == "Title"), new SearchService.ResultComparer<Attribute>());
//value = value.OrderBy(o => o.StringAttributes.Where(p => p.AttributeName == "Title"), new SearchService.ResultComparer<AttributeItem>()));
}
if (order.Equals("DESC"))
{
value = value.OrderByDescending(c => c, new SearchService.ResultComparer<Attribute>());
//value = value.OrderByDescending(o => o.StringAttributes.Where(p => p.AttributeName == "MatterName"));
}
return value;
}
A little background:
In my MVC2 application, I perform a search in my Search controller. When I send my results to the Results view, I am trying to order the results alphabetically, in ascending or descending order.
However, when I write out the logic to set the OrderBy property for my result object I get the squiggly red line underneath the code (as seen in VS2008). For some reason the method doesn't like the data model I am trying to do a sort upon. Each Result object has various properties, one of which is a list of attributes of type string (hence the name StringAttributes) I am trying to sort each Result object in my IEnumerable collection by the value of one of the String Attributes which is present in ALL of my result records.
Help please!
I think you need to use First() or Single instead of Where() in the place where you are picking out the Attribute to order by. At the moment you are asking OrderBy to calculate order using an IEnumerable<Attribute>, rather than a particular attribute.
value = value.OrderBy<Result>(o => o.StringAttributes.Single(p => p.AttributeName == "Title"), new SearchService.ResultComparer<Attribute>());
or
value = value.OrderBy<Result>(o => o.StringAttributes.First(p => p.AttributeName == "Title"), new SearchService.ResultComparer<Attribute>());