Fizz Buzz問題の一般化の一例

Fizz Buzz問題というプログラミングの練習問題があります。この記事はこの問題の一般化の一例です。

#一般化の一例

Fizz Buzz問題は、番号に従い、以下のように返事をするという問題です。

|  番号i        |  返事      |
|:--------------|:-----------|
| 3かつ5の倍数  |  FizzBuzz  |
| 3の倍数       |  Fizz      |
| 5の倍数       |  Buzz      |
| 上記以外      |  i         |

この問題の一般化を考えてみます。

2以上の自然数a, bが与えらているとします。ただし、aとbは互いに他の倍数ではないとします。自然数cを次のうちのどちらかとします。

+ aとbの積
+ aとbの最小公倍数

a, b, cに対して文字列A, B, Cを対応させます。番号iに対して以下のように返事をするという問題を考えます。

|  番号i   |  返事        |
|:---------|:-------------|
| cの倍数  |  文字列C     |
| aの倍数  |  文字列A     |
| bの倍数  |  文字列B     |
| 上記以外 |  i           |

これがFizz Buzz問題の一般化の一例です。

#JavaFXでプログラミング

以下の機能を持つ画面を作りました。

+ 自然数a, bと文字列A, B, Cを入力できる。
+ 自然数cをa, bの積か最小公倍数か選択できる。
+ 出力ボタンを押すと番号1から50までのFizz Buzz問題の返事が出力される。

![shoki.png](https://qiita-image-store.s3.amazonaws.com/0/133921/2933705e-6cc3-c3fd-b9a9-680c8af2fc9d.png)

![example.png](https://qiita-image-store.s3.amazonaws.com/0/133921/6fca4d62-37f3-41a0-e790-cb8f219967b2.png)


##fizzbuzzパッケージ
###FizzBuzzConfigクラス

前述の自然数a, b, cと文字列A, B, Cを保持するクラスです。

```java:FizzBuzzConfig.java
package fizzbuzz;

public class FizzBuzzConfig {

    public int fizzNumber;
    public int buzzNumber;
    public int fizzbuzzNumber;

    public String fizzWord;
    public String buzzWord;
    public String fizzbuzzWord;

}
```

###FizzbuzzTypeクラス

自然数cをa, bの積にするか最小公倍数にするかという判定に使うための列挙型です。

```java:FizzbuzzType.java
package fizzbuzz;

public enum FizzbuzzType {
    Mutiple, LeastCommonMultiple
}
```

###FizzBuzzHumanクラス

コンストラクタでFizzBuzzConfigを受け取ってフィールドに設定します。replyBasedOn()メソッドはフィールドに従ってFizz Buzz問題の返事をします。

```java:FizzBuzzHuman.java
package fizzbuzz;

public class FizzBuzzHuman {

    private FizzBuzzConfig config;

    public FizzBuzzHuman(FizzBuzzConfig config) {
        this.config = config;
    }

    public String replyBasedOn(int i) {
        if (i % config.fizzbuzzNumber == 0) {
            return config.fizzbuzzWord;
        } else if (i % config.fizzNumber == 0) {
            return config.fizzWord;
        } else if (i % config.buzzNumber == 0) {
            return config.buzzWord;
        } else {
            return String.valueOf(i);
        }
    }

}
```

##applicationパッケージ

以下3つはJavaFXのためのファイルです。

###Mainクラス

```java:Main.java
package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        BorderPane root;
        try {
            root = (BorderPane)FXMLLoader.load(getClass().getResource("app.fxml"));
            Scene scene = new Scene(root);
            primaryStage.setTitle("FizzBuzz");
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
```

###fxmlファイル

```xml:app.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.Integer?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.Pane?>

<BorderPane xmlns="http://javafx.com/javafx/" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.AppController">
  <center>
    <Pane prefHeight="450.0" prefWidth="450.0">
      <children>
        <Label alignment="CENTER_RIGHT" layoutX="15.0" layoutY="45.0" prefHeight="20.0" prefWidth="45.0" text="Fizz" />
        <Label alignment="CENTER_RIGHT" layoutX="15.0" layoutY="80.0" prefHeight="20.0" prefWidth="45.0" text="Buzz" />
        <Label alignment="CENTER_RIGHT" layoutX="15.0" layoutY="115.0" prefHeight="20.0" prefWidth="45.0" text="FizzBuzz" />

        <Label layoutX="65.0" layoutY="15.0" text="文言" />
        <TextField fx:id="textFieldFizz" layoutX="65.0" layoutY="40.0" promptText="文言を入力してください。" text="Fizz" />
        <TextField fx:id="textFieldBuzz" layoutX="65.0" layoutY="75.0" promptText="文言を入力してください。" text="Buzz" />
        <TextField fx:id="textFieldFizzBuzz" layoutX="65.0" layoutY="110.0" promptText="文言を入力してください。" text="FizzBuzz" />

        <Label layoutX="225.0" layoutY="15.0" text="条件" />
        <ComboBox fx:id="comboBoxFizz" layoutX="225.0" layoutY="40.0" prefWidth="150.0" promptText="選択してください。" onAction="#comboBoxSelected">
          <items>
            <FXCollections fx:factory="observableArrayList">
              <Integer fx:value="2" />
              <Integer fx:value="3" />
              <Integer fx:value="4" />
              <Integer fx:value="5" />
              <Integer fx:value="6" />
              <Integer fx:value="7" />
              <Integer fx:value="8" />
              <Integer fx:value="9" />
            </FXCollections>
          </items>
          <value>
            <Integer fx:value="3" />
          </value>
        </ComboBox>
        <ComboBox fx:id="comboBoxBuzz" layoutX="225.0" layoutY="75.0" prefWidth="150.0" promptText="選択してください。" onAction="#comboBoxSelected">
          <items>
            <FXCollections fx:factory="observableArrayList">
              <Integer fx:value="2" />
              <Integer fx:value="3" />
              <Integer fx:value="4" />
              <Integer fx:value="5" />
              <Integer fx:value="6" />
              <Integer fx:value="7" />
              <Integer fx:value="8" />
              <Integer fx:value="9" />
            </FXCollections>
          </items>
          <value>
            <Integer fx:value="5" />
          </value>
        </ComboBox>
        <RadioButton fx:id="radioButtonMult" layoutX="230.0" layoutY="115.0" mnemonicParsing="false" text="倍数" onAction="#fizzBuzzTypeSelected">
          <toggleGroup>
            <ToggleGroup fx:id="toggleGroupFizzBuzzType" />
          </toggleGroup>
        </RadioButton>
        <RadioButton fx:id="radioButtonLCM" layoutX="285.0" layoutY="115.0" mnemonicParsing="false" text="最小公倍数" onAction="#fizzBuzzTypeSelected" toggleGroup="$toggleGroupFizzBuzzType" />
        <TextField fx:id="textFieldFizzBuzzNum" editable="false" layoutX="225.0" layoutY="145.0" promptText="上のラジオボタンをクリック" />

        <Separator layoutX="15.0" layoutY="185.0" prefHeight="10.0" prefWidth="360.0" />

        <Button layoutX="15.0" layoutY="205.0" mnemonicParsing="false" text="出力" onAction="#output" />
        <TextArea fx:id="textAreaOutput" editable="false" layoutX="65.0" layoutY="205.0" prefHeight="200.0" prefWidth="310.0" promptText="出力ボタンをクリック" />
      </children>
    </Pane>
  </center>
</BorderPane>
```

###AppControllerクラス

```java:AppController.java
package application;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import fizzbuzz.FizzBuzzConfig;
import fizzbuzz.FizzBuzzHuman;
import fizzbuzz.FizzbuzzType;

public class AppController {

    @FXML TextField textFieldFizz;
    @FXML TextField textFieldBuzz;
    @FXML TextField textFieldFizzBuzz;

    @FXML ComboBox<Integer> comboBoxFizz;
    @FXML ComboBox<Integer> comboBoxBuzz;

    @FXML ToggleGroup toggleGroupFizzBuzzType;
    @FXML RadioButton radioButtonMult;
    @FXML RadioButton radioButtonLCM;

    @FXML TextField textFieldFizzBuzzNum;
    @FXML TextArea textAreaOutput;

    @FXML
    private void output(ActionEvent event) {

        // 出力テキストエリアをクリア
        textAreaOutput.setText(null);

        // 入力チェック
        if (!radioButtonMult.isSelected() && !radioButtonLCM.isSelected()) {
            showDialog("FizzBuzzの条件が未選択です。");
            return ;
        }

        // 値受け取り
        FizzBuzzConfig config = new FizzBuzzConfig();
        config.fizzWord       = textFieldFizz.getText();
        config.buzzWord       = textFieldBuzz.getText();
        config.fizzbuzzWord   = textFieldFizzBuzz.getText();
        config.fizzNumber     = comboBoxFizz.getValue();
        config.buzzNumber     = comboBoxBuzz.getValue();
        config.fizzbuzzNumber = Integer.parseInt(textFieldFizzBuzzNum.getText());

        // fizz buzz
        FizzBuzzHuman h = new FizzBuzzHuman(config);
        String lineSeparetor = System.getProperty("line.separator");
        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= 50; i++) {
            sb.append(h.replyBasedOn(i));
            sb.append(lineSeparetor);
        }
        textAreaOutput.setText(sb.toString());

        // 完了通知
        showDialog("出力が完了しました。");

    }

    @FXML
    private void comboBoxSelected(ActionEvent event) {
        clear();
    }

    @FXML
    private void fizzBuzzTypeSelected(ActionEvent event) {
        int fizzNum = comboBoxFizz.getSelectionModel().getSelectedItem().intValue();
        int buzzNum = comboBoxBuzz.getSelectionModel().getSelectedItem().intValue();

        // どちらかが一方の倍数の場合、エラーとする。
        if (fizzNum % buzzNum == 0) {
            clear();
            showDialog("fizzの条件がbuzzの条件の倍数です。");
            return ;
        } else if (buzzNum % fizzNum == 0) {
            clear();
            showDialog("buzzの条件がfizzの条件の倍数です。");
            return ;
        }

        FizzbuzzType type = null;
        if (radioButtonMult.isSelected()) {
            type = FizzbuzzType.Mutiple;
        } else if (radioButtonLCM.isSelected()) {
            type = FizzbuzzType.LeastCommonMultiple;;
        }

        textFieldFizzBuzzNum.setText(String.valueOf(getFizzbuzzNumberFrom(fizzNum, buzzNum, type)));
    }

    private int getFizzbuzzNumberFrom(int fizzNumber, int buzzNumber, FizzbuzzType type) {

        int num = 0;
        if (type == FizzbuzzType.Mutiple) {
            num = fizzNumber * buzzNumber;
        } else {
            num = getLCM(fizzNumber, buzzNumber);
        }

        return num;

    }

    private int getLCM(int x, int y) {

        int a = x;
        int b = y;

        int temp;
        if (a < b) {
            temp = a;
            a = b;
            b = temp;
        }

        int r = a % b;
        while (r != 0) {
            a = b;
            b = r;
            r = a % b;
        }
        return x * y / b;
    }

    private void clear() {

        radioButtonMult.setSelected(false);
        radioButtonLCM.setSelected(false);
        textFieldFizzBuzzNum.setText(null);
        textAreaOutput.setText(null);

    }

    private void showDialog(String contentText) {

        Alert alert = new Alert(AlertType.INFORMATION);
        alert.getDialogPane().setHeaderText(null);
        alert.getDialogPane().setContentText(contentText);
        alert.showAndWait();

    }

}
```