0
votes

I spend 2 hours trying to convert one example from Scala lang to Java lang, the example are simple in scala but prove to be more trick in Java than I expected. The main concepts are lambdas, passing functions as parameters, and parallel collections. My question is: How to improve the Java code, there are a few things I would like to do:

  1. first return the result from the function in the measure function, I try use generics with no immediate success (I guess I do something wrong)
  2. A better way to make a list from a sequence, I do not expect something so easy as (0 to 10) but maybe..
  3. A better permutation implementation, some java library. The algorithm in scala is performing 10 times faster, but my is just one copy past from stack overflow as I can't find one natively or library.
  4. Why I need to return null in the lambda expression even from a void expression? In scala I know that everything returns something even if it is Nothing

This is not a comparison between the two languages, well in practice this could be, this is a learning code, from the perspective of a guy who learn coding in Java, then starts coding in Scala for fun and functional learning them back to Java 8 to see what is new. That way, I'm not a expert in Scala and in functional programming.

Let's go to the code

Scala Version:

object ParalelTest {
  def measure[T] (func: => T):T ={
    val start = System.nanoTime()
    val result = func
    val elapsed = System.nanoTime() - start
    println("A execução do metodo demorou %s ns".format(elapsed))
    result
  }
  var sum:Int = _
  def heavyComputation ="abcdefghij".permutations.foreach(i=> sum+=1)

  def main(args: Array[String]) {
    measure(heavyComputation)
    println(s"Soma das permutacoes: ${sum}")
    measure((0 to 10).foreach(i => heavyComputation))
    measure((0 to 10).par.foreach(i => heavyComputation))
    println(s"Soma das permutacoes: ${sum}")
  }


}

Java Version (the best I could in the time)

public class ParallelTest implements Runnable {
    int sum =0;
    private void measure(Callable callable) {
        long start = System.nanoTime();

        try {
            callable.call();
        } catch (Exception e) {
            e.printStackTrace();
        }
        long elapsed = System.nanoTime() -  start;
        System.out.println(String.format("A execução demorou  %s ns",elapsed));

    }

    private Integer heavyComputation(){
       sum = 0;
       permutation("abcdefghij").stream().forEach(i -> sum++);
       return sum;
    }
    public Collection<String> permutation(String str) {
        List<String> permutations = new ArrayList<>();
        permutation("", str, permutations);
        return permutations;
    }

    private void permutation(String prefix, String str, Collection<String> permutations) {

        int n = str.length();
        if (n == 0) permutations.add(prefix);
        else {
            for (int i = 0; i < n; i++)
                permutation(prefix + str.charAt(i), str.substring(0, i) + str.substring(i+1, n), permutations);
        }
    }
    @Override
    public void run() {
        System.out.println("Running one heavycomputations");
        measure(this::heavyComputation);
        // First we need to fill the list (there is no '0 to 10')
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 9; i++) {
            list.add(i);
        }
        // Sequencial
        System.out.println("Running 10 sequencial heavy computations");
        measure(()-> {
            list.stream().forEach(i -> this.heavyComputation());
            return null;
        });

        // Parallel
        System.out.println("Running 10 in parallel");
        measure(() -> {
            list.parallelStream().forEach(i -> this.heavyComputation());
            return null;
        });
    }

}
2
Ignore the fact I'm using implements Runnable, this is only because my main method is separated from the exemples and each example implements Runnable (In scala I just use sbt and the tool figures out each main class in the directory)Giovanni Silva
And don't tell me to just use the scala version :-), I'm trying to learn something here, I can't just drop all the Java away and stop using the good ones (Hibernate, JavaEE, Jackson, CDI etc...). As of the time of writing, use scala in this environment is not full compatible as they say, the first time you need to pass a class reference (Java: Somethig.class | Scala: classOf[Something]) to an annotation or use Enum (quite common) the thing not compile.Giovanni Silva
I'd like to comment on "there is no '1 to 10'" claim. There is in fact. IntStream.rangeClosed(1, 10) does exactly that. Or you can use range(): IntStream.range(0, 10). Other than that, Luciano's answer is great. Unfortunately, Java stdlib is not as comprehensive as Scala one, so we have to rely on third-party libraries for some tasks (e.g. generating permutations).Vladimir Matveev

2 Answers

1
votes

This is a slight variant of Luciano's answer. Regarding your 4 questions:

  • return type: use generics
  • int range: use IntStream.range
  • permutation algorithm: easy to implement on you own, see Heap's algorithm
  • return type: use an overloaded method

Here is the complete code (except for the implementation of the permutations):

public class ParallelTest {
    private static <T> T measure(Supplier<T> func) {
        long start = System.nanoTime();
        T result = func.get();
        long elapsed = System.nanoTime() - start;
        System.out.printf("Execution time %dns\n", elapsed);
        return result;
    }
    private static void measure(Runnable runnable) {
        measure(() -> { runnable.run(); return ""; });
    }
    private static int sum = 0; // Warning: data race
    private static void heavyComputation() {
        Permutations.of("abcdefghij").forEach(i -> ++sum);
    }
    public static void main(String... args) {
        measure(ParallelTest::heavyComputation);
        System.out.printf("Sum: %s\n", sum);
        measure(() -> IntStream.range(0, 10).forEach(i -> heavyComputation()));
        measure(() -> IntStream.range(0, 10).parallel().forEach(i -> heavyComputation()));
        System.out.printf("Sum: %s\n", sum);
    }
}

However, be careful when comparing languages. I recommend using examples, that are based on your daily work. The above example has been made up, and there are a few problems with it. For example:

  • there is a bad data race on the variable sum
  • streams are designed to make light computations on large streams, not heavy computations on small streams
  • the performance is dominated by the implementation of the permutations, a rarely used function
1
votes

This is what I could come up with (using Guava):

import com.google.common.collect.*;
import java.util.function.Supplier;

public class ParalelTest {

  // if the function returns something
  public static <T> T measure(Supplier<T> func) {
    long start = System.nanoTime();
    T result = func.get();
    long elapsed = System.nanoTime() - start;
    System.out.println(String.format("Method execution took %s ns", elapsed));
    return result;
  }

  // if the function doesn't return anything
  public static void measure(Runnable func) {
    long start = System.nanoTime();
    func.run();
    long elapsed = System.nanoTime() - start;
    System.out.println(String.format("Method execution took %s ns", elapsed));
  }

  public static void heavyComputation() {
    Collections2.permutations(Lists.charactersOf("abcdefghij")).stream().forEach(i -> sum += 1);
  }

  public static volatile int sum = 0;

  public static void main(String[] args) {
    measure(ParalelTest::heavyComputation);
    System.out.println("Sum of permutations: " + sum);
    measure(() -> {
        ContiguousSet.create(Range.closed(0, 9), DiscreteDomain.integers()).stream().forEach(i -> heavyComputation());
    });
    measure(() -> {
        ContiguousSet.create(Range.closed(0, 9), DiscreteDomain.integers()).parallelStream().forEach(i -> heavyComputation());
    });
    System.out.println("Sum of permutations: " + sum);
  }

}