RでFizzBuzz問題を解いてみる~ifelse関数のベンチマーク~

FizzBuzz問題とは、

入力:数字ベクトル (長さ1以上)

出力:各要素に対して、3の倍数のときは”Fizz”、5の倍数のときは”Buzz”、3と5両方の倍数の場合には”FizzBuzz”、その他の場合は数字を文字列化する

1:100を入力とすると、 (“1”, “2”, “Fizz”, “4”, “Buzz”, “Fizz”, …という出力になる)

という問題です。

FizzBuzz問題を、Rで高速に計算する実験をしてみます。

高速化のカギとなるのはif文をR特有のベクトル演算処理で行う、ifelse関数です。ifelse関数とは文字通り、if文の分岐処理をベクトルの要素ごとに行う関数で、Rでは基本パッケージのifelse関数以外にも、dplyrパッケージのif_else、data.tableパッケージの高速化版fifelseがあります。どれも機能は同じです。

 

今回はifelseを使わない場合と、上記3つのifelseを使って実行速度の比較をしてみます。

 

方法① 要素ごとにforループを回す。

Rに詳しくない人がまず思いつく方法です。

func.naive <- function(x)
{
    out <- rep("", length(x))
    for (i in seq_along(x)) {
        if (x[i] %% 3 == 0 & x[i] %% 5 == 0)
          out[i] <- "FizzBuzz"
        else if (x[i] %% 3 == 0)
          out[i] <- "Fizz"
        else if (x[i] %% 5 == 0)
          out[i] <- "Buzz"
        else
         out[i] <- as.character(x[i])
    }
    out[out == ""] <- as.character(x[out==""])
    out
}

入力データを作ります。1~1000万までの自然数からランダムに100万個を抽出したベクトルを作ります。

> set.seed(1234) # 再現性確保のためにシードを固定します。
> x <- sample(1e+09, 1e+06)

実行結果①

> time <- system.time(func.naive(x))
> system.time(out <- func.naive(x))
   ユーザ   システム       経過  
      3.48       0.01       3.52 

約3.5秒。遅いです。

方法② ifelseを使う

if文の各要素のループをベクトル演算で一気にやってしまうのが、ifelse関数です。

 

func0 <- function(x)
{
    # 
    # base:ifelseを使う場合
    # 
    is3 <- x %% 3 == 0 # 3の倍数かどうかのboolean
    is5 <- x %% 5 == 0 # 5の倍数かどうかのboolean
    
    out <- ifelse(is3 & is5, "FizzBuzz",
       ifelse(is3, "Fizz",
      ifelse(is5, "Buzz", "")))
    
    out[out == ""] <- as.character(x[out==""])
    out
}

実行結果②

> system.time(out <- func0(x))
   ユーザ   システム       経過  
      1.93       0.11       2.04 

方法①から約1秒速くなりました。

 

方法③ dplyrパッケージのif_elseを使う場合

方法②のifelseの部分をdplyr::if_elseで置き換えます。

func1 <- function(x)
{
    # 
    # dplyr:if_elseを使う
    # 
    is3 <- x %% 3 == 0
    is5 <- x %% 5 == 0
    out <- dplyr::if_else(is3 & is5, "FizzBuzz",
       dplyr::if_else(is3, "Fizz",
      dplyr::if_else(is5, "Buzz", "")))
    out[out == ""] <- as.character(x[out==""])
   out
}

実行結果③

> system.time(out <- func1(x))
   ユーザ   システム       経過  
      0.76       0.07       0.84 

1秒を切りました。

 

方法④ data.tableパッケージのfifelseを使う。

func2 <- function(x)
{
    # 
    # data.table::fifelseを使う
    # 
    is3 <- x %% 3 == 0
    is5 <- x %% 5 == 0
    out <- data.table::fifelse(is3 & is5, "FizzBuzz",
       data.table::fifelse(is3, "Fizz",
      data.table::fifelse(is5, "Buzz", "")))
    out[out == ""] <- as.character(x[out==""])
    out    
}

実行結果④

> system.time(out <- func2(x))
   ユーザ   システム       経過  
      0.48       0.03       0.51 

0.5秒を切りました!

 

まとめ

計算速度の比較結果

結論、data.table::fifelseが爆速でした。