Linq sum on object w/o group - c#

This seems simple enough but I'm not getting it for some reason.
Given:
public class Foo
{
public List<Bar> Bars { get; private set; }
public Bar Totals { get; private set; }
public Foo()
{
// Blah blah something to populate List of Bars
this.Bars = new List<Bar>()
{
new Bar("Some dude", 50, 1),
new Bar("Some other dude", 60,25)
};
// Calculate Total
var totals = Bars
.GroupBy(gb => gb.CustomerName) // When I comment out line i get "Bar does not contain a definition for "Sum" and no extension...." I want the sum of this without the group.
.Select(s => new
{
Cost = s.Sum(x => x.Cost),
Donation = s.Sum(x => x.Donation),
}
).ToList();
Totals = new Bar("Totals", totals[0].Cost, totals[0].Donation);
}
}
public class Bar
{
public string CustomerName { get; private set; }
public int Cost { get; private set; }
public int Donation { get; private set; }
public int Total { get { return Cost + Donation; } }
public Bar(string customerName, int cost, int donation)
{
this.CustomerName = customerName;
this.Cost = cost;
this.Donation = donation;
}
}
I'm having a few problems here:
-This works with a group by, but if i take out the group by which is my end goal I get "Bar does not contain a definition for "Sum" and no extension....". I want this sum on the entire collection, so do not want a group by.
-I'm creating an anon object before placing into a Bar because I'm not sure how to create a Bar without a parameterless constructor (and I can't add one to this particular class)
-I don't like accessing the "var totals" data using index 0 - should I not be ToListing at the end? If not, how do i access the properties? totals.Cost does not work.
Please help me figure out the proper way to get around my issue (specifically the 3 bullet points above this paragraph). Pretty new to the fluent (and linq in general)syntax and I'm trying to figure out the right way to do it.
EDIT:
thanks for the responses all. Taking kind of a combination of several answers really got me to what my end goal was (but biggest thanks D Stanley)
This is how I'm implementing now:
public Foo()
{
// ....
// Calculate Total
Totals = new Bar("Totals", Bars.Sum(s => s.Cost), Bars.Sum(s => s.Donation));
}
Guess I was just making it more complicated than it needed to be! :O

The s variable in the lambda is of type Bar if you remove the GroupBy. You want it to be List<Bar> instead in your case. So, what I think you want is something like:
var totalCosts = Bars.Sum(x => x.Cost);
var totalDonations = Bars.Sum(x => x.Donation);

but if i take out the group by I get "Bar does not contain a definition for "Sum"
That's because when you take out the GroupBy you're iterating over the individual items instead of a collection of groups. If you want to sum the entire collection use
var totals = new
{
Cost = Bars.Sum(x => x.Cost),
Donation = Bars.Sum(x => x.Donation),
}
;
or if you want a collection with one item, just change your GroupBy:
var totals = Bars
.GroupBy(gb => true) // trivial grouping
.Select(s => new Bar
{
Cost = s.Sum(x => x.Cost),
Donation = s.Sum(x => x.Donation),
}
).ToList();
-I'm casting to an anon object before placing into a Bar because I'm not sure how to cast it in without a parameterless constructor (and I can't add one to this particular class)
Just change your projection to
var totals = new Bar("Totals", Bars.Sum(x => x.Cost), Bars.Sum(x => x.Donation));
I don't like accessing the "var totals" data using index 0 - should I not be ToListing at the end? If not, how do i access the properties? totals.Cost does not work.
If you take out the group by you end up with just one object. If you have a colection with one item you could use First:
Totals = new Bar("Totals", totals.First().Cost, totals.First().Donation);

I want this sum on the entire collection, so do not want a group by.
Then use Sum on the collection
Bars.Sum(x => x.Cost)
I'm casting to an anon object before placing into a Bar because I'm not sure how to cast it in without a parameterless constructor (and I can't add one to this particular class)
You are not casting, you are creating anonymous objects
I don't like accessing the "var totals" data using index 0 - should I not be ToListing at the end? If not, how do i access the properties? totals.Cost does not work.
If you want single result use First.

It's simple enough, when you do Bars.Select(s =>, s is of type Bar and Bar has no definition of Sum. If you want the sum of all of it without any grouping, you can do:
Bars.Sum(b => b.Cost);
Bars.Sum(b => b.Donation);

You only need this :
Totals = new Bar("Totals", Bars.Sum(o => o.Cost), Bars.Sum(o => o.Donation));

Related

C# List.OrderBy with multiple lists

I got 5 lists. One is containing the date of release and the others are the attributes of that list but seperated in multiple lists.
List<string> sortedDateList = x1.OrderBy(x => x).ToList();
This code is sorting the list with the oldest date first, like it should. But I also want to sort (sync) the other attributes list, because they need the same index as the date.
How can I realize that? I'm new to Linq-methods.
You could use the .Zip() method to combine the lists as described here. You could combine them into a class or an anonymous type and then sort them.
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => new { Num = first, Word = second });
var sorted = numbersAndWords.OrderBy(x => x.Num).ToList();
Alternately, if you can guarantee that all the lists are of the same length (or just grab the shortest list) you could use the following instead of the .Zip() extension.
var numbersAndWords = numbers.Select((number, i) => new { Num = number, Word = words[i], Foo = myFoos[i] }); // Where myFoos is another collection.
And in the lambda combine all the items from the separate lists into an object at the same time by accessing the collection by index. (Avoids multiple use of .Zip()) Of course, if you try to access an index that is larger than the list size you will get an IndexOutOfRangeException.
As far as I understand your question, you have different lists containing properties of certain objects. You should definitely look into storing all data into one list of a class of your making, where you consolidate all separate information into one object:
var list = new List<YourClass>
{
new YourClass
{
Date = ...,
OtherProperty = ...,
},
new YourClass
{
Date = ...,
OtherProperty = ...,
},
};
var ordered = list.OrderBy(o => o.Date);
But if you insist in storing different properties each in their own list, then you could to select the dates with their index, then sort that, as explained in C# Sorting list by another list:
var orderedDates = list.Select((n, index) => new { Date = n, Index = index })
.OrderBy(x => x.Date)
.ToList();
Then you can use the indexes of the sorted objects to look up the properties in the other lists, by index, or sort them on index as explained in C# Sort list while also returning the original index positions?, Sorting a list and figuring out the index, and so on.
It almost sounds like you want 1 list of a class.
public class MyClass{
public string Date{get; set;} //DateTime is a better type to use for dates by the way
public string Value2{get; set;}
public string Value3{get; set;}
public string Value4{get; set;}
public string Value5{get; set;}
}
...
var sortedDateList = x1.OrderBy(x => x.Date).ToList()
Create an Object containing the date and attributes:
public class DateWithAttributes
{
public string Date {get;set;}
public Attribute Attribute1 {get;set;}
public Attribute Attribute2 {get;set;}
...
}
List<DateWithAttributes> DateWithAttributesList = new List<DateWithAttributes>()
{
DateWithAttribute1,
DateWithAttribute2
}
List<DateWithAttributes> sortedDateList = DateWithAttributesList.OrderBy(x => x.date).ToList();
If you want to keep the lists separate, and/or create the ordered versions as separate lists, then you can concatenate the index to the dates and sort by dates, then use the sorted indexes:
var orderedIndexedDateOfReleases = dateOfReleases.Select((d, i) => new { d, i }).OrderBy(di => di.d);
var orderedDateOfReleases = orderedIndexedDateOfReleases.Select(di => di.d).ToList();
var orderedMovieNames = orderedIndexedDateOfReleases.Select(di => movieNames[di.i]).ToList();
If you don't mind the result being combined, you can create a class or use an anonymous class, and again sort by the dates:
var orderedTogether = dateOfReleases.Select((d, i) => new { dateOfRelease = d, movieName = movieNames[i] }).OrderBy(g => g.dateOfRelease).ToList();

Creating an anonymous object via LINQ from IObservable?

I've inherited a byzantine API that accesses remote data via JSON, the queries themselves are dealt with via TPL which for reasons I won't go into is backing me into a corner.
So, I have an IObservable subscription, which I query as thus;
mcSub.Take(1).Subscribe(x => x.Markets.ForEach(i => i.Prices.AvailableToBuy.ForEach(t => tabPanel.textBox1.AppendText(i.Id + " Back \t" + t.Size + " # " + t.Price))));
Obviously Markets and AvailableToBuy are lists, my question is - how can I create a LINQ query/object to extract i.Id, t.Size and t.Price?
I believe you can do it like this, by flattening twice (once the markets, and then the prices) in one collection with all prices:
//get all prices available to buy:
var pricesAvailableToBuy = mcSub.SelectMany(x => x.Markets)
.SelectMany(y => y.Prices.AvailableToBuy)
.Select(p => new { p.Id, p.Size, p.Price });
This gives you all prices for all markets in mcSub, while your initial statement works only on one market item (which also depends how it's called - if it's on a consumer worker task/thread then it makes sense).
Or, a similar formulation:
var pricesAvailableToBuy = mcSub.SelectMany(x => x.Markets.SelectMany(y => y.Prices.AvailableToBuy))
.Select(p => new { p.Id, p.Size, p.Price });
Woof.
That is one nasty honking subscribe/query blob. Let's see if we can clean that up somewhat...I see you're taking 1 from mcSub and subscribing to it, so I'll assume it's an enumerable of observables?
Let's first fake up some types I can refer to (I'll do my best to infer from your sample):
First, the data items:
public class Thing
{
public List<Market> Markets {get; set;}
}
public class Market
{
public Price Prices {get; set;}
}
public class Price
{
public List<AvailablePrice> AvailableToBuy {get; set;}
}
public class AvailablePrice
{
public string Id {get; set;}
public int Size {get; set;}
public decimal Price {get;set;}
}
Next, the thing that will generate an IEnumerable<IObservable<Thing>>, which I will ham-handedly hack together:
public IEnumerable<IObservable<Thing>> GetThingsObs()
{
var rnd = new Random();
return Enumerable.Range(0, 3).Select(_ => {
return Observable.Create<Thing>(obs =>
{
var things = Enumerable.Range(0, 3).Select(i => new Thing()
{
Markets = Enumerable.Range(0, 3).Select<int, Market>(x =>
{
return new Market()
{
Prices = new Price
{
AvailableToBuy = Enumerable.Range(0, 3)
.Select(y => new AvailablePrice { Id = string.Format("{0}:{1}:{2}", i, x, y), Size = rnd.Next(0, 10), Price = rnd.Next(0, 20) })
.ToList()
}
};
}).ToList()
});
foreach(var thing in things)
obs.OnNext(thing);
// this bit is important, but I'll get back to it later
obs.OnCompleted();
return Disposable.Empty;
});
});
}
Ok, now that we've got something that'll (I hope) map somewhat to your data, let's query!
IF the observable is known to complete (the bit above I glossed over), you can transform an IObservable<T> into an IEnumerable<T> reasonably safely - If it does not complete, this will basically hang, so be careful!
var mcSub = GetThingsObs();
var query =
// for each observable
from obs in mcSub
// this replaces your subscribe calls, but the observable MUST complete
// for this to return properly!
from thing in obs.ToEnumerable()
// After that, it's just multiple SelectManys
// (which is what the nested 'from x in y' calls get translated into)
// For each market
from market in thing.Markets
// for each price thingy
from price in market.Prices.AvailableToBuy
// select out your stuff
select new { price.Id, price.Size, price.Price };
That's the best guess I can make from the sample you've provided - if you can provide any more information/details, I'll take another shot at it if this doesn't work for ya.

How can I sort the file txt line 5000000?

i've got a disordered file with 500000 line which its information and date are like the following :
for instance desired Result
------------ ---------------
723,80 1,4
14,50 1,5
723,2 10,8
1,5 14,50
10,8 723,2
1,4 723,80
Now how can i implement such a thing ?
I've tried the sortedList and sorteddictionary methods but there is no way for implemeting a new value in the list because there are some repetative values in the list.
I'd appreciate it if u suggest the best possible method .
One more thing , i've seen this question but this one uses the class while i go with File!
C# List<> Sort by x then y
var result = File.ReadAllLines("...filepath...")
.Select(line => line.Split(','))
.Select(parts => new
{
V1 = int.Parse(parts[0]),
V2 = int.Parse(parts[1])
})
.OrderBy(v => v.V1)
.ThenBy(v => v.V2)
.ToList();
Duplicates will be handled properly by default. If you want to remove them, add .Distinct() somewhere, for example after ReadAllLines.
You need to parse the file into an object defined by a class. Once it's in the object, you can start to sort it.
public class myObject
{
public int x { get; set; }
public int y { get; set; }
}
Now once you get the file parsed into a list of objects, you should be able to do something like the following:
var myList = new List<myObject>(); //obviously, you should have parsed the file into the list.
var sortedList = myList.OrderBy(l => l.x).ThenBy(l => l.y).ToList();
First, sort each row so that they are in the correct order (e.g [723,80] - > [80,723]
Then sort all rows using a comparison something like this:
int Compare(Tuple<int,int> lhs, Tuple<int,int> rhs)
{
int res = lhs.Item1.CompareTo(rhs.Item1)
if(res == 0) res=lhs.Item2.CompareTo(rhs.Item2);
return res;
}

Search a List of Objects based on List of Keywords

I have a List of this class:
class Stop
{
public int ID { get; set; }
public string Name { get; set; }
}
and I want to search through all the stop names in the List matching all the keywords of search list and returning the matched subset.
List<string> searchWords = new string { "words1", "word2", "words3" ...}
Here is my try but I am not really sure I am on the right track
var l = Stops.Select((stop, index) => new { stop, index })
.Where(x => SearchWords.All(sw => x.stop.Name.Contains(sw)));
Here is an example that might make it clearer, Say I have stop with a name "Dundas at Richmond NB" and the user types in "dun", "rich" this should match and return the correct stop.
var l = Stops.Where(s => searchWords.Contains(s.Name)).ToList();
It will return List<Stop> will only these stops, which have coresponding string within searchWords collection.
To make it perform better you should consider changing searchWords to HashSet<string> first. Contains method is O(1) on HashSet<T> and O(n) on List<T>.
var searchWordsSet = new HashSet<string>(searchWords);
var l = Stops.Where(s => searchWordsSet.Contains(s.Name)).ToList();
UPDATE
Because of OP update, here is a version, which requires all items from searchWords exists in Stop.Name to return that particular Stop instance:
var l = Stops.Where(s => searchWords.All(w => s.Name.Contains(w)).ToList();

Average extension method in Linq for default value

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.

Categories

Resources