Saturday, August 24, 2013

Two algorithms

Two small code fragments for problems I found interesting.

First, I had a list of criteria that were being used to decide which files in a file list to mark as selected. Some of the criteria were simple, like "none" or "all", but some of them were more complex, like "modified in the last 15 minutes". Since I strongly disliked the idea of a series of ifs (or the equivalent switch statement), I came up with the following:

    var filters = new Dictionary<string, Predicate<FileObject>>
    {
        {"None", file => false},
        {"All (*.*)", file => true},
        {"Invert Selection", file => !file.Selected},
        {"Modified Last 15min", file => file.LastWriteTime.AddMinutes(15) >= DateTime.Now},
        {"Modified Today", file => file.LastWriteTime.Date == DateTime.Now.Date},
        {"*.dll (not Telerik)", file => !file.Name.StartsWith("Telerik") && file.Name.EndsWith(".dll")},
    };

    foreach (var file in fileList)
        file.Selected = filter(file);

Of course, this is rather specific to the exact problem I had, but the idea can be used in the more general case: create one of

    Dictionary<string, Predicate<T>>
    Dictionary<string, Func<T, TResult>>
    Dictionary<string, Action<T>>

and then iterate through the list and get the result / apply the action for each item. This is much easier to understand and modify than a complicated series of ifs, if only because the tendency when modifying the ifs is to add ad-hoc code to solve the particular problem you have right now instead of trying to preserve the code structure.

The second problem I had had to do with formatting a file size to the closest approximation in bytes, KB, MB or GB. For example, a 345 byte file would return "345 bytes", while a 345,000 byte file would say "336.91 KB". (1 KB = 1024 bytes.)

This is again a problem that would normally be solved with a succession of ifs, so once again I looked for something else. (One of the things I've read and that stuck with me was: if possible, move logic out of code and into data.) Here's what I came up with:

    public static string Prettify(this long size)
    {
      var formats = new List<string>
      {
        "#,##0 bytes",
        "#,##0.## KB",
        "#,##0.## MB",
        "#,##0.## GB",
      };

      var limit = 1024L;
      var index = 0;
      while (size >= limit)
      {
        limit *= 1024L;
        index++;
      }

      var format = formats[index];
      return ((double) size / limit * 1024.0).ToString(format);
    }

The reason for the multiplication with 1024 at the end is due to the fact that the limit has already been multiplied once; what I'm doing is basically dividing it back down: size / (limit / 1024), I've just opened the parenthesis.

So, here they are: alternative algorithms for problems normally solved with multiple ifs.

No comments: