Posted at

FizzBuzz色々

More than 1 year has passed since last update.

こいつ何言ってるのかわからないと思ったら読み飛ばしてください


まずはC

C++特有の文法を使ったほうがもちろん描きやすい場合が多いですが、Cでも書ける文法で書きますね。

#include <stdio.h>


void loop(int n);
void fizzbuzz(int x);

#define N 100

#define TWO(i) fizzbuzz(i); fizzbuzz(i + 1);
#define FOUR(i) TWO(i) TWO(i + 2)
#define EIGHT(i) FOUR(i) FOUR(i + 4)
#define SIXTEEN(i) EIGHT(i) EIGHT(i + 8)
#define THIRTY_TWO(i) SIXTEEN(i) SIXTEEN(i + 16)
#define SIXTY_FOUR(i) THIRTY_TWO(i) THIRTY_TWO(i + 32)
#define HUNDRED(i) SIXTY_FOUR(i) THIRTY_TWO(i + 64) FOUR(i + 96)

int main(){
int array[100];

// 普通にfor文でFizzBuzz
for(int i = 0; i < 100; i++)
fizzbuzz(i);
printf("\n");

// ポインタの引き算でFizzBuzz
for(int *i = &array[0]; i != &array[N]; i++)
fizzbuzz((int)(i - &array[0]) + 1);

// マクロ
HUNDRED(1)

// 再帰
loop(100);
printf("\n");

// while文
int j = 0;
while(++j < 100)
fizzbuzz(j);
printf("\n");

return 0;
}

void fizzbuzz(int x){
if(x % 15 == 0){
printf("fizzbuzz");
}else if(x % 3 == 0){
printf("fizz");
}else if(x % 5 == 0){
printf("buzz");
}else{
printf("%d", x);
}

printf(" ");
}

void loop(int n){
if(n <= 0)
return;

loop(n - 1);
fizzbuzz(n);
}

 2番目のポインタの引き算を使ってFizzBuzzは配列が連続的なデータ構造であることを利用しています。なので連結リストを使って同じことはできませんね。

 3番目のマクロを使ったFizzBuzzはマクロの定義が少しめんどくさかったですね。repマクロとかを普段使っていればどういうことをしてるのかわかると思います。

 4番目の再帰を使ったFizzBuzzですが、理論上for文やwhile文で書けるループは全て再帰で書けるので、必然的に再帰で書けるますね。ただ、実際は再帰を使ったループの方がforやwhileを使うより重い上にメモリを消費するのでforやwhileで書けるときはそっちを使った方がいいです。

 forで書けるならwhileでも書けます。なのでFizzBuzzもwhileで書けますね。まあ、実際はforで書いた方が読みやすいのでforを使う人の方が多いですね。


C++でも実装

#include <iostream>

#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
#include <numeric>

using namespace std;
#define N 100

string fizzbuzz(int x);

int main(){
vector<int> vec_int(N);

// for_eachで実装
for(vector<int>::iterator i = vec_int.begin(); i < vec_int.end(); i++){
*i = distance(vec_int.begin(), i) + 1;
}
for_each(vec_int.begin(), vec_int.end(), [](int element){ cout << fizzbuzz(element) << " "; });
cout << endl;

// accumulateで実装
fill(vec_int.begin(), vec_int.end(), 1);
accumulate(vec_int.begin(), vec_int.end(), 0, [](int x, int y){ cout << fizzbuzz(x) << " "; return x + y; });
cout << endl;

// transformで実装
vector<string> output_string(N);
for(vector<int>::iterator i = vec_int.begin(); i < vec_int.end(); i++){
*i = distance(vec_int.begin(), i) + 1;
}
transform(vec_int.begin(), vec_int.end(), output_string.begin(), [](int input){ return fizzbuzz(input); });
for_each(output_string.begin(), output_string.end(), [](string s){ cout << s << " "; });

return 0;
}
string fizzbuzz(int x){
if(x % 15 == 0){
return "fizzbuzz";
}else if(x % 3 == 0){
return "fizz";
}else if(x % 5 == 0){
return "buzz";
}else{
return to_string(x);
}
}

かなりのループはalgorithmパッケージを使って書き換えられますね。自分はたまに競技プログラミングでforやwhileを使わないでACさせることがあります。ただ、やる人が少ないのは、可読性が落ちるからです。ラムダ式の中で複数の処理を書かれると一気に可読性が落ちます。

2番目のFizzBuzzは、accumulateで配列の要素の総和を計算する際についでに標準出力に書き出す操作をしています。algorithmパッケージのラムダ式を使う関数なら大体似たようなことができますね。

3番目のFizzBuzzで使ってるtransformという関数ですが、rubyやLispではmap関数とか呼ばれてるものですね。ここではint型のvectorをstring型のvectorに変換するために使ってます。

ここでは割愛しますが、listやsetを使っても実装できますね。


Javaでの実装

import java.util.ArrayList;

import java.util.List;

class Main {
public static void main(String[] args){
for(int i = 0; i < 100; i++)
FizzBuzz(i);
System.out.println();

int n = 0;
while(n++ < 100)
FizzBuzz(n);
System.out.println();

Loop(100);
System.out.println();

int length = 100;
List<Integer> numbers = new ArrayList<Integer>(length);
for(int i = 0; i < length; i++){
numbers.add(i);
}

numbers.stream()
.forEach((i) -> { FizzBuzz(i); });
System.out.println();

numbers.stream()
.map((i) -> { return toString(i); })
.forEach((i) -> { System.out.print(i); });
System.out.println();

for(int i = 0; i < length; i++){
numbers.set(i, 1);
}
numbers.stream()
.reduce((i, j) -> { FizzBuzz(i); return i + j; });
System.out.println();
}

static void FizzBuzz(int n){
if(n % 15 == 0)
System.out.print("FizzBuzz");
else if(n % 3 == 0)
System.out.print("Fizz");
else if(n % 5 == 0)
System.out.print("Buzz");
else
System.out.print(n);
System.out.print(" ");
}

static void Loop(int n){
if(n <= 0)
return;
Loop(n - 1);
FizzBuzz(n);
}
static String toString(int n){
if(n % 15 == 0)
return "FizzBuzz ";
else if(n % 3 == 0)
return "Fizz ";
else if(n % 5 == 0)
return "Buzz ";
else
return Integer.toString(n) + " ";
}
}

1番目、2番目、3番目はCでも実装したfor文、while文、再帰での実装です。

4番目のArrayListでの実装ですが、LinkedListでも同じように実装できるはずです。ただ、LinkedListの使い心地があまり良いものではなかったのでこちらを使いました。ArrayListで気をつけなければいけないのが、一番最初の変数宣言の時点で要素の数を指定しないと要素を追加するたびに配列を作り直すので、配列の長さの分時間がかかる点ですね。

5,6,7番目のstreamという関数ですが、これはC#でいうLinqと同じです。Linqと違うのはSQLのような構文が使えない点ですね。

C++でいうaccumulateがJavaでreduceという関数になっていたのでこれを使いました。畳み込み関数大好き。同じようにC++でいうtransformという関数はJavaではmapという関数になっていました。

比較してみるとわかると思いますが、C++のalgorithmパッケージの関数を使うよりJavaのStream使った方が読みやすいですね。