Tuesday, February 14, 2012

mired in Java

So, while this weekend was largely consumed with The MegaMonster Enduro, my quest to become a Java master continues.

The Megamonster went okay. It's a long trip down to Paicines: I am in Howard's debt for the ride. And since I have the Low-Key "stuff", it made for a full car. I didn't really help much while there: this year I rode. I did the 100-miler for the first time in 3 years, the preceding two not having the fitness for it. I felt fairly good, but I was slow, taking 5:35 for the course. It's not easy: wind, hills, and this year light rain. I don't think the rain was much of a factor but the wind always is.

Anyway, the big deal is doing the results after. I always have a hard time with Megamonster results: for each rider we have three checkpoint times, a start time, and a finish time. I'm prone to making typos, and my scoring scripts only get used once per year, so it's never as smooth as I want. I think things are in fairly good shape, however.

Anyway, that took a big chunk out of my goal for getting this Android project done by end of Feb. It's not looking good at this point, yet I forge onward.

Basically tasks which which I'm fluent in Perl I have to struggle with in Java. Sure, you can use only static methods and write a Java which is a fairly clean map of Perl code, but that's not the point. I want to embrace the Java model.

But even for simple tasks, sometimes it takes a bit of digging. For example, the simple matter of generating a key-value list and printing the result.

Here's Perl... I can write something like this almost in my sleep... I'm writing the following without even checking if it works:

#! /usr/bin/perl
use strict;
my %h =
  (
    a => 1,
    b => 2,
    c => 3,
    d => 4,
    e => 5,
  );
for my $k ( sort { $a cmp $b } ( keys %h ) ) {
  print "key: $k = $h{$k}\n";
}

How hard was that? (Okay: I cheated.. I missed a ")" first time I wrote it).

So onto Java...

As an aside, the default Java mode for emacs uses rather generous 4-column tab stops, which isn't at all good for this Blogger 400 pixel wide format. Ah, well. Even with a small font things are going to wrap.

import java.util.*;

class HashMapTest {
    public static void main(String[] args) {
        HashMap h = new HashMap(5);

        h.put("e", "5");
        h.put("a", "1");
        h.put("d", "4");
        h.put("c", "3");
        h.put("b", "2");

        System.out.println("using iterator:");
        Iterator i = h.entrySet().iterator();
        while (i.hasNext()) {
            Map.Entry m = (Map.Entry)i.next();
            System.out.println(m.getKey() + ": " + m.getValue());
        }

        // produce a sorted list of results
        System.out.println("sorted keys:");
        Object[] keys = h.keySet().toArray();
        Arrays.sort(keys);
        for (Object k: keys) {
            System.out.println("key: " + k.toString() + " = " + h.get(k));
        }
    }
}

Here two methods are used to step through the keys. One is with an Iterator, part of the Set class, which is returned by the entrySet() method of the HashMap class, which is a subclass of the Map class. At least I think I got that correct...

I wasn't sure how to work with the iterator to produce a result sorted by key, so I bailed out and used the toArray() method to give me something I knew how to sort. Iterators are still a bit hazy to me. I've seen them but I've never used them on my own.

So anyway, I forge onward. Hopefully I don't make a total mess of things.

I have managed to write code to smooth time-series data. That's a key component of doing anything analytic with ride data. I've done several blog posts on the topic: I really like the "biexponential" smoothing algorithm in which data are first convolved with a forward exponential smoothing function, then with a reverse one, then the two results averaged yielding a symmetric smoothing (no lag or lead). Since the convolution function is continuous, the Fourier transform is fairly clean (unlike a square smoothing filter). There's a bit of care taken here to deal with nonuniformly spaced data, for example as one might get from Garmin Edge "smart sampling".

private static double[] smoothArray(double[] x, double[] y, double tau ) {
  double[] ys = new double[y.length];

  if (ys.length == 0)
    return ys;

  // if no smoothing is requested, simply copy over numbers
  if (tau == 0)
    for ( int i = 0;  i < x.length; i++ ) {
      ys[i] = y[i];
    }

  // otherwise run filter in both positive or negative directions
  else  {
    int[] directions = {-1, 1};
    for (int d: directions) {
      System.err.println("direction = " + d);

      double xold = 0;
      double yold = 0;
      double ysold = 0;

      // along each direction, we run an exponential convolution
      for ( int i = 0;  i < x.length; i++ ) {

        /* if this is the negative direction, use points starting
           from the last, otherwise go in forward order.
           So i is an initial counter but n is the point we're going
           to process
        */
        int n = (d == 1) ? i : x.length - 1 - i;


        /* for either the first point in the sequence or if there is
           a gap much larger than the smoothing constant, use the unsmoothed
           point
        */
        if ((i == 0) || (Math.abs(x[n] - x[n - d]) > 100 * tau)) {
          ys[n] = y[n];
          xold  = x[n];
          yold  = y[n];
          ysold = y[n];
        }
        /* calculate the proper contribution of the new point with a running
           average of old points
        */
        else {

          /* u is a normalized difference between this point and the preceding one */
          double u = Math.abs(x[n] - xold) / tau;

          /* apply the exponential decay to z */
          double z  = Math.exp(-u);

          /* dy is the difference between the present point and the previous point */
          double dy = y[n] - yold;

          /* naive approach: doesn't work for non-uniform point spacing */
          // ys[n] = ysold * z + (1 - z) * y;

          /* proper approach: works with non-uniform point spacing */
          double ysdir =
            (u == 0) ?
            ysold :
            ysold * z + y[n] * (1 - z) + (dy / u) * ((u + 1) * z - 1);

          /* update ys */
          ys[n] =
            (d == -1) ?
            ysdir :
            (ys[n] + ysdir) / 2;
               

          /* update "previous" points before going to next point */
          xold = x[n];
          yold = y[n];
          ysold = ysdir;
        }
      }
    }
  }

  return ys;
}
Nothing objecty about that... I realized I'm not going to really get this stuff using only the Oracle tutorials, so I just ordered the O'Rielly book, Learning Java. It's huge. Ordering books is easy: reading them is the hard part. But I feel if I want to really learn this stuff I've got to stick with it.

No comments: