开发者

Reusing code - Java

Is there any way of reusing the iteration through the array code in these functions:

public static double[] ln(double[] z) {
    // returns  an array that consists of the natural logarith开发者_JS百科m of the values in array z
    int count = 0;

    for (double i : z){
        z[count] = Math.log(i);
        count += 1;
    }
    return z;
}


public static double[] inverse(double[] z) {
    //  returns  an array that consists of the inverse of the values in array z
    int count = 0;

    for (double i : z){
        z[count] = Math.pow(i,-1);
        count += 1;
    }
    return z;
}


Yes, using http://en.wikipedia.org/wiki/Strategy_pattern but it'll probably be an overkill. A duplicate iteration and count++ doesn't look so bad.


Your code doesn't match your comments.

public static double[] ln(double[] z) {
    // returns  an array that consists of the natural logarithm of the values in array z
    double[] r = new double[z.length];
    for(int i=0;i<z.length;i++) r[i] = Math.log(z[i]);
    return r;
}

public static double[] inverse(double[] z) {
    //  returns  an array that consists of the inverse of the values in array z
    double[] r = new double[z.length];
    for(int i=0;i<z.length;i++) r[i] = 1/z[i];
    return r;
}

You can use a strategy pattern to make the loop generic but this has three down sides.

  • The code is much harder to read.
  • The code is longer and more complex.
  • The execution is many times slower.

Math.pow(x, -1) is many times more expensive than 1/x and can incurr more rounding error.


Here's a way to reuse the loop. Here's an example:

public interface MathOperation {
   public double f(double value);
}

public class Invert implements MathOperation {
   public double f(double value) {
     return Math.pow(value, -1);
   }
}

public class Log implements MathOperation {
   public double f(double value) {    
     return Math.log(value);
   }
}

private static void calculateOnArray(double[] doubles, MathOperation operation) {
  int count = 0;

  for (double i : doubles){
    doubles[count] = operation.f(i);
    count += 1;
  }
}

public static double[] ln(double[] z) {
  calculateOnArray(z, new Log());
  return z;
}

public static double[] inverse(double[] z) {
  calculateOnArray(z, new Invert());
  return z;
}

Note - this is not the command pattern but not an implementation of the real strategy pattern either. It's close to Strategy, but to avoid further downvotes, I keep the pattern unnamed ;-)


Not so much reusing, but two points here - firstly, never use i as an array value, it's convention that it's the iterator and you'll confuse people no end if you do this. Secondly, you'd be as well using a for loop rather than a for each loop here which will take away your manual "count" variable.

I'd also create a copy of the array before returning it, you're actually changing the parameter being passed in here which, if someone else saw your method signature probably isn't what they'd expect. I'd expect a method with a return type like that to leave the original parameter untouched (since there's no point in returning the same value that I've got a reference to already!)


In languages that support closures, or lambda expressions, or blocks which can be passed (Ruby for example) you could do this concisely. In Java, you could simulate this by defining an interface with a method that is to be called as a callback. In places where you use it, you create an anonymous class using the interface. It looks a bit cumbersome:

public interface Calculation {
    double calculate(double x);
}

public static double[] calcArray(double[] z, Calculcation calc) {
    int count = 0;

    for (double i : z){
        z[count] = calc.calculate(i);
        count += 1;
    }
    return z;
}

public static double[] ln(double[] z) {
    return calcArray(z, new Calculation() {
        double calculate(double x) {
            return Math.log(x);
        }
    });
}

public static double[] inverse(double[] z) {
    return calcArray(z, new Calculation() {
        double calculate(double x) {
            return Math.pow(x, -1);
        }
    });
}


You can reuse this if you utilize functional programming. There are a number of libraries out there that have a lot of this stuff written for you. For this example, I simply used Guava (http://code.google.com/p/guava-libraries/), but libraries such as Functional Java (http://functionaljava.org/) would work just as well.

One of the major benefits of functional programming is the capability to abstract away an algorithm so that you can reuse that algorithm wherever you need. For your case, your algorithm is essentially this:

  1. For each element in a list
  2. Do operation x to that element
  3. Return the new list

The trick is being able to pass in a new operation in place of "x" depending on what you want to do. In the world of functional programming, we create an object called a "functor", which is really just a way to encapsulate a function in an object. (If Java had closures, this would be even easier, but this is what we have.)

Anyway, here's a bit of code that will do what you want:

Class: Inverse


import com.google.common.base.Function;

public class Inverse implements Function
{
    @Override
    public Double apply(Double arg0)
    {
        return Math.pow(arg0, -1);
    }
}

Class: Logarithm


import com.google.common.base.Function;

public class Logarithm implements Function
{
    @Override
    public Double apply(Double arg0)
    {
        return Math.log(arg0);
    }
}

Class: Driver


import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import com.google.common.collect.Collections2;

public class Driver
{
    @Test
    public void testInverse()
    {
        List initialValues = Arrays.asList(new Double[] {1.0, 2.0, 3.0});

        List logValues = new ArrayList(Collections2.transform(initialValues, new Inverse()));

        assertEquals(3, logValues.size());

        assertEquals(Double.valueOf(1.0), logValues.get(0), 0.01);
        assertEquals(Double.valueOf(0.5), logValues.get(1), 0.01);
        assertEquals(Double.valueOf(0.333), logValues.get(2), 0.01);

    }
}

The Driver class just contains a single test case, but it does illustrate the usage of the Functors defined above. You'll notice the use of a method called Collections2.transform in the testInverse method above. You can find the documentation for this method here:

http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Collections2.html#transform%28java.util.Collection,%20com.google.common.base.Function%29

Essentially, what this method does is abstract away the algorithm of stepping over a list and applying some function to each element of that list - all you have to do is apply the function to perform. So, in this case, I pass to that method a list of Doubles and a functor (Inverse). It gives me back a new list that contains the inverse of every value in the initial list. If you would rather do logs of each value, pass a different functor to the transform method.

This type of programming does come with a little bit of a learning curve, but it can be absolutely awesome for the type of code reuse you want to do. And by leveraging existing libraries (like Guava), you can make your code even easier to write. Notice the code I wrote is actually a little easier than what you had written because I don't have to deal with lists/arrays when performing my mathematical functions - I just perform it on one element; the transform function takes care of applying that function across an entire list.


Yes, as a couple of examples here illustrate. But if this is the real problem and not an extreme simplification, creating three classes with inheritance and overloading to save on writing one FOR statement seems to me to be heading in the wrong direction. You're writing twenty lines of code to save on repeating one line of code. What's the gain?

If in real life the process of iterating through the data is much more complicated for some reason -- if it's not just looping through an array but, I don't know, some complex process of looking things up in a database and submitting a web services request and doing a page full of complex calculations to find the next element -- then my answer would be different.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜