LoginSignup
0
0

Stream でインデックスと要素を扱いたい

Posted at

目次

1. はじめに
2. Java の for 文
3. guava
4. 実装
5. まとめ
参考文献

1. はじめに

Java 100 本ノック、55 本目の応用。

業務で Java を使うので、Java 100 本ノックを使い学習をしています。55 本目の問題は、ファイルを読み 1 行の文字数を合計して出力するプログラムを実装するというものですが、応用として読んだ行に行番号をつけて別ファイルに書くプログラムを実装することにしました。

Python の for 文では簡単にインデックスを扱える。

Java では、FilesBufferedReader を使えばファイルの中身を StringListStream で取得できます。なので、for 文や Stream でインデックスと要素を取得すれば簡単に実装できるなと思いました。Python では簡単にできるので。

sample.py
list = ['a', 'b', 'c']

for idx, element in enumerate(list):
    print(f'{idx} {element}')
出力
0 a
1 b
2 c

でも、Java ではこんな便利機能はすぐに見つからなかった......

2. Java でインデックスと要素を扱う

拡張 for 文

Sample.java
import java.util.List;

public class Sample {
    public static void main(String[] args) throws Exception {
        List<String> list = List.of("a", "b", "c");
        
        int idx = 0;
        
        for (String element : list) {
            System.out.println(String.format("%s %s", idx, element));
            idx++;
        }
    }
}

まあできるけど、idx のスコープがいけてないよね。

普通の for 文

Sample.java
import java.util.List;

public class Sample {
    public static void main(String[] args) throws Exception {
        List<String> list = List.of("a", "b", "c");

        for (int idx = 0; idx < list.size(); idx++) {
            String element = list.get(idx);
            System.out.println(String.format("%s %s", idx, element));
        }
    }
}

古臭い。

IntStream

Sample.java
import java.util.List;
import java.util.stream.IntStream;

public class Sample {
    public static void main(String[] args) throws Exception {
        List<String> list = List.of("a", "b", "c");

        IntStream
                .range(0, list.size())
                .mapToObj(i -> String.format("%s %s", i, list.get(i)))
                .forEach(System.out::println);
    }
}

まだ許せるが何をやっているかわかりにくい。List を操作してる感がない。そもそも BufferReader を使いたいから List は NG だった。

Sample.java
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Sample {
    public static void main(String[] args) throws Exception {
        Stream<String> stream = Stream.of("a", "b", "c");
        List<String> list = stream.toList();

        IntStream
                .range(0, list.size())
                .mapToObj(i -> String.format("%s %s", i, list.get(i)))
                .forEach(System.out::println);
    }
}

まったくいけてない......

3. guava

コア API だけでは厳しそうだということで調べてみると、guava という Google が開発しているライブラリを見つけました。Streams.mapWithIndex 使えば良さそう。

Sample.java
import com.google.common.collect.Streams;

import java.util.stream.Stream;

public class Sample {
    public static void main(String[] args) throws Exception {
        Stream<String> stream = Stream.of("a", "b", "c");

        Streams.mapWithIndex(
                        stream,
                        (element, idx) -> String.format("%s %s", idx, element)
                )
                .forEach(System.out::println);
    }
}

python の enumerate と全く同じ要領で使えて感動。

4. 実装

本題の実装。テキストファイルを 1 行ずつ読み、行番号をつけて新規ファイルに出力するプログラムです。

ExAnswer055.java
import com.google.common.collect.Streams;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * 応用。
 */
public class ExAnswer055 {
    /**
     * メインメソッド。
     *
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        Path input = Path.of("./src/main/resources/MyAnswer055.txt");
        Path output = Path.of("./src/main/resources/exoutput055.txt");

        try (
                BufferedReader reader = Files.newBufferedReader(
                        input,
                        StandardCharsets.UTF_8
                );
                BufferedWriter writer = Files.newBufferedWriter(
                        output,
                        StandardCharsets.UTF_8
                )
        ) {
            Streams.mapWithIndex(
                            reader.lines(),
                            (str, idx) -> String.format("%s %s%n", idx + 1, str)
                    )
                    .forEach(i -> write(writer, i));
        } catch (IOException | UncheckedIOException e) {
            e.printStackTrace();
        }
    }

    private static void write(BufferedWriter writer, String line) {
        try {
            writer.write(line);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}

シンプルでいい感じに実装できました。プライベートメソッドの write は、writer.write(line)IOException が出るので、非チェック例外にして throw するために作りました。Stream 内で try-catch はしたくないので。

5. まとめ

Python でデータ処理する時は enumerate を頻繁に使っていたので、Java でも標準であってもいいと思うんですけどね。まあでもこんな感じでコア API 以外も自分で調べて使いこなせてきたのでヨシ!

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0