はじめに
この記事はC3 Advent Calendar 2024で書いた記事です。こんにちは、buridaikonです。C3ではHackで活動しています。最近はJavaを勉強しています。
本記事を書くにあたって、できることなら今回用いた技術を完全に理解したうえで実装したかったのですが、時間が足りなかったため、この記事は ”正規表現とStreamAPIを使ってみた” 旨の記事になっています。
なぜ時間が足りなかったのかは、お察しください!(必修の単位は大事(といいつつ落としたかも)).
私自身、本当に素人なのでどうか温かい目で読んでいただければ幸いです。
前置きが長くなりましたが内容に入ります。
内容
- 予備知識
- StreamAPIとは
- C3ちゃんbotについて
- C3ちゃんbotの挙動を真似してみる
- まとめ
- 参考文献
予備知識
オブジェクト指向
プログラムの中で扱う対象をモノ(オブジェクト)になぞらえ、オブジェクトの組み合わせによってアプリを作成していく手法のこと。Javaはオブジェクト指向の考え方で作られた言語の一つ。
正規表現
特定の条件を持つ文字列の集合を一つの記号列で表す表現である。また、すべての正規表現は有限オートマトンで表現することができる。
StreamAPI
Stream APIは、繰り返し処理のためのAPIです。コレクション、ファイルなどのデータソースから個々の要素を取り出して、これを処理の流れ(=Stream)に引き渡すためのしくみを提供します。Streamの中ではデータが加工されていき、出口で加工した結果を得られます。
実装したコードとC3chan_botについて
ここで、今回実装したコードとその背景について話したいと思います。簡単に言うとMastodonにいるbotの応答を表面上だけ真似してみたものになっています。背景としては、C3のAdventCalendarを書くにあたり、どうせならC3に関係する話にしたいと思ったことが主な理由です。
C3chan_botはMastodonのC3サーバーにいるボットで、講義開始のリマインドをしてくれたりトゥート数を教えてくれたりします。下の写真が私(buridaikon)の「C3ちゃん、トゥート数教えて」という投稿に反応して総トゥート(投稿)数を教えてくれている様子です。
C3ちゃんbotの挙動を真似してみる
ソースコード
/* C3chan_bot.java */
import java.util.*; //Scannerクラスとかもろもろ
import java.util.regex.*; //正規表現関連
import java.util.stream.*; //StreamAPI
class user {
private String userName; //ユーザー名
private int tootNumber; //トゥート数
public user (String userName, int tootNumber) {
this.userName = userName;
this.tootNumber = tootNumber;
}
public String getUserName {
return userName;
}
public int getTootNumber {
return tootNumber;
}
}
public class c3chan_origin {
public static void main(String[] args) {
List<user> users = Arrays.asList(
new user("僕", 5198),
new user("wotsu", 2232),
new user("buridaikon", 2311),
new user("Mille", 2696)
);
Scanner scan = new Scanner(System.in);
String input = scan.next();
Pattern pattern = Pattern.compile("C3ちゃん、@([a-zA-Z0-9_]+)のトゥート数教えて");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
String userName = matcher.group(1);
//StreamAPI
Optional<user> userStream = users.stream()
.filter(x -> x.getUserName().equalsIgnoreCase(userName))
.findFirst();
String response = userStream.map(x -> "今の" + "@" + x.getUserName() + " の総トゥート数は " + x.getTootNumber() + " 件だよ")
.orElse("@" + userName + " のデータが見つかりませんでした。");
System.out.println(response);
} else {
System.out.println("コマンドが見つかりませんでした");
}
}
}
プログラムの解説
➀userクラスを定義
class user {
private String userName; //ユーザー名
private int tootNumber; //トゥート数
public user (String userName, int tootNumber) {
this.userName = userName;
this.tootNumber = tootNumber;
}
public String getUserName() {
return userName;
}
public int getTootNumber() {
return tootNumber;
}
}
ここでは、userクラスを定義して、ユーザーデータを格納するためのgetUserName
とgetTootNumber
のフィールドを用意しています。外からアクセスできるように可視性はpublic
です。
➁フィールドに値を代入
List<user> users = Arrays.asList(
new user("僕", 5198),
new user("wotsu", 2232),
new user("buridaikon", 2311),
new user("Mille", 2696)
);
➂入力
Scanner scan = new Scanner(System.in);
String input = scan.next();
Scannerクラスを使って文字列型変数inputに入力します。
➃正規表現
Pattern pattern = Pattern.compile("C3ちゃん、@([a-zA-Z0-9_]+)のトゥート数教えて");
Matcher matcher = pattern.matcher(input);
Pattern.compile
で入力として認識する文字列を定義しました。次にMatcher
を使って入力した文字列input
が定義した正規表現に合致するかどうかをするためのオブジェクトを生成しています。
➄正規表現に当てはまった時
String userName = matcher.group(1);
//StreamAPI
Optional<user> userStream = users.stream()
.filter(x -> x.getUserName().equalsIgnoreCase(userName))
.findFirst();
String userName = matcher.group(1);
で最初に定義した正規表現とマッチする文字列をuserName
に代入します。group
の引数が1であるのは、文字列中のユーザー名だけを取得するためです。0を指定するとスコープが文字列全体になってしまうので使えません。
そして、ここでやっとStreamAPIを使っていきます。リストにまとめたユーザーデータを調べていきます。userクラスのフィールドからデータを取得しStream(一連の流れのようなイメージ)にします。.findFirst()
とあるように、先頭から調べます。
ユーザの情報
作ったコードのデモをするためにMastodonのアクティブな鯖缶の方々(僕、wotsu、Mille)にお名前をお借りしました。ご協力ありがとうございます。下の表に私を含む4人の名前とトゥート数を示します。
ユーザー名 | トゥート数 |
---|---|
boku(僕) | 5198 |
wotsu | 2232 |
buridaikon | 2311 |
Mille | 2696 |
デモ
このデモでは、まず入力の文字列が「C3ちゃん、@<ユーザー名>のトゥート数教えて」という正規表現に合致した場合に文字列をStreamにして、次にユーザー名を抜き出してリストにあるデータを参照してC3chan_botの真似をできたら成功です。
上の写真では、入力された文字列がプログラム中に定めた正規表現に合致するため、ユーザー名に該当するデータを参照できています。
ちなみに、equalsIgnoreCase
としてみたところ、小文字であるべきところに大文字を使ってもしっかり正解を出力してくれました。
念のため、すべてのユーザーでテストをしました
参考文献
- 独習Java 第6版
最後に
ここまで読んでいただきありがとうございました!来年のAdventCalendarはこのような駄文ではなくしっかりとした技術系記事を書けるように研鑽いたしますので、よろしくお願いいたします!