2015年4月10日金曜日

[Ruby][Java]reduce(inject)を使ってみる

#ruby 2.0.0p643 (2015-02-25 revision 49749) [x86_64-darwin13.4.0]
1から100までの足し算は、テンポラリーの変数を使ってこんなふうにかける。
sum = 0
(1..100).each {|n| sum += n}
puts "sum = #{sum}"

Enumerable#reduceを使うと、こんなふうにかける。
sum = (1..100).reduce(0) {|memo, n| memo + n}
puts "sum = #{sum}"

1回目のループでは、memoにはreduce(0)で指定した0が入る。nには1が入っている。
memo + nの結果は次のループではmemoに格納される。
つまり2回目のループではmemo=1, n=2で、3回目にはmemo=3, n=3、4回目にはmemo=6, n=4…… といった具合。
reduceのかわりにinjectでもよい。

単純な足し算だけじゃつまらないので、下記のようなUserというクラスがあったとして、
class User
  attr_accessor :name, :age
  def initialize(options={})
    @name = options[:name]
    @age = options[:age]
  end
end

Userインスタンスの配列の中から21歳以上のUserのnameだけを取り出したい場合には下記のようにすればよい。

users = [User.new(name: 'john', age: 20), User.new(name: 'hanako', age: 21),User.new(name: 'taro', age: 22)]
names = users.reduce([]) {|memo, u| u.age >= 21 ? memo << u.name : memo}
p names
# => ["hanako", "taro"]


JavaではJava8でinjectが使える。
 class User {

  private final String name;
  private final int age;

  private User(final String nameArg, final int ageArg) {
    name = nameArg;
    age = ageArg;
  }

  static User newInstance(String name, int age) {
    return new User(name, age);
  }

  String getName() {
    return name;
  }

  int getAge() {
    return age;
  }
}

List list = new ArrayList<>();
list.add(User.newInstance("john", 20));
list.add(User.newInstance("hanako", 21));
list.add(User.newInstance("taro", 22));
List result = list.stream().reduce(new ArrayList(),
            new BiFunction, User, List>() {
              @Override
              public List apply(List t, User u) {
                if (u.getAge() >= 21) {
                  t.add(u.getName());
                }
                return t;
              }
            },
            new BinaryOperator>() {
              @Override
              public List apply(List t, List u) {
                t.addAll(u);
                return t;
              }
            }
    );

長い……
クラス定義やlistを作っているところはしようがないとしても、reduceのところが長い……
ラムダ式を使うと
result = list.stream().reduce(new ArrayList(), (memo, user) -> {
      if (user.getAge() >= 21) {
        memo.add(user.getName());
      }
      return memo;
    }, (t, u) -> {
      t.addAll(u);
      return t;
    });

第3引数の存在がなぞ。
この例だと{}の中をthrow new NotImplementedException();と書いても動作する。
要素数が多くなったりすると活躍するのかなあ?

けれど、結局
result = new ArrayList<>();
for (User u : list) {
  if (u.getAge() >= 21) {
    result.add(u.getName());
  }
}
のほうがわかりやすい?

0 件のコメント:

コメントを投稿