こんにちは、usagi-sanです。
今回は、R言語で標準入力と標準出力の方法について紹介します。
現在、就活中の私が選考の1つであるプログラミング試験の対策としてこの記事を書きました。
だいたいのプログラミング試験では、標準入力や標準出力の問題が出題されます。
データ解析に特化したスクリプト言語であるRを使っている方は普段、標準入力や標準出力に触れる機会が滅多にないと思います。プログラミング試験があるけど、データサイエンティストとか目指してるからRしか分からないという方に向けて解説していきます。
今回紹介する標準入力・標準出力の問題はAtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~で紹介されているAtCoderで解くべき10問から引用しました。
解答は、R言語をベースにしており、R言語が苦手なfor文を極力使わないように書いています。
R言語ならではのテクニックが沢山あるので、ぜひぜひ参考にしてください。
今回の用いるプログラミングコードを保存したスクリプトファイルを以下からダウンロードできます。
R言語 標準入力・標準出力 Rスクリプト
ぜひぜひ、問題を解くときのテンプレートとしてご使いください。
標準入力・標準出力のやり方
R言語で標準入力と標準出力を行う方法を解説していきます。
ここでは、R Studioをエディターとして使用している方に向けて解説していきます。
標準入力
標準入力を行うには、関数readLinesを用います。
次のように関数readLinesの引数に、標準入力stdin()と入力を受け付ける行数nを指定することで、n行分の標準入力を実行することができます。
1 | input <- readLines(stdin(), n = 2) |
上の例では、n=2行分の入力を受け付けて、入力された文字列をinputに代入しています。
実際に次のように、1行目と2行目に整数と文字列を入力してみます。
1 2 3 | > input <- readLines(stdin(), n = 2) 2 8 6 82 2 usagisan 統計学 |
inputを参照すると、inputは1行目と2行目で入力された文字列を要素に持つベクトルであることが分かります。
1 2 3 4 5 6 | > input [1] "2 8 6 82 2" "usagisan 統計学" > input[1] [1] "2 8 6 82 2" > input[2] [1] "usagisan 統計学" |
これらの整数と文字列をスペース(空白文字)区切りで取得したいときには関数strsplitを用いましょう。
次のようにstrsplitの引数に文字列のベクトルと区切り文字を" "を指定することでスペース区切りで取得することが可能です。
1 2 3 4 5 6 7 8 9 | > strsplit(input, " ") [[1]] [1] "2" "8" "6" "82" "2" [[2]] [1] "usagisan" "統計学" > strsplit(input, " ")[[1]] [1] "2" "8" "6" "82" "2" |
また、1行ずつ取得した場合は次のように表現することもできます。
1 2 | > unlist(strsplit(input[1], " ")) [1] "2" "8" "6" "82" "2" |
ココに注意
標準入力を行うために、readLinesの最初の引数に"stdin"を指定し、readLines("stdin")とすることもできますが、R studio上で行うとタスクがキルされるまで入力を受け付けるようになってしまい、一切R studioが使えなくなってしまいます。
標準出力
標準出力を行うには、関数catを用います。
cat関数の引数には、次のように出力したオブジェクトを代入します。
1 2 3 4 5 6 7 8 | > cat("ウサギさんの統計学サロン") ウサギさんの統計学サロン > cat(1234567890) 1234567890 cat(1234567890, "usagisan", c("hoge", "temp", "bar"), 0.999) 1234567890 usagisan hoge temp bar 0.999 |
他の言語と違い、型を指定することなく複数の文字列や実数を出力することが可能なので、非常に柔軟性が高いです。
簡単に標準出力を実行することが可能です。
問題集
問題1 Welcome to AtCoder
3つの整数a, b, cを1行目に入力し、文字列sを2行目に入力すると、a+b+cとsを出力する問題です。
以下が、入力と出力の1例です。
1 2 | 3 7 2 usagi-san |
1 | 12 usagi-san |
次の関数output_numberAndStringがこの問題の解答となります。
1 2 3 4 5 6 | output_numberAndString <- function() { input <- readLines(stdin(), n =2) num <- unlist(strsplit(input[1], " ")) str <- unlist(strsplit(input[2], " ")) cat(sum(as.numeric(num)), str) } |
2行目で、関数readLinesを用いてn=2行分の入力を受け付けます。
問題2 Product
整数aとbを1行目に入力し、aとbの積が偶数であるか奇数であるかを出力するのが問題となります。
以下が、入力と出力の1例です。
1 | 5 7 |
1 | Odd |
次の関数isProduct_evenOrOddがこの問題の解答となります。
1 2 3 4 5 6 7 8 9 10 11 | isProduct_evenOrOdd <- function() { input <- readLines(stdin(), n = 1) nums <- as.numeric(unlist(strsplit(input, " "))) product <- prod(nums) if (product %% 2 == 0) { cat("Even") } else { cat("Odd") } } |
2行目にreadLinesを用いてn=1行分だけ入力を受け付けています。
3行目で、as.numeric(unlist(strsplit(input, " ")))を実行することにより、2行目の入力"a b"をスぺ―ス区切りで分割し、実数のベクトルc(a, b)に変化しています。
次に、4行目のように関数prodでaとbの積を計算します。
最後に5行目以降で、aとbの積productが2で割り切れるか、割り切れないかで、偶数であるか奇数であるかを判別しています。
問題3 Placing Marbles
0と1が並んだものがあり1行目に入力され、その中に1がいくつあるかを出力する問題です。
以下が、入力と出力の1例です。
1 | 101110011 |
1 | 6 |
次の関数countUp_oneがこの問題の解答となります。
1 2 3 4 5 | countUp_One <- function() { input <- readLines(stdin(), n = 1) numOfOne <- length(grep("1", unlist(strsplit(input, "")))) cat(numOfOne) } |
同様に2行目にn=1行分入力を受け付けます。
3行目以降では、unlist(strsplit(input, ""))を実行し文字列inputを1文字ずつ分割しています。その次に分割した文字列に対し、関数grepを用いて"1"の位置のインデックスを取得し、そのインデックスのベクトルの長さを求めることで、1の数を計算しています。
問題4 Shift only
1行目に整数を入力し、2で割り切れる回数が最も小さい整数についてその割り切れる回数を求めるのが問題です。
以下が、入力と出力の1例です。
1 | 8 16 32 64 |
1 | 3 |
次の関数maxTimes_DivideByTwoがこの問題の解答となります。
1 2 3 4 5 6 7 8 9 10 11 | maxTimes_DividedByTwo <- function() { input <- readLines(stdin(), n = 1) nums <- as.numeric(unlist(strsplit(input, " "))) tempNums <- nums timesDivide <- rep(0, length(nums)) while (any(tempNums %% 2 == 0)) { timesDivide[tempNums %% 2 == 0] <- timesDivide[tempNums %% 2 == 0] + 1 tempNums[tempNums %% 2 == 0] <- (tempNums[tempNums %% 2 == 0]) %/% 2 } cat(min(timesDivide)) } |
2行目で、n=1行分の入力を受け付けます。
3行目で、1行目で入力した整数をnumsとして保存し、4行目に一時的にnumsのコピーをとっています。
5行目で割り切れる回数のベクトルtimesDivideをc(0, . . . , 0)で定義しています。
6行以降では、any(tempNums %% 2 == 0)のとき、すなわち2で割り切れる整数がtempNumsにある場合while文により処理を繰り返しています。
7、8行目をみると、tempNumsが2で割り切れるインデックスのtimesDivide(timesDivide[tempNums %% 2 == 0] )をインクリメントしています。tempNumsがc(3, 4, 2)の場合timesDivideはc(0, 0, 0)からc(0, 1, 1)に更新されます。8行目では、tempNumsが2で割り切れるインデックスのtempNumsを2で割った商に更新しています。
これをwhile文で繰り返すことで、最終的にそれぞれの整数の2で割り切れる回数を得ることができます。
最後に9行目で、関数minを用いて、割り切れる回数timesDivideの最小値を得ています。
問題5 Coins
1行目に500円玉の枚数、2行目に100円玉の枚数、3行目に50円玉の枚数、4行目に合計金額Xを入力し、与えられた500円玉、100円だま50円玉で合計金額Xを作る方法が何通りあるかを計算する問題です。
以下が、入力と出力の1例です。
1 2 3 4 | 3 7 10 600 |
1 | 8 |
次の関数comb_coinsがこの問題の解答となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | comb_coins <- function() { input <- readLines(stdin(), n = 4) yen500 <- as.numeric(input[1]) yen100 <- as.numeric(input[2]) yen50 <- as.numeric(input[3]) x <- as.numeric(input[4]) numOfCombs <- 0 for (i in 0 : yen500) { for (j in 0 : yen100) { for (k in 0 : yen50) { if(500 * i + 100 * j + 50 * k == x){ numOfCombs <- numOfCombs + 1 } } } } cat(numOfCombs) } |
2行目で、n=4行分入力を受け付けて、500円玉、100円玉、50円玉、合計金額をそれぞれ入力します。
7行目で、何通りあるかを保存する変数numOfCombsを定義します。
8行目以降では、500円玉、100円玉、50円玉の枚数分だけfor分によって、合計金額Xに等しくなるかを全探索しています。等しい場合は、numOfCombsを1だけインクリメントします。
問題6 Some Sums
1行目に整数N、2、3行目に整数A<Bを入力し、1からNまでの整数の各桁の和がA以上B以下であるもの総和を求めるのが問題です。
以下が、入力と出力の1例です。
1 2 3 | 10 3 8 |
1 | 33 |
次の関数sum_about_digidがこの問題の解答となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 | sum_about_digit <- function() { input <- readLines(stdin(), n = 3) N <- as.numeric(input[1]) A <- as.numeric(input[2]) B <- as.numeric(input[3]) sum <- 0 for (i in 1:N) { if (A <= sumOfDigid(i) & sumOfDigid(i) <= B) { sum <- sum + sumOfDigid(i) } } cat(sum) } |
2行目で、readLinesによってn=3行分入力を受け付けます。3行目に整数N、4行目にA、5行目にBをinputから代入しています。
6行目で、 A以上B以下である整数の数sumを定義します。
7行目以降で、for文によって整数1からNまでの整数の各桁がA以上かつB以下である場合sumにその整数の各桁の和を加えています。
また、整数の各桁の和を計算する関数sumOfDigidは次となります。
1 2 3 4 | sumOfDigid <- function(N) { splitedN <- as.numeric(unlist(strsplit(as.character(N), ""))) return(sum(splitedN)) } |
桁数を求めるために、2行目で整数Nをcharacter型に変換します。
unlist(strsplit(as.character(N), ""))を実行つすることで、文字列を一文字ずつ分割し、文字列の長さの要素を持つベクトルを生成することができます。
このベクトルを実数に変換し、関数sumにより各ベクトルの要素の和を計算することで、各桁の和を計算しています。
問題7 Card Game for Two
1行目に整数を入力し、自分の得点が最大になるようにAliceとBobが準にその整数を選択していきます。最終的なAliceとBobの点数の差を出力するのが問題です。
以下が、入力と出力の1例です。
1 | 85 76 90 24 76 |
1 | 29 |
次の関数getMax_cardGameがこの問題の解答となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | getMax_cardGame <- function() { input <- readLines(stdin(), n = 1) sorted_cards <- sort(as.numeric(unlist(strsplit(input[1], " "))), decreasing = TRUE) points_Alice <- 0 points_Bob <- 0 for (i in seq_len(length(sorted_cards))) { if (i %% 2 == 1) { points_Alice <- points_Alice + sorted_cards[i] } else { points_Bob <- points_Bob + sorted_cards[i] } } cat(points_Alice - points_Bob) } |
2行目で、n=1行分の整数の入力を受け付けます。
3行目で入力された整数のベクトルas.numeric(unlist(strsplit(input[1], " ")))を関数sortにより降順に並べ替えています。
4、5行目でAliceとBobの得点を保存するための変数points_Aliceとpoints_Bobを定義しています。6行目以降のfor文でiが奇数のときはAliceのターン、偶数のときはBobのターンとして、それぞれの得点にsorted_cardsの要素を加えていきます。
最後にAliceの得点points_AliceとBobの得点points_Bobの差points_Alice - points_Bobを求めています。
問題8 Kagami Mochi
1行目に鏡餅の重さを表す整数が入力されます。次にその鏡餅を下から重たい順に積んでいきます。同じ重さの鏡餅は積むことができないと仮定し、鏡餅を何段積むことができるか計算するのが問題です。
以下が、入力と出力の1例です。
1 | 89 72 6 56 77 77 89 25 33 33 20 |
1 | 8 |
次の関数stack_kagamimochiがこの問題の解答となります。
1 2 3 4 5 | stack_kagamiMochi <- function() { input <- readLines(stdin(), n = 1) mochi <- as.numeric(unlist(strsplit(input[1], " "))) cat(length(unique(mochi))) } |
2行目で、readLinesを用いて整数を読み込んでいます。
3行目でinputをスペース区切りで分割し実数値に変換しています。
同じ重さの鏡餅を削除し、重い方から積んでいったときの鏡餅の量が答えとなるため、重さのベクトルmochiを、関数uniqueにより重複削除します。この重複削除したベクトルの長さが解答となります。
問題9 Otoshidama
1行目に金額が入力されます。1000円札、5000円札、10000円札だけで、その金額を作ることができるかを答えるのが問題となります。
以下が、入力と出力の1例です。
1 | 77000 |
1 | TRUE |
次の関数can_sumUpWithBillがこの問題の解答となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | can_sumUpWithBill <- function() { input <- readLines(stdin(), n = 1) money <- as.numeric(input[1]) bills <- c(10000, 5000, 1000) if (money %% Reduce(gcd, bills) == 0 ) { cat(TRUE) } else { cat(FALSE) } } gcd <- function(a, b) { if (b == 0) {return(a)} return(gcd(b, a %% b)) } |
2行目で、n=1行分の入力を受け付けます。
3行目で、inputを実数に変換し、金額を表す変数moneyに代入します。
4行目では、10000円札、5000円札、1000円札を表すベクトルをbillsと定義しています。
「10000円札、5000円札、1000円札だけで金額moneyを作ることができるか」は「10000と5000と1000の最大公約数でmoneyを割り切ることができるか」という問題に帰着できます。
そのため上記の最大公約数を求める関数gcdを用います(ユークリッドの互除法を用いています)。
5行目以降では、関数Reduceによって、billsの要素を順にgcdの引数に代入し3つの数の最大公約数を求めています。
その最大公約数でmoneyを割り切れる場合はTRUEを返し、そうでない場合はFALSEを返しています。
また補足として、最小公約数を求める関数lcmも作りました。
1 2 3 | lcm <- function(a, b) { return(a * b %/% gcd(a, b)) } |
問題10 Daydream
1行目に文字列が入力されます。入力された文字列が"dream", "dreamer", "erase", "eraser"で構成されているかを答えるのかが問題です。
以下が、入力と出力の1例です。
1 | eraserdreamerdreameraseeraser |
1 | TRUE |
次の関数dreamerEraserがこの問題の解答となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | dreamerEraser <- function() { input <- readLines(stdin(), n = 1) strs <- input[1] terms <- c("dream", "dreamer", "erase", "eraser") perm <- permutation(terms, 4) for (i in seq_len(ncol(perm))) { copyStrs <- strs for(j in perm[, i]) { copyStrs <- gsub(j, "", copyStrs) } if (copyStrs == "") { return(cat(TRUE)) } } cat(FALSE) } permutation <- function(vector, k) { dataFrame <- data.frame(matrix(NA, nrow = k)) colCount <- 1 findPerm <- function(vector, permVector, k) { if (k == 0) { dataFrame[, colCount] <<- permVector colCount <<- colCount + 1 return() } for (i in vector) { findPerm(setdiff(vector, i), c(permVector, i), k - 1) } } findPerm(vector, "", k) return(dataFrame) } |
2行目で、readLinesを用い、n=1行分入力を受けています。
3行目で文字列を保存し、4行目で"dream", "dreamer", "erase", "eraser"の要素を持つベクトルを定義しています。
ここで入力された文字列strsが上の4つの単語で構成されているかを知るには、strsの文字列の中からtermsの要素に一致するものを、termsの全ての順序で削除していき、全て削除できたらtermsだけで構成されていることが確認できます。
全ての順序を求めるには、上で定義した関数permutationを用います。permutationはその関数内でローカルに定義された関数permを再帰的に実行することで、全順序をデータフレームに保存していきます。
あとは、この関数permuatationを用いて、全順序をpermに代入し、その全順序permの各列についてfor文で全探索することで、解を見つけることが可能です。
問題11 Traveling
1行目に何回実行するかという入力をし、次にその回数分だけ、時間とx, y座標を入力し、時間丁度に原点0, 0からx, y座標にいることができるかを判定するのが問題です。ここで、1単位時間に上下左右の1つずつしか移動できないと仮定しています。
以下が、入力と出力の1例です。
1 2 3 4 | 3 9 2 3 6 7 9 12 4 4 |
1 2 3 | TRUE FALSE TRUE |
次の関数travelingがこの問題の解答となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | traveling <- function() { inputNum <- readLines(stdin(), n = 1) input <- readLines(stdin(), n = as.numeric(inputNum[1])) for (i in lapply(strsplit(input, " "), as.numeric)) { times <- i[1] x <- i[2] y <- i[3] if ((abs(x) + abs(y) - times) <= 0 & (abs(x) + abs(y) - times) %% 2 == 0) { cat(TRUE, "\n") } else { cat(FALSE, "\n") } } } |
2行目で、行目で何回判定するかを表す変数inputNumに入力を渡します。
3行目で、inputNumの数だけt 、x、yという入力を受け付けます。
4行目以降のfor文では、lapply関数を用いて、strsplit(input, " ")というリストをas.numericにより実数にしています。そのlapplyで作ったリストの要素に対して、times、x、yを定義し、times丁度にx, y座標にいることができるかを判定していきます。
判定基準は「x座標とy座標のそれぞれの絶対値の和から時間を引いたものがマイナスであり、x座標とy座標のそれぞれの絶対値の和から時間を引いたものが2で割り切ることができるか」となります。
上の条件を満たしていたら、TUREを返し、そうでなければ、FALSEを返します。
まとめ
R言語で標準入力と標準出力をする方法を解説しました。
標準入力や標準出力は、全然解析と関係ないですが、解析以外の部分(データクレンジングや解析結果の編集)で今回まとめたテクニックがすごい役に立ちます。
どんどん練習して、自分の実力を高めていってください。