LoginSignup
1
2

More than 3 years have passed since last update.

RとPython書き比べ(ユークリッドの互除法)

Last updated at Posted at 2020-07-30

はじめに

「RとPython,やるならどっち?」
こんな感じの記事が世の中には溢れています。
「R Python」とかで検索した日には終わらない論争を目の当たりにすることとなるでしょう。
一般的には,「Rはデータ解析特化・アカデミアでの使用が多い」
「Pythonは汎用的・ビジネスサイドで多用」のような括りが語られています。

ただ,こういった記事を読んだところで
実際にコーディングするときに,どこがどう違うのか,ということはわからない。
やはり違いを知るには両方やってみる必要がありそう…
そう思い立って簡単なプログラムを両方の言語で書いてみようということになりました。

ちなみに筆者はR歴1年で,普段はTidyverseでdata.frameをいじいじしているため,
今回のような制御構文を書くことはほとんどありません。
Pythonもやっと環境構築を終えたばかりなので,今回の記事はその練習用にもなっています。
間違い・もっといいコードあるよというコメントがあればじゃんじゃんお願いします。

ユークリッドの互除法

今回実装するユークリッドの互除法についての説明を置いておきます。(高校数学なつかしい)

ユークリッドの互除法は2つの自然数の最大公約数を求めるアルゴリズムです。
そのために以下の性質を利用します

$a, bを自然数とする$
$等式:a = bq + rにおいて,gcd(a, b) = gcd(b, r)が成り立つ$

この性質を利用して,
「$a$を$b$で割って余り$r_1$を算出」→「$b$を$r_1$で割って余り$r_2$を算出」→…
→「$r_{n-1}$を$r_n$で割ると割り切れた」
→$gcd(r_{n-1}, r_n) = gcd(r_{n-2}, n_{n-1})= ...=gcd(b, r_1) = gcd(a, b) = r_n$
という形で最大公約数を求めます。

Rで関数を実装

  • 関数定義
# ユークリッドの互除法
gcd <- function(a, b){
  if (!(a %% 1 == 0 & b %% 1 == 0 & a > 0 & b > 0)) {
    cat('入力が自然数じゃないのでやり直し')
    } else if (a < b) {
      w <- a
      a <- b
      b <- w

      while (b != 0) {
        r <- a %% b
        a <- b
        b <- r
        }
      return(a)      
      } else {
        while (b != 0) {
          r <- a %% b
          a <- b
          b <- r
          }
        return(a)
      }
  }
  • 実行結果
> gcd(50856, 96007)
[1] 163

Pythonで実装

  • 関数定義
# ユークリッドの互除法
def gcd(a, b):
  if not (a % 1 == 0 and b % 1 == 0 and a > 0 and b > 0):
    print('入力が自然数じゃないのでやり直し')
  elif a < b:
    w = a
    a = b
    b = w

    while not b == 0:
      r = a % b
      a = b
      b = r
    else:
      return(a)
  else:
    while not b == 0:
      r = a % b
      a = b
      b = r
    else:
      return(a)
  • 実行結果
>>> gcd(50856, 96007)
163

両言語の比較

両者のコーディングの特徴について見ていきます。
(筆者の属性を踏まえた主観的判断であることに注意!)

1. ざっと見た感じ

  • Pythonはインデントがそろっててキレイ(こなみ)
  • Rは括弧を多用する→どの括弧とどの括弧が対応しているかが分からなくなりがち

Pythonはほぼ初めて書くといってもいいくらいで,
「これ閉じてないけど,ちゃんと動くんかな…?」と心配になったりもしました。
でもばっちり動く。この辺の手軽さが支持されているポイントでしょうか。

Rは括弧で括ることで,Pythonはインデントを揃えることで,処理の初めと終わりを示しますが,
インデントを用いるほうが,「処理+ユーザーが目視しやすい」という点で優れているかもしれません。
(Rstudioの新機能で,対応する括弧に色がつくというウワサを聞いたので期待)

2. 制御構文

動作 R Python
関数定義 name <- function(引数){処理} def name(引数):処理
条件分岐1 if(条件式){処理} if 条件式:処理
条件分岐2 else if(条件式){処理} elif 条件式:処理
繰り返し while(条件式){処理} while 条件式:処理

これはだいたい同じ。強いて言えばelse ifかelifの違いくらい。

3. 演算子など

動作 R Python
整数商 %/% //
剰余 %% %
論理積 & and
論理和 | or
否定 ! not

Rでは一貫して記号で演算子が与えられている一方,
Pythonは条件分岐に関わる部分はアルファベットが用いられている。

Rの論理演算子がfilter処理とかで多用されることがイメージされている一方,
Pythonはもっぱら条件分岐での使用がイメージされてそう?
if not ~とかは自然に読みやすいけど,filter(a == 1 and b <= 3 and ~)は長くなって読みにくいみたいな)

おわりに

以上雑な比較でしたが,両言語のメリット・デメリットが見えてきたのではないかと思います。
個人的にはPythonの制御構文の書きやすさに驚いたといった印象です。

今回は制御構文という言語の根幹(=思想が色濃く出る部分)を比較しましたが,
余裕があればデータフレーム処理などの比較もやっていきたいと思います。

追記

制御フローの見直し

コードの無駄が多いなと感じたので,諸々を参考にしつつ書き直し。

R

  • 変数の代入の部分を;を用いて一列にできるらしい(やってることは変わらない)
  • Pythonみたいなa, b = b, aという書き方はできず,中間変数を使わざるを得ない
gcd <- function(a, b){
  if (!(a %% 1 == 0 & b %% 1 == 0 & a > 0 & b > 0)) {
    cat('入力が自然数じゃないのでやり直し')
  } else {
    if(a < b){
      tmp <- a; a <- b; b <- tmp
    }
    while(b != 0){
      r <- a %% b; a <- b; b <- r 
    }
    return(a)
  }
}

Python

  • a, b = b, aという記法が大変便利。スワップ処理とかで中間変数が必要ない。
def gcd(a, b):
  if not (a % 1 == 0 and b % 1 == 0 and a > 0 and b > 0):
    print('入力が自然数じゃないのでやり直し')
  else:
    if a < b:
      a, b = b, a
    while b != 0:
      a, b = b, a % b
    else:
      return b

再帰関数を用いた実装

再帰関数をコメントで教えてもらったので実装してみた。
注意点として,b == 0になるまで繰り返してしまうと,引数が自然数という条件に反してしまうので,その一回前(a % b == 0)まで繰り返すように書き換える必要がある。

  • Python
def gcd(a,b):
  if not (a % 1 == 0 and b % 1 == 0 and a > 0 and b > 0):
    print('入力が自然数じゃないのでやり直し')
  else:
    if a < b:
      a, b = b, a
    if not a % b == 0:
      return gcd(b, a % b)
    else:
      return b
  • R

再帰関数を呼び出す用のRecallという関数もある

gcd <- function(a, b){
  if (!(a %% 1 == 0 & b %% 1 == 0 & a > 0 & b > 0)) {
    cat('入力が自然数じゃないのでやり直し')
  } else {
      if(a < b){
        tmp <- a; a <- b; b <- tmp
      }
      if(a %% b != 0){
        return(Recall(b, a %% b) # またはgcd(b, a %% b)
      } else{
        return(b)
      }
  }
}
1
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2