Recipe9.16.Performing Multiple Operations on a List Using Functors


Recipe 9.16. Performing Multiple Operations on a List Using Functors

Problem

You want to be able to perform multiple operations on an entire collection of objects at once, while keeping the operations functionally segmented.

Solution

Use a functor (or function object as it is also known) as the vehicle for transforming the collection. A functor is any object that can be called as a function. Examples of this are a function, a function pointer, or even an object that defines operator () for us C/C++ converts.

Needing to perform multiple operations on a collection is a reasonably common thing in software. Let's say that you have a stock portfolio with a bunch of stocks in it. Your StockPortfolio class would have a List of Stock objects. It would be able to be created with an initial capacity, and it would have the ability to add stocks.

 public class StockPortfolio {     List<Stock> _stocks;          public StockPortfolio(int capacity)     {         _stocks = new List<Stock>(capacity);     }     public void AddStock(string ticker, double gainLoss)     {         _stocks.Add(new Stock(ticker, gainLoss));     }     More methods down here… } 

The Stock class is rather simple. You just need a ticker symbol for the stock and its percentage of gain or loss:

 public class Stock {     string _tickerSymbol;     double _gainLoss;          public Stock(string ticker, double gainLoss)     {         _tickerSymbol = ticker;         _gainLoss = gainLoss;     }     public double GainLoss   {get { return _gainLoss; } }     public string Ticker       {get { return _tickerSymbol; }} } 

To use this StockPortfolio, you add a few stocks to it with gain/loss percentages and print out your starting portfolio. Once you have the portfolio, you want to get a list of the three best performing stocks, so you can cash in by selling them, and print out your portfolio again.

 StockPortfolio tech = new StockPortfolio(10); tech.AddStock("OU81", -10.5); tech.AddStock("C#4VR", 2.0); tech.AddStock("PCKD", 12.3); tech.AddStock("BTML", 0.5); tech.AddStock("NOVB", -35.2); tech.AddStock("MGDCD", 15.7); tech.AddStock("GNRCS", 4.0); tech.AddStock("FNCTR", 9.16); tech.AddStock("ANYMS", 9.12); tech.AddStock("PCLS", 6.11); tech.PrintPortfolio("Starting Portfolio"); // Cash in by selling the best performers. List<Stock> best = tech.GetBestPerformers(3); tech.SellStocks(best); tech.PrintPortfolio("After Selling Best 3 Performers"); 

So far nothing terribly interesting is happening. Let's take a look at how you figured out what the three best performers were by looking at the internals of the GetBestPerformers method:

 public List<Stock> GetWorstPerformers(int bottomNumber) {     int foundItems = 0;          // Sort the stocks by performance using a binary functor.     _stocks.Sort(delegate(Stock lhs, Stock rhs)     {         // Reverse parameters to sort highest to lowest.         return Comparer<double>.Default.Compare(rhs.GainLoss, lhs.GainLoss);      });      // Return stock that match criteria using a unary functor.      return _stocks.FindAll(delegate(Stock s)      {          // If we have accepted no more than how many were asked for          // then keep accepting.          if (foundItems < bottomNumber)          {              string result = "gain";              if (s.GainLoss < 0)                  result = "loss";              Console.WriteLine(                  string.Format("Best stock added ({0}) with {1} of {2}%",                  s.Ticker, result, System.Math.Abs(s.GainLoss)));              // Increment count.              foundItems++;              return true;          }          else // Have all we need          return false;     }); } 

The first thing you do is make sure the list is sorted so that the best performing stocks are at the front of the list by calling the Sort method on List<T>. One of the overloads of the Sort method is defined to take a Comparison<T> which is defined like this:

 public delegate int Comparison<T>(T x, T y) 

You create the Comparison you need by using the Comparer<T> class passing a double as the argument and using the default comparison method, while passing the gain/ loss percentage for each stock as it is iterated over by the Sort function. The stocks being compared (rhs, lhs) are reversed to place the best performing stocks at the front of the list:

 return Comparer<double>.Default.Compare(rhs.GainLoss, lhs.GainLoss); 

Your comparison tells Sort which object should be placed ahead of the other. This is a simple callback from Sort that allows you to control the algorithm to determine sort order. You wrapped this up in an anonymous method by using the delegate keyword and now you have your Comparison delegate that the Sort function needs.

Now that your list is sorted, you use the FindAll function from List<T>, which takes a Predicate<T> as the parameter. Again you use the delegate keyword to create an anonymous method to determine if the item FindAll passes to the anonymous method should be returned in the list that FindAll is building. In your functor, you take the first n items in the list (n is the total number of stocks performing the best) represented by bottomNumber, and once you have taken that many, you return false to FindAll so that no more items are returned.

GetBestPerformers returns a List<Stock> full of the three best performers. As they are making money, it is time to cash in and sell them. For your purposes, selling is simply removing them from the list of stocks in StockPortfolio. To accomplish this, you use yet another functor to iterate over the list of stocks handed to the SellStocks function (the list of worst-performing ones in your case) and then remove that stock from the internal list that the StockPortfolio class maintains:

 public void SellStocks(List<Stock> stocks) {     stocks.ForEach(delegate(Stock s)     {         _stocks.Remove(s);     }); } 

Discussion

Functors come in a few different flavors that are known as a generator (a function with no parameters), a unary function (a function with one parameter), and a binary function (a function with two parameters). Before you ask, yes, you could keep going, but the STL (Standard Template Library from C++) didn't bother so we won't either at this point. If the functor happens to return a Boolean value, then it gets an even more special naming convention: a unary function that returns a Boolean is called a predicate, and a binary function with a Boolean return is called a binary predicate. You will now notice in the Framework that there are both Predicate<T> and BinaryPredicate<T> delegates defined to facilitate these uses of functors.

The List<T> and System.Array classes have been enhanced in the 2.0 version of the .NET Framework to take predicates (Predicate<T>, BinaryPredicate<T>), actions (Action<T>), comparisons (Comparison<T>), and converters (Converter<T,U>). This allows these collections to be operated on in a much more general way than was previously possible and brings some of the richness of the C++ STL to C#.

Thinking in terms of functors can be a bit of a challenge at first, but once you put a bit of time into it, you can start to see powerful possibilities open up before you. Any code you can write once, debug once, and use many times is a useful thing, and functors can help you get to that place.

See Also

See the "System.Collections.Generic.List<T>" and "System.Array" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net