MyEnigma

とある自律移動システムエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

各プログラミング言語における高階関数による関数型プログラミングの初歩入門


Javaによる関数型プログラミング ―Java 8ラムダ式とStream

目次

はじめに

ちゃんとした関数型プログラミングは少し敷居が高いですが、

高階関数をベースとしたシンプルなものは、使えると便利なので、

様々な言語で実装したものを、まとめておきます。

複数の条件を元にソート

ある構造体(クラス)のリストを複数の条件でソートしたい場合は、

各言語で下記のように、高階関数を使って、簡単にソートできます。

Python

Pythonの場合は、sorted関数に与えるラムダで、

ソートに使いたい条件の値を複数タプルで返すと、

タプルの先頭を優先してソートしてくれます。

from dataclasses import dataclass


@dataclass
class Person:
    name: str = ""
    age: int = 0


person_list = [Person("John", 20), Person("Mary", 23), Person("Ann", 23),
               Person("Peter", 18)]
print(f"{person_list=}")
# person_list=[Person(name='John', age=20), Person(name='Mary', age=23), Person(name='Ann', age=23), Person(name='Peter', age=18)]

sorted_list = sorted(person_list,
                     key=lambda p: (p.age, p.name))
print(f"{sorted_list=}")
# sorted_list=[Person(name='Peter', age=18), Person(name='John', age=20), Person(name='Ann', age=23), Person(name='Mary', age=23)]

Java

Javaでは、ComparatorのcomparingとthenComparingをつなげて、

sortedを呼ぶと複数条件でソートできます。

package fanctional_programing;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.util.Comparator.comparing;

public class MultiConditionSort {
    static class Person {
        String name;
        int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "{name='" + name + '\'' +
                   ", age=" + age +
                   '}';
        }
    }

    public static void main(String[] args) {

        List<Person> personList = new ArrayList<>(Arrays.asList(
                new Person("John", 20),
                new Person("Mary", 23),
                new Person("Ann", 23),
                new Person("Peter", 18)
        ));
        System.out.println(personList);
        // [{name='John', age=20}, {name='Mary', age=23}, {name='Ann', age=23}, {name='Peter', age=18}]

        Function<Person, Integer> byAge = person -> person.age;
        Function<Person, String> byName = person -> person.name;

        List<Person> sortedList = personList.stream()
                .sorted(comparing(byAge).thenComparing(byName))
                .collect(Collectors.toList());

        System.out.println(sortedList);
        // [{name='Peter', age=18}, {name='John', age=20}, {name='Ann', age=23}, {name='Mary', age=23}]

    }
}

qiita.com

Julia

JuliaもPythonと同様に、sort関数のby引数にわたすラムダに、

ソートに使いたい条件の値を複数タプルで返すと、

タプルの先頭を優先してソートしてくれます。

struct Person
    name::String
    age::Int64
end

person_list = [Person("John", 20), Person("Mary", 23), Person("Ann", 23), Person("Peter", 18)]
@show person_list
# person_list = Person[Person("John", 20), Person("Mary", 23), Person("Ann", 23), Person("Peter", 18)]

sorted_list = sort(person_list, by = p -> (p.age, p.name) )
@show sorted_list
# sorted_list = Person[Person("Peter", 18), Person("John", 20), Person("Ann", 23), Person("Mary", 23)]

同じデータでグルーピングするgroupby

ある構造体(クラス)のリストから、構造体のあるデータを元にグルーピングする

いわゆるgroup byが下記のように実装できます。

Python

Pythonでは、標準ライブラリであるItertollsのgroupbyを使うと便利です。

docs.python.org

注意点として、groupbyの返り値はイテレータなので、dict内表記を使って、

dictに変換できます。

from dataclasses import dataclass
from itertools import groupby


@dataclass
class Person:
    name: str = ""
    age: int = 0


person_list = [Person("John", 20), Person("Mary", 23), Person("Ann", 23),
               Person("Peter", 18)]
print(f"{person_list=}")
# person_list=[Person(name='John', age=20), Person(name='Mary', age=23), Person(name='Ann', age=23), Person(name='Peter', age=18)]

groupby_map = {k: list(v) for k, v in groupby(person_list, lambda p: p.age)}
print(f"{groupby_map=}")
# groupby_map={20: [Person(name='John', age=20)], 23: [Person(name='Mary', age=23), Person(name='Ann', age=23)], 18: [Person(name='Peter', age=18)]}

Java

Javaの場合は、Collector.groupingByで、

条件をもとに、hashmapでデータをグルーピングできます。

qiita.com

docs.oracle.com

package fanctional_programing;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupBy {
    static class Person {
        String name;
        int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "{name='" + name + '\'' +
                   ", age=" + age +
                   '}';
        }
    }

    public static void main(String[] args) {

        List<Person> personList = new ArrayList<>(Arrays.asList(
                new Person("John", 20),
                new Person("Mary", 23),
                new Person("Ann", 23),
                new Person("Peter", 18)
        ));
        System.out.println(personList);
        // [{name='John', age=20}, {name='Mary', age=23}, {name='Ann', age=23}, {name='Peter', age=18}]

        Map<Integer, List<Person>> groupBy = personList.stream()
                .collect(Collectors.groupingBy(person -> person.age));
        System.out.println(groupBy);
        // {18=[{name='Peter', age=18}], 20=[{name='John', age=20}], 23=[{name='Mary', age=23}, {name='Ann', age=23}]}

    }
}

Julia

残念ながらJuliaは標準ライブラリとして

groupbyは対応してないので、

下記のように、Groupbyっぽい関数を定義すれば、 同じような処理が可能です。

struct Person
    name::String
    age::Int64
end

function groupby(list::Array{T}, f::Function) where T
    KeyType = Core.Compiler.return_type(f, Tuple{T})
    groups = Dict{KeyType, Array{T}}()
    for v in list
        push!(get!(groups, f(v), Array{T}[]), v)
    end
    return groups
end

person_list = [Person("John", 20), Person("Mary", 23), Person("Ann", 23), Person("Peter", 18)]
@show person_list
# person_list = Person[Person("John", 20), Person("Mary", 23), Person("Ann", 23), Person("Peter", 18)]

groupby_map = groupby(person_list, p -> p.age)
@show groupby_map
# groupby_map = Dict{Int64, Array{Person}}(20 => [Person("John", 20)], 18 => [Person("Peter", 18)], 23 => [Person("Mary", 23), Person("Ann", 23)])

ちなみにこの関数の作成はは、Twitter上のJuliaに詳しい方にサポートしてもらいました(^^)。

 

一つのリストから、複数の要素を作り、一つのリストにまとめる (flatmap)

一つのリスト情報から、複数の情報を作って、

それを一つのリストとして並べる処理をflatmapなどといいます。

hydrocul.github.io

 

Python

Pythonの場合は、リスト内包表記の前のfor の要素を

後ろのforで使うことでflatmapができます。

from dataclasses import dataclass


@dataclass
class Person:
    name: str = ""
    age: int = 0


person_list = [Person("John", 20), Person("Mary", 23), Person("Ann", 23),
               Person("Peter", 18)]
print(f"{person_list=}")
# person_list=[Person(name='John', age=20), Person(name='Mary', age=23), Person(name='Ann', age=23), Person(name='Peter', age=18)]

flatmap_list = [p2 for p in person_list for p2 in (p.age, p.name)]
print(f"{flatmap_list=}")
# flatmap_list=[20, 'John', 23, 'Mary', 23, 'Ann', 18, 'Peter']

Java

Javaの場合は、公式APIのflatMapが使えます。

www.baeldung.com

package fanctional_programing;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FlatMap {
    static class Person {
        String name;
        int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "{name='" + name + '\'' +
                   ", age=" + age +
                   '}';
        }
    }

    public static void main(String[] args) {

        List<Person> personList = new ArrayList<>(Arrays.asList(
                new Person("John", 20),
                new Person("Mary", 23),
                new Person("Ann", 23),
                new Person("Peter", 18)
        ));
        System.out.println(personList);
        // [{name='John', age=20}, {name='Mary', age=23}, {name='Ann', age=23}, {name='Peter', age=18}]

        List<String> flatMapList = personList.stream()
                .flatMap(x -> Stream.of(String.valueOf(x.age), x.name))
                .collect(Collectors.toList());

        System.out.println(flatMapList);
        // [20, John, 23, Mary, 23, Ann, 18, Peter]
    }
}

Julia

JuliaはPythonと同じようにできます。

struct Person
    name::String
    age::Int64
end

person_list = [Person("John", 20), Person("Mary", 23), Person("Ann", 23), Person("Peter", 18)]
@show person_list
# person_list = Person[Person("John", 20), Person("Mary", 23), Person("Ann", 23), Person("Peter", 18)]

flatmap_list = [p2 for p in person_list for p2 in (p.age, p.name)]
@show flatmap_list
# flatmap_list = Any[20, "John", 23, "Mary", 23, "Ann", 18, "Peter"]

複数の関数を繋げるチェーンラムダ

複数の関数やラムダをつないで、

一つのデータを複数の処理をするのが、チェーンラムダです。

Java

Javaの場合は、composeとandThenメソッドを使うことで、

ラムダを連結することができます。

fits.hatenablog.com

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ChainLambda {
    static class Person {
        String name;
        public int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "{name='" + name + '\'' +
                   ", age=" + age +
                   '}';
        }
    }

    public static void main(String[] args) {

        List<Person> personList = Arrays.asList(
                new Person("John", 20),
                new Person("Mary", 23),
                new Person("Ann", 23),
                new Person("Peter", 18)
        );
        System.out.println(personList);
        // [{name='John', age=20}, {name='Mary', age=23}, {name='Ann', age=23}, {name='Peter', age=18}]

        Function<Person, String> get_name = (x) -> x.name;
        Function<String, String> make_uppercase = String::toUpperCase;

        List<String> flatMapList = personList.stream()
                .map(get_name.andThen(make_uppercase))
                .collect(Collectors.toList());

        System.out.println(flatMapList);
        // [JOHN, MARY, ANN, PETER]
    }
}

Julia

Juliaの場合は、chained function operator (|>)を使うと、

結構綺麗に書けます。

docs.julialang.org

 

struct person
    name::String
    age::Int64
end

person_list = [person("john", 20), person("mary", 23), person("ann", 23), person("peter", 18)]
@show person_list
# person_list = person[person("john", 20), person("mary", 23), person("ann", 23), person("peter", 18)]

chainedlambda = person_list .|> (p -> p.name) .|> uppercase
@show chainedlambda
# chainedlambda = ["JOHN", "MARY", "ANN", "PETER"]

参考資料

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com


Javaによる関数型プログラミング ―Java 8ラムダ式とStream

 

MyEnigma Supporters

もしこの記事が参考になり、

ブログをサポートしたいと思われた方は、

こちらからよろしくお願いします。

myenigma.hatenablog.com