Streamを使ったgroup化

まずは実際のソースコード
今回は(ちょっと古いが)「このマンガがすごい」をオブジェクトにしてみた。

       ComicRank cr1 = new ComicRank(2015, "こえのかたち", Genre.Men, 1);
        ComicRank cr2 = new ComicRank(2015, "魔法使いの嫁", Genre.Men, 2);
        ComicRank cr3 = new ComicRank(2015, "子供はわかってあげない", Genre.Men, 3);
        ComicRank cr4 = new ComicRank(2015, "ちーちゃんはちょっと足りない", Genre.Women, 1);
        ComicRank cr5 = new ComicRank(2014, "暗殺教室", Genre.Men, 1);
        ComicRank cr6 = new ComicRank(2014, "坂本ですが?", Genre.Men, 2);
        ComicRank cr7 = new ComicRank(2014, "亜人", Genre.Men, 3);
        ComicRank cr8 = new ComicRank(2014, "さよならソルシエ", Genre.Women, 1);
        ComicRank cr9 = new ComicRank(2014, "ときめきトゥナイト", Genre.Women, 2);

        List<ComicRank> list = new ArrayList<>();
        list.addAll(Arrays.asList(cr1, cr2, cr3, cr4, cr5, cr6, cr7, cr8, cr9));
        //リストを年ごとに名前でグループ化する
        Map<Integer, List<String>> yearsBookNameList = 
                list.stream()
                .collect(
                        Collectors.groupingBy(ComicRank::getYear, Collectors.mapping(ComicRank::getBookName, Collectors.toList())));
        
        System.out.println(yearsBookNameList);

結果

{2014=[暗殺教室, 坂本ですが?, 亜人, さよならソルシエ, ときめきトゥナイト], 2015=[こえのかたち, 魔法使いの嫁, 子供はわかってあげない, ちーちゃんはちょっと足りない]}

肝になるのはここ。 Collectors.groupingBy(ComicRank::getYear, Collectors.mapping(ComicRank::getBookName, Collectors.toList())));

何をしているのかというと、 groupingBy(arg1, arg2)というメソッドに、第一引数にはグループ化の基準となる値(この場合年)を渡し、
第2引数に、mapping()メソッドの結果となるCollectorを渡している。 んで、mappingには(マッピング関数、各要素が蓄積される型に変換するCollector)という形で使う。 今回はmapping(本の名前、リスト化)という具合。

では次に、各年の1位を{年:タイトル}(ジャンルは男のみ)で絞ってみる。

       //各年の、男編1位のコミック名を出す
        Comparator<ComicRank> byRank = Comparator.comparing(ComicRank::getRank);
        Map<Integer, Optional<ComicRank>> noOneMap =
        list.stream()
        .filter(o -> o.getGenre() == Genre.Men)
        .collect(groupingBy(ComicRank::getYear, reducing(BinaryOperator.minBy(byRank))));
        
        System.out.println(noOneMap);

結果

{2014=Optional[2014年1位:男編は[暗殺教室]], 2015=Optional[2015年1位:男編は[こえのかたち]]}

Streamを使った並び替え

とりあえずいつもの。(準備)

        Person p1 = new Person("abc", 30);
        Person p2 = new Person("def", 25);
        Person p3 = new Person("ghi", 17);
        Person p4 = new Person("jkl", 8);
        Person p5 = new Person("mno", 30);

        List<Person> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        list.add(p4);
        list.add(p5);

①年齢の昇順で並び替え

       ArrayList<Person> sortedList = 
                list.stream()
                .sorted(Comparator.comparing(Person::getAge))
                .collect(Collectors.toCollection(ArrayList::new));


②年齢の降順で並び替え

       ArrayList<Person> reverseSortedList = 
                list.stream()
                .sorted(Comparator.comparing(Person::getAge).reversed())//←#reversedを使用
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(reverseSortedList);

③年齢の昇順 + 名前の昇順

       ArrayList<Person> ageReverseAndNameAscSortedList = 
                list.stream()
                .sorted(Comparator.comparing(Person::getAge).reversed().thenComparing(Person::getName)) //#reversedに続けて#thenComparingを使用
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(ageReverseAndNameAscSortedList);


結果

[jkl - 8, ghi - 17, def - 25, abc - 30, aaa - 30]//年齢の昇順
[abc - 30, aaa - 30, def - 25, ghi - 17, jkl - 8]//年齢の降順
[aaa - 30, abc - 30, def - 25, ghi - 17, jkl - 8]//年齢の昇順 + 名前の昇順 ※aaaが先頭に来ている。

④オブジェクトがComparableな場合
Comparableを実装したPerson

public class ComparablePerson implements Comparable<ComparablePerson> {
    
    private int age;
    private String name;

    public int getAge() {
        return age;
    }
    
    public ComparablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(ComparablePerson another) {
        if (this.getAge() != another.getAge()) {
            return this.getAge() - another.getAge(); //①年齢の昇順
        } else { 
            return this.getName().compareTo(another.getName()); //②名前の昇順
        }
    }
    
    @Override
    public String toString() {
        return "age = " + this.getAge() + ",name = " + this.getName() + "|";
    }

}

オブジェクトがComparableな場合、Stream#sortedには特に引数はいらない。 実装した#compareToの自然順序付けになる。

       ComparablePerson p1 = new ComparablePerson("aaa", 11);
        ComparablePerson p2 = new ComparablePerson("bbb", 22);
        ComparablePerson p3 = new ComparablePerson("acc", 5);
        ComparablePerson p4 = new ComparablePerson("add", 23);
        ComparablePerson p5 = new ComparablePerson("baa", 22);
        List<ComparablePerson> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        list.add(p4);
        list.add(p5);
        ArrayList<ComparablePerson> sortedList = list.stream().sorted().collect(Collectors.toCollection(ArrayList::new)); //←#sortedにはなにも渡さなくてよい。
        System.out.println(sortedList);

ほかにもいろいろあるみたいだが以上。

オブジェクトのリストをArrayList<ArrayList<String>>に変換する

オブジェクト(今回はPersonオブジェクト)のリストを Personオブジェクトのプロパティ文字列のリストに変換する。 (Personはnameとageのプロパティを持つ)

public class ServiceClass {

    private int cnt;
    {
        cnt = 1;
    }

    public void hoge() {

        Person p1 = new Person("taro", 5);
        Person p2 = new Person("taro", 10);
        Person p3 = new Person("taro", 15);
        Person p4 = new Person("taro", 20);

        List<Person> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        list.add(p4);

        list.stream().map(convertToArray()).forEach(System.out::println);

    }

    /**
    * Functionオブジェクト.
    * PersonをArrayListへ変換する
    * @return
    */
    private Function<Person, ArrayList<String>> convertToArray() {
        return p -> {
            ArrayList<String> list = new ArrayList<>();
            list.add(String.valueOf(cnt)); //連番を入れる
            list.add(p.getName());
            list.add(String.valueOf(p.getAge()));
            cnt++;
            return list;
        };
    }

}

で、hoge()を実行すると、こうなる。

[1, taro, 5]
[2, taro, 10]
[3, taro, 15]
[4, taro, 20]

今回の件とは関係ないけど、 初期化子とかよくわかんないんだよなぁ・・・

Enumで遊ぶ

javaenumでいろいろやってみよう。 まず、enumに定数固有の数値を持たせる。

   enum Nums {
        ZERO(0),
        ONE(1),
        TWO(2),
        THREE(3);
        
        int index;
        private Nums(int index) {
            this.index = index;
        }
        String get(List<String> list) {
            return list.get(this.index);
        }
    }

こんな感じで使える。

   public static void main(String[] args) {
        List<String> list = Arrays.asList("A","B","C","D","E");
        System.out.println(Nums.ZERO.get(list));
        System.out.println(Nums.ONE.get(list));
        System.out.println(Nums.TWO.get(list));
        System.out.println(Nums.THREE.get(list));

        }


②定数固有メソッドを使用する。 enumの宣言内でabstractメソッドを定義すると、 定数ごとにメソッドの実装を強制できる。(メソッドを実装しないと コンパイルエラーになるため、将来の定数追加において、実装漏れが防げる。)

   enum Calc {
        PLUS("+") {
            @Override
            double calc(int a, int b) {
                return a + b;
            }
        },
        MINUS("-") {
            @Override
            double calc(int a, int b) {
                return a - b;
            }
        };
        String type;
        Calc(String type) {
            this.type = type;
        }
        
        abstract double calc(int a , int b);
    }


Python基礎いろいろ

さーて、本日はPythonの基本的な文法のまとめだよー!

1. Pythonは動的型付

    a = 10
    b = 'string'
    print(type(a))
    print(type(b))

と、いうわけで、上記のように、変数の宣言時に型の宣言は必要ない。
(JavaではintとかStringとかで宣言してるよね。) 変数の型を調べるときはtype()を使用する。

<class 'int'>
<class 'str'>

2. 文字列

2.1 変数の宣言

text = 'text'

2.2 文字列の取り出し(スライス)

    print(text[1]) #e
    print(text[0:2]) #te

string[x:y]とすると、x番目からy-1番目までの文字列が取得できる。

2.3 文字列の分割

    text2 = 'i have a apple'
    print(text2.split(" "))

['i', 'have', 'a', 'apple']

2.4 文字列の結合

    arr = ['one','two','three','four']
    print('_'.join(arr))

one_two_three_four

2.5 文字列中の文字の探索

    text = 'i have a pen and apple'
    print(text.find('apple'))

17

3. コレクション

コレクションには、リスト・タプル・辞書・集合の4種がある。 以下、順に見ていく。

3.1 リスト

    array = ['abc', 'def', 'ghi']
3.1.2 リストの中身の変更
    array[0] = 'jkl'
    print(array)
3.1.3 リストもスライス
    array[0:2]

['jkl', 'def']

3.1.4 リスト要素の削除

削除にはpop()もしくはdel(index)を使用する

   a = ['i','have','pen']
   a.pop() #末尾要素の削除
   a.del(2)#指定要素の削除
3.1.5 リストへの要素の追加
   a.append('!')
   a.insert(2, 'ooo')

3.1.6 リストの長さ
   len(a)

3.2 タプル

ほぼリストと一緒。ただし、 要素の変更ができないことには注意する!

   a = ('one', 'two', 'three')#タプルは()で作成する
   a[2] = 'error'
TypeError: 'tuple' object does not support item assignment

要素を変更しようとすると、こんなエラーが出る!

3.3 辞書

{キー:値}でデータを保持するデータ構造。
JavaのMapと似てる。

3.3.1 辞書の生成
   d = {'left':'apple', 'right':'pen'}
3.3.2 キーがあるか
   'left' in d
3.3.3 辞書の一覧
   d.keys() #キーの一覧を取得
   d.values() #値の一覧を取得

といっても、上記のままではオブジェクトが返却されるだけなので、
普通は以下のようにリストやタプルにして使用する。

   list(d.keys()) #キーをリストに
   tuple(d.values()) #値をタプルに

3.4 集合

値のないキーだけの集合みたいなもん。
要素の重複は許されないぞ。

3.4.1 生成
   s = {'abc', 'def', 'ghi', 'abc'}
   print(s) #{'abc', 'def', 'ghi'} abcの重複が排除される

4. 制御構文

どんな言語でもおなじみの条件分岐、繰り返しなど

4.1 条件分岐 if, else , elif

何はともあれ書いてみるのが一番。

       if a > 10 :
           print('over 10')
       elif a < 10 :
           print('under 10')
       else :
           print('just 10')

4.2 繰り返し

pythonには繰り返しを実現するのに二つの方法がある。 以下順に!

4.2.1 while文

while文は条件に合致する間だけ 処理を実行する制御文。
以下のようにカウンタを使用した繰り返しはwhileが適している。

    c = 10
    while c > 0 :
        print(c)
        c = c - 1


4.2.2 for文

一方、for文は以下のようにリストや辞書などのコレクションを走査する場合に 用いる。

    arr = ['one','two','three','four']
    for n in arr :
        print(n)


5. 関数

defを使って定義したものがpythonでは関数としてみなされる。 以下、いろいろな使い方がある。

5.1 位置引数

def plus(a, b):
    print(a + b)

5.2 キーワード引数

上記の位置引数で例示したplus(a, b)をキーワード引数として呼び出すとこのようになる。
plus()で定義した仮引数のa, bそれぞれに値を指定することができる。

plus(b = 'two', a = 'one') #onetwo

5.3 デフォルト引数

引数が渡されなかった場合に、デフォルトで使用する実引数を定義することができる。
呼び出しもとで引数を省略した場合にはこの値が使用される。

def square(a, b=2):
    print(a ** b)

square(3) #9 ※3の2乗が計算される
square(3, 3) #27 ※3の3乗が計算される

5.4 可変長引数(タプル引数)

仮引数に*を付けると、引数にタプルを使用することができる。この方法で可変長引数が実現できる。

def tfunc(*t):
    print(t)
    for n in t:
        print(n)

tfunc('apple', 'lemon', 'orange', 'grape')

5.5 辞書引数

仮引数に**を付けると引数を辞書で受け取ることができる。

def dicFunc(**dic):
    print(dic)
    for n in list(dic.values()):
        print(n)


dicFunc(one='いち', two='に', three='さん')

5.6 ジェネレータ

反復処理で使用できる特殊な関数。
リストとの違い?知らん。

def gfunc(a,b,c):
    yield a
    yield b
    yield c

for v in gfunc('apple','strawberry','cream'):
    print(v)

Java 可変長引数の使い方

可変長引数は、同一の型の引数について、 呼び出し元から渡される引数の数が不定の場合に有用な方法である。 が、使用方法を誤るといろいろ面倒なことになるそうなので、以下にメモ。

とりあえず、使い方を書いてみる。

        //渡された可変長引数の中から、最長値を返却する
    static int min(int... args) {

        if (args.length == 0) {
            throw new IllegalArgumentException();
        }
        int min = args[0];
        for (int i = 1; i < args.length; i++) {
            if (args[i] < min){
                min = args[i];
            }
        }
        return min;
    }

と、こんな感じで可変長引数は内部で配列になるので、 for文などに使用できる。(当然lengthも持っている。)

さて、とりあえず書いてみたものの、いくつか悪い点がある。 ①引数なしでメソッドを呼び出した場合に、コンパイル時ではなく実行時にエラー ②args(引数)に対する明示的な正当性検査を含めなければならない

対策として、以下のように修正する。

static int min(int firstArg, int...args) {
        int min = firstArg;
        for (int arg : args) {
            if(arg < min) {
                min = arg;
            }
        }
        return min;
    }

見てわかる通り、可変長引数の前に、同じ型の引数を一つ追加する。 こうすることによって、上記の①、②が回避できる。

また、可変長引数は呼び出しのたびに、配列オブジェクトが生成されるため、 コストが大きい。 仮に、上記のメソッドの可変引数の数が3個までのものが多数を占める場合、 引数が0~3個の4つと、引数一つ + 可変長引数のメソッドという具合に オーバーロードしたメソッドを作成することも考えたほうが良い。

static int min (){};
static int min (int first){};
static int min (int first, int second){};
static int min (int first, int second, int third){};
static int min (int firstArg int... args){};

とりあえずこんな感じで。以上。

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)

Pythonいろいろメモ

1.文字列を配列にするにはlist()を使用する

input = 'abcdef'
array = list(input)
print(array)

→[‘a’,‘b’,‘c’,’d',‘e’,‘f’]

2.条件分岐

a = 10

if a == 10:
    print('10!')
elif a < 10:
    print('under 10!')
else :
    print('over 10!')

こんな感じ。 Javaではelse if だったので、混乱したからメモ。

3.zip()

複数シーケンスを並列に反復処理する。

    days = ['monday','tuesday','wednesday']
    fruits = ['banana', 'orange', 'peach']
    drinks = ['coffee', 'tea', 'beer']
    desserts = ['tiramisu', 'ice cream', 'pie', 'pudding']

    for day , fruit, drink, dessert in zip(days,fruits,drinks,desserts):
        print(day, fruit, drink, dessert)

4.range()

指定した範囲の数値ストリームを生成する

    print(list(range(0,3)))
    #[0, 1, 2]

    print(list(range(0, 11, 2)))
    #[0, 2, 4, 6, 8, 10]

使い方は、range(start, end , step)とすることで、 startからstepごとにend-1の数までの数値ストリームができる。

5.内包表記

以前のブログで 少しだけ話題に挙げた内包表記をもう一度。 とりあえず書く。

number_list = [number for number in range(1,6)]

[1, 2 ,3 ,4 ,5]となる。

使い方は[expression for item in iterable] 一つ目のnumber変数はリストに入れる値を作るために必要。 2つ目のnumber変数はforループの一部。 最初のnumberはである。 以下のように書き直すとその性質がわかりやすい。

number_list = [number - 1 for number in range(1,6)]

結果は[0, 1, 2, 3, 4]となる。

んじゃ次に、内包表記に条件式を追加したものを。

a_list = [number for number in range(1,6) if number % 2 == 1]

結果は[1, 3, 5]になる。 if以降が条件式になっており、その条件に合致するものだけがリスト化対象になる。

次は、内包表記中にfor節を複数記述する形。

    rows = range(1,4)
    cols = range(1,3)
    cells = [(row, col) for row in rows for col in cols]
    for cell in cells:
        print(cell)

結果

(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)

つまり2重ループですな。

入門 Python 3

入門 Python 3