Вычисление нескольких результатов из одной итерации массива

#java #functional-programming #performance #metaprogramming #iteration

#java #функциональное программирование #Производительность #метапрограммирование #итерация

Вопрос:

Я хочу вычислить несколько функций за одну итерацию массива или списка, но, возможно, захочется динамически расширять список функций. Например, я мог бы захотеть вычислить min и max, а затем также захотеть среднюю функцию (или любую другую функцию линейной сложности) когда-нибудь позже. Итак, я мог бы захотеть иметь высокоуровневую спецификацию (используя некоторые вызываемые функции addFunc и applyAllFuncs , такие как

 funcs = emptyFunctionList;    # no functions in funcs "list" yet
funcs = addFunc (min, funcs);  # add minimum function
funcs1 = addFunc (max, funcs);  # add maximum function 

answers1 = applyAllFuncs (funcs1, myArray);     

funcs2 = addFunc (avg, funcs);  # add average function 

answers2 = applyAllFuncs (funcs2, myArray); 
  

Я могу довольно легко сделать это на функциональном языке, создав «суперфункцию» из этих функций, которая передается в foldl , и метапрограммирование может быть механизмом, позволяющим также выполнять это гораздо эффективнее в Haskell / C , но хотел бы знать, существует ли эффективный и не очень сложный способ, которым это обычно делается (или может быть сделано) в стандартной Java (без необходимости много заниматься имитацией / реализацией функций более высокого уровня).

Ответ №1:

Вот полный рабочий Java-код, эквивалентный функциональному псевдокоду, который вы указали в своем сообщении:

 import java.util.*;

interface Function1<A, B> {
  public B apply(final A a);
}

class Main {
  public static <A, B> List<Function1<A, B>> addFunc(final Function1<A, B> f, final List<Function1<A, B>> fs) {
    final List<Function1<A, B>> gs = new ArrayList<Function1<A, B>>();
    gs.addAll(fs);
    gs.add(f);
    return gs;
  }

  public static <A, B> List<B> applyAllFuncs(final List<Function1<List<A>, B>> fs, final List<A> as) {
    final List<B> bs = new ArrayList<B>();
    for(final Function1<List<A>, B> f : fs) {
      bs.add(f.apply(as));
    }
    return bs;
  }

  public static Function1<List<Double>, Double> min = new Function1<List<Double>, Double>() {
    public Double apply(final List<Double> xs) {
      double mx = xs.get(0);
      for(final Double x : xs) {
        if(x < mx) {
          mx = x;
        }
      }
      return mx;
    }
  };

  public static Function1<List<Double>, Double> avg = new Function1<List<Double>, Double>() {
    public Double apply(final List<Double> xs) {
      double sum = 0;
      for(final Double x : xs) {
        sum  = x;
      }
      return sum / xs.size();
    }
  };

  public static Function1<List<Double>, Double> max = new Function1<List<Double>, Double>() {
    public Double apply(final List<Double> xs) {
      double mx = xs.get(0);
      for(final Double x : xs) {
        if(x > mx) {
          mx = x;
        }
      }
      return mx;
    }
  };

  public static void main(final String[] args) {
    final List<Double> myArray = Arrays.asList(3.0, 8, 1, 2, 9);
    List<Function1<List<Double>, Double>> funcs = new ArrayList<Function1<List<Double>, Double>>();
    funcs = addFunc(min, funcs);
    final List<Function1<List<Double>, Double>> funcs1 = addFunc(max, funcs);
    final List<Double> answers = applyAllFuncs(funcs1, myArray);
    final List<Function1<List<Double>, Double>> funcs2 = addFunc(avg, funcs);
    final List<Double> answers2 = applyAllFuncs(funcs2, myArray);
    System.out.println(answers   "n"   answers2);
  }
}
  

Большей части этого шаблона можно было бы избежать, если бы вы использовали уже существующие библиотеки функционального программирования для Java, такие как функциональная Java или та, что предлагается GridGain.

Не так уж много из этого оптимизируется, поскольку JVM никогда не предназначалась для такого рода вещей. Scala, который является функциональным языком на JVM, использует те же методы, что и выше, для реализации лямбд и функций более высокого порядка и обеспечивает одинаковую производительность с Java. Тем не менее, я советую вам профилировать код и решить, удовлетворяет ли он требованиям к производительности вашего конкретного варианта использования.

Комментарии:

1. Просто для острастки, вот как эквивалентный код выглядел бы в Scala: ideone.com/Fyokg

Ответ №2:

Создайте свой собственный fold:
создайте метод, который принимает список и список функций для запуска в списке.
Список функций для запуска в списке может быть просто, ArrayList<XFunction> где XFunction это определенный вами класс, у которого есть метод (скажем run(...) ), и вы можете создать его анонимную реализацию для каждой функции.

Пример:

 list.add(new XFunction(){ 
   run(ArrayList<int> numbers){ 
      return numbers.size(); 
       }
   }
  

Это, конечно, не функция, которая что-либо решает, но вы поняли идею

Комментарии:

1. Большое спасибо за ваш ответ. Идеологическая проблема, связанная с этим способом, заключается в том, что он реализует функциональную парадигму на императивном языке. Практическая проблема заключается в том, насколько это повлияет на время выполнения. Я не уверен, какая часть оборудования, используемого для реализации функций более высокого порядка (как вы показали), оптимизируется стандартным компилятором Java (Sun-but-now-Oracle).

2. Я знаю, что они уродливы и негигиеничны, и я совсем не большой поклонник, и, каким бы злым он ни был, механизм макросов (например, макросы итератора C), который позволит мне просто встроить дополнительные вычисления результатов, кажется, единственный способ, который я могу придумать, чтобы решить практическую проблему. … или, может быть, я просто недостаточно хорошо подумал.

Ответ №3:

Вы можете использовать какой-то шаблон Stategy :

 public interface Function{
    public void Compute();
}

public Function1 implements Function{
   public void Compute(){
       System.out.println("This is Function1");
   }
}

public Function2 implements Function{
   public void Compute(){
       System.out.println("This is Function2");
   }
}
  

После этого объявите контейнер функции:

 ArrayList<Function> functions = new ArrayList<Function>();
functions.add(new Function1());
functions.add(new Function2());

ArrayList<Object> someData = new ArrayList<Object>();
Something.execute(someData,functions);