- 投稿日:2021-12-30T18:15:32+09:00
『エラトステネスの篩』各言語記述例まとめHi
タイトル通りです.筆者過去まとめ記事と趣旨は同じなのですが,一説に『ネット上のエラトステネスの篩のプログラムの大半がエラトステネスの篩じゃない』というのがあるらしく,その割合を少しでも減らす,という目論見もあったりします.なにそれ. 初版で用意した各言語記述例については,とりあえずWikipediaの説明に沿っているつもりですが,上記の記事の趣旨から,それちげーよというツッコミ大歓迎.また,最初にPython版を作成してそれを基に各言語に書き換えたという経緯から,各言語の特徴を活かした,よりシンプルでわかりやすい記述例も大募集.ただし,次の条件を満たすプログラムとします. アルゴリズムが『エラトステネスの篩』であること. 1から一億までの範囲の素数を一旦求め,その中で最大の素数(99999989)を出力すること. 差し替える際には,当方の次の環境で実行を確認することにしています.なお,非力なハードウェアなのはわざとです(^^;).この環境で実行できる限り,他のプログラミング言語の記述例も募集中. ASUS Zenfone 5: Qualcomm Snapdragon 636 1.8GHz, 6GBメモリ Android 9 + Termux + Debian GNU/Linux 10 + 各言語処理系 $ uname -a Linux localhost 4.4.141-perf+ #1 SMP PREEMPT Fri Jul 10 17:48:58 CST 2020 aarch64 GNU/Linux 各言語記述例 Python sieve.py from numpy import ones, sqrt, arange x = int(input()) a = ones(x+1, dtype=bool) a[0:2] = False for i in range(2, int(sqrt(x))+1): if a[i]: a[i*i::i] = False print((arange(x+1)[a])[-1]) Python3.7.3+NumPy1.16.2 $ time python3 sieve.py <<< 100000000 99999989 real 0m6.905s user 0m5.260s sys 0m2.140s Ruby sieve.rb x = gets.to_i a = Array.new(x+1, true) a[1] = false 1.upto(x**0.5) { |i| if a[i] (i*i).step(x, i) { |j| a[j] = false } end } r = [] 1.upto(x) { |i| if a[i] then r.push(i) end } p r[-1] Ruby2.5.5 $ time ruby sieve.rb <<< 100000000 99999989 real 0m55.606s user 0m53.630s sys 0m0.870s JavaScript sieve.js let x; if (process.argv.length < 3) x = 10000000; else x = Number(process.argv[2]); let a = new Array(x+1).fill(true); a[1] = false; for (i = 1; i*i <= x; i++) if (a[i]) for (j = i*i; j <= x; j += i) a[j] = false; let r = []; for (i = 1; i <= x; i++) if (a[i]) r.push(i); console.log(r.slice(-1)[0]); Node.js10.24.0 $ time node sieve.js 100000000 99999989 real 0m35.230s user 0m33.300s sys 0m1.310s C言語 sieve.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> int main(int argc, char *argv[]) { int x = 100000000; if (argc > 1) x = atoi(argv[1]); bool *a = calloc(sizeof(bool), x + 1); a[1] = true; for (int i = 1; i * i <= x; i++) if (!a[i]) for (int j = i * i; j <= x; j += i) a[j] = true; unsigned int c = 0; for (int i = 1; i <= x; i++) if (!a[i]) c++; unsigned int *r = calloc(sizeof(unsigned int), c + 1); for (int i = 1; i <= x; i++) if (!a[i]) { r[c] = i; c--; } printf("%u\n", r[1]); return 0; } gcc8.3.0 $ gcc -O2 -o sieve sieve.c $ time ./sieve 100000000 99999989 real 0m4.061s user 0m3.920s sys 0m0.090s Scheme sieve.scm (define (sieve x) (let ((a (make-vector (+ x 1) #t)) (s (sqrt x))) (vector-set! a 1 #f) (let loop1 ((i 1)) (if (<= i s) (if (vector-ref a i) (let loop2 ((j (* i i))) (cond ((<= j x) (vector-set! a j #f) (loop2 (+ j i))) (else (loop1 (+ i 1))))) (loop1 (+ i 1))))) (let loop ((i 0) (r '())) (if (<= i x) (if (vector-ref a i) (loop (+ i 1) (cons i r)) (loop (+ i 1) r)) r)))) (display (car (sieve (read)))) (newline) Gauche0.9.11-p1 $ time gosh sieve.scm <<< 100000000 99999989 real 0m32.206s user 0m31.180s sys 0m0.550s Julia sieve.jl x = parse(Int, readline()) a = trues(x) a[1] = false for i in 2:isqrt(x) a[i] && (a[i*i:i:x] .= false) end println((1:x)[a][end]) Julia1.0.3 $ time julia sieve.jl <<< 100000000 99999989 real 0m3.354s user 0m2.950s sys 0m1.460s R sieve.r f <- file("stdin", "r") x <- as.numeric(readLines(f, 1)) a <- 1:x a[1] <- 0 for (i in 2:floor(sqrt(x))) if (a[i]) a[i:(x %/% i)*i] <- 0 r <- a[a > 0] r[length(r)] R3.5.2 $ time R -f sieve.r --slave <<< 100000000 [1] 99999989 real 0m18.204s user 0m14.550s sys 0m4.180s 備考 更新履歴 2021-12-31:各言語間で変数名を可能な限り統一 2021-12-31:C言語版の実行確認を修正(コメントより) 2021-12-31:R版を追加(コメントより) 2021-12-31:添字の扱い等について各言語版に修正 2021-12-31:実行確認を『1から一億までの範囲の素数』に変更 2021-12-31:Julia版を追加(コメントより) 2021-12-30:Python版を変更(コメントより) 2021-12-30:初版公開(Python,Ruby,JavaScript,C言語,Scheme)
- 投稿日:2021-12-30T18:15:32+09:00
『エラトステネスの篩』各言語記述例まとめ
【2022-01-02追記】『別館』としていた次の言語版は,個別記事に分離しました. シェルスクリプト版,bash版,awk版,bc版 タイトル通りです.筆者過去まとめ記事と趣旨は同じなのですが,一説に『ネット上のエラトステネスの篩のプログラムの大半がエラトステネスの篩じゃない』というのがあるらしく,その割合を少しでも減らす,という目論見もあったりします.なにそれ. 初版で用意した各言語記述例については,とりあえずWikipediaの説明に沿っているつもりですが,上記の記事の趣旨から,それちげーよというツッコミ大歓迎.また,最初にPython版を作成してそれを基に各言語に書き換えたという経緯から,各言語の特徴を活かした,よりシンプルでわかりやすい記述例も大募集.ただし,次の条件を満たすプログラムとします. アルゴリズムが『エラトステネスの篩』であること. 1から一億までの範囲の素数を一旦求め,その中で最大の素数(99999989)を出力すること(別館除く). 差し替える際には,当方の次の環境で実行を確認することにしています.なお,非力なハードウェアなのはわざとです(^^;).この環境で実行できる限り,他のプログラミング言語の記述例も募集中. ASUS Zenfone 5: Qualcomm Snapdragon 636 1.8GHz, 6GBメモリ Android 9 + Termux + AnLinux(Debian GNU/Linux 10) + 各言語処理系 $ uname -a Linux localhost 4.4.141-perf+ #1 SMP PREEMPT Fri Jul 10 17:48:58 CST 2020 aarch64 GNU/Linux 各言語記述例 Python sieve.py from numpy import ones, sqrt, arange x = int(input()) a = ones(x+1, dtype=bool) a[0:2] = False for i in range(2, int(sqrt(x))+1): if a[i]: a[i*i::i] = False print((arange(x+1)[a])[-1]) Python3.7.3+NumPy1.16.2 $ time python3 sieve.py <<< 100000000 99999989 real 0m6.905s user 0m5.260s sys 0m2.140s Ruby sieve.rb x = gets.to_i a = Array.new(x+1, true) a[1] = false 1.upto(x**0.5) { |i| if a[i] (i*i).step(x, i) { |j| a[j] = false } end } r = [] 1.upto(x) { |i| if a[i] then r.push(i) end } p r[-1] Ruby2.5.5 $ time ruby sieve.rb <<< 100000000 99999989 real 0m55.606s user 0m53.630s sys 0m0.870s JavaScript sieve.js let x; if (process.argv.length < 3) x = 10000000; else x = Number(process.argv[2]); let a = new Array(x+1).fill(true); a[1] = false; for (i = 1; i*i <= x; i++) if (a[i]) for (j = i*i; j <= x; j += i) a[j] = false; let r = []; for (i = 1; i <= x; i++) if (a[i]) r.push(i); console.log(r.slice(-1)[0]); Node.js10.24.0 $ time node sieve.js 100000000 99999989 real 0m35.230s user 0m33.300s sys 0m1.310s C言語 sieve.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> int main(int argc, char *argv[]) { int x = 100000000; if (argc > 1) x = atoi(argv[1]); if (x < 1) return 0; bool *a = calloc(sizeof(bool), x + 1); a[1] = true; for (int i = 1; i * i <= x; i++) if (!a[i]) for (int j = i * i; j <= x; j += i) a[j] = true; unsigned int c = 0; for (int i = 1; i <= x; i++) if (!a[i]) c++; unsigned int *r = calloc(sizeof(unsigned int), c + 1); for (int i = 1; i <= x; i++) if (!a[i]) { r[c] = i; c--; } printf("%u\n", r[1]); return 0; } gcc8.3.0 $ gcc -O2 -o sieve sieve.c $ time ./sieve 100000000 99999989 real 0m4.061s user 0m3.920s sys 0m0.090s Scheme sieve.scm (define (sieve x) (let ((a (make-vector (+ x 1) #t)) (s (sqrt x))) (vector-set! a 1 #f) (let loop1 ((i 1)) (if (<= i s) (if (vector-ref a i) (let loop2 ((j (* i i))) (cond ((<= j x) (vector-set! a j #f) (loop2 (+ j i))) (else (loop1 (+ i 1))))) (loop1 (+ i 1))))) (let loop ((i 0) (r '())) (if (<= i x) (if (vector-ref a i) (loop (+ i 1) (cons i r)) (loop (+ i 1) r)) r)))) (display (car (sieve (read)))) (newline) Gauche0.9.11-p1 $ time gosh sieve.scm <<< 100000000 99999989 real 0m32.206s user 0m31.180s sys 0m0.550s Julia sieve.jl x = parse(Int, readline()) a = trues(x) a[1] = false for i in 2:isqrt(x) a[i] && (a[i*i:i:x] .= false) end println((1:x)[a][end]) Julia1.0.3 $ time julia sieve.jl <<< 100000000 99999989 real 0m3.354s user 0m2.950s sys 0m1.460s R sieve.r f <- file("stdin", "r") x <- as.numeric(readLines(f, 1)) a <- 1:x a[1] <- 0 for (i in 2:floor(sqrt(x))) if (a[i]) a[i:(x %/% i)*i] <- 0 r <- a[a > 0] r[length(r)] R3.5.2 $ time R -f sieve.r --slave <<< 100000000 [1] 99999989 real 0m18.204s user 0m14.550s sys 0m4.180s Common Lisp sieve.lisp (defun sieve (x) (let ((a (make-array (+ x 1) :initial-element t)) (s (sqrt x))) (setf (aref a 1) nil) (do ((i 1 (+ i 1))) ((> i s)) (if (aref a i) (do ((j (* i i) (+ j i))) ((> j x)) (setf (aref a j) nil)))) (do ((i 0 (+ i 1)) (r '() (if (aref a i) (cons i r) r))) ((> i x) r)))) (princ (car (sieve (read)))) (terpri) SBCL1.4.16 $ time sbcl --script sieve.lisp <<< 100000000 99999989 real 0m21.725s user 0m18.960s sys 0m1.740s Perl sieve.pl my $x = <STDIN>; my @a; my $i; my $j; $a[1] = 1; for ($i = 1; $i * $i <= $x; $i++) { if (!$a[$i]) { for ($j = $i * $i; $j <= $x; $j += $i) { $a[$j] = 1; } } } my @r; my $c = 0; for ($i = 1; $i <= $x; $i++) { if (!$a[$i]) { $r[$c] = $i; $c++; } } print $r[$c-1], "\n"; Perl5.28.1 $ time perl sieve.pl <<< 100000000 99999989 real 2m10.029s user 2m0.990s sys 0m4.830s PHP sieve.php <?php declare(strict_types = 1); $x = $argc > 1 ? intval($argv[1]) : 100000000; if ($x < 1) exit; $a = array_fill(1, $x, true); $a[1] = false; for ($i = 1; $i * $i <= $x; $i++) { if ($a[$i]) { for ($j = $i * $i; $j <= $x; $j += $i) { $a[$j] = false; } } } $r = []; $c = 0; for ($i = 1; $i <= $x; $i++) { if ($a[$i]) $r[$c++] = $i; } echo $r[$c - 1], PHP_EOL; PHP7.3.29 $ time php sieve.php 100000000 99999989 real 0m56.325s user 0m53.050s sys 0m2.430s Lua ※インタプリタ実行では一億までの処理が行えず強制終了してしまうため,バイトコードコンパイルで実行しています. sieve.lua x = tonumber(io.read()) a = {} a[1] = true for i = 1, math.sqrt(x) do if not a[i] then for j = i * i, x, i do a[j] = true end end end r = {} for i = 1, x do if not a[i] then table.insert(r, i) end end print(r[#r]) Lua5.3.3 $ luac sieve.lua $ time lua luac.out <<< 100000000 99999989 real 1m0.471s user 0m54.910s sys 0m4.410s Java sieve.java import java.util.ArrayList; class sieve { public static void main(String[] args) { int x = args.length > 0 ? Integer.parseInt(args[0]) : 100000000; if (x < 1) return; boolean[] a = new boolean[x + 1]; a[1] = true; for (int i = 1; i * i <= x; i++) if (!a[i]) for (int j = i * i; j <= x; j += i) a[j] = true; ArrayList<Integer> r = new ArrayList<>(); for (int i = 1; i <= x; i++) if (!a[i]) r.add(i); System.out.println(r.get(r.size() - 1)); } } OpenJDK11.0.12 $ javac sieve.java $ time java sieve 100000000 99999989 real 0m5.017s user 0m7.860s sys 0m0.470s Pascal sieve.pp program eratosthenes; uses SysUtils; var x: longint; a: array of boolean; i: longint; j: longint; c: longint; r: array of longint; begin x := 100000000; if argc > 1 then x := StrToInt(argv[1]); if x < 1 then exit; SetLength(a, x + 1); a[1] := true; i := 1; while i * i <= x do begin if not a[i] then begin j := i * i; while j <= x do begin a[j] := true; j := j + i; end; end; i := i + 1; end; c := 0; for i := 1 to x do if not a[i] then c := c + 1; SetLength(r, c + 1); j := 0; for i := 1 to x do if not a[i] then begin r[j] := i; j := j + 1; end; writeln(r[c - 1]); end. fp-compiler3.0.4 $ fpc -O4 sieve Free Pascal Compiler version 3.0.4+dfsg-22 [2019/01/24] for aarch64 Copyright (c) 1993-2017 by Florian Klaempfl and others Target OS: Linux for AArch64 Compiling sieve.pp Assembling eratosthenes Linking sieve 45 lines compiled, 1.1 sec $ time ./sieve 100000000 99999989 real 0m3.802s user 0m3.680s sys 0m0.070s C++ sieve.cpp #include <iostream> #include <cstdlib> #include <list> int main(int argc, char *argv[]) { int x = 100000000; if (argc > 1) x = atoi(argv[1]); if (x < 1) return 0; bool *a = new bool[x + 1]; a[1] = true; for (int i = 1; i * i <= x; i++) if (!a[i]) for (int j = i * i; j <= x; j += i) a[j] = true; std::list<int> r; for (int i = 1; i <= x; i++) if (!a[i]) r.push_front(i); std::cout << r.front() << std::endl; } gcc8.3.0(GNUCompilerCollection) $ g++ -O2 -o sieve sieve.cpp $ time ./sieve 100000000 99999989 real 0m4.774s user 0m4.180s sys 0m0.330s 備考 更新履歴 2022-01-03:Pascal版の実行確認を修正(コメントより) 2022-01-02:PHP版,Java版,Pascal版,C++版を追加(コメントより) 2022-01-02:別館(POSIXシェル,bash,awk,bc)を個別記事に分離 2022-01-02:Lua版をバイトコードコンパイルの結果に置き換えて本館に移動 2022-01-02:別館を追加(POSIXシェル,bash,awk,bc,Lua) 2022-01-01:Perl版を追加 2022-01-01:Common Lisp版を追加 2021-12-31:各言語間で変数名を可能な限り統一 2021-12-31:C言語版の実行確認を修正(コメントより) 2021-12-31:R版を追加(コメントより) 2021-12-31:添字の扱い等について各言語版に修正 2021-12-31:実行確認を『1から一億までの範囲の素数』に変更 2021-12-31:Julia版を追加(コメントより) 2021-12-30:Python版を変更(コメントより) 2021-12-30:初版公開(Python,Ruby,JavaScript,C言語,Scheme)
- 投稿日:2021-12-30T17:33:46+09:00
【Rails】モデルにインスタンスメソッドを定義する方法
モデルにメソッドを定義する理由 モデルに書くのはvalidatesやbelongs_to,has_manyなどだけでなく、メソッドも定義できる。 もちろんモデルを使わなくても小さいサービスであればコントローラーでも全然問題ないが、 理由として少し大きなサービスになってくると、すぐにコントローラーのコード量が増えてしまうからである。(ファットコントローラー) その時は理解できても、数ヶ月後にコードを見返すと何のコードなのかわからなくなってしまう可能性がある。 例を交えて解説 user_controller.rb def index @user = User.find(1) @my_age = @user.too_young ⬅️ end このtoo_youngがインスタンスメソッドというもので、モデルで書いたメソッドを コントローラーで呼び出している。 user.rb class User < ApplicationRecord validates :username, presence: true validates :email, presence: true #ここがインスタンスメソッド def too_young if self.age >= 20 #20歳以上の場合 return "大人です!" else #20歳以下の場合 return "子供です!" end end end too_youngメソッド内に「self」というものがある。 この「self」は何かというとusers_controller.rbで定義された@userのこと。 @userというインスタンスに対して「.(ドット)」で繋げてインスタンスメソッドを呼び出す時、インスタンスメソッド内では「self」が使え、self=呼び出し元のインスタンス(@user)となる。 インスタントメソッド内のselfは省略可能。 インスタントメソッドとは インスタンスメソッドとは、作成したインスタンスから実行出来るメソッドのこと。 initializeメソッドでインスタンス変数に代入し、情報を保持している。 インスタンスメソッドは、このインスタンスごとに保持している情報を使って処理を書くことが出来る。 インスタントメソッドとは別にクラスメソッドがあるが、使い分けはクラスメソッドは全体で扱うべき情報を扱い、インスタンスメソッドは個々で扱うべき情報を扱うことを認識しておけばいい。 参考記事 Railsのモデルに書いたメソッドってどうやってコントローラで使うの? 【Ruby】 たい焼きで理解するオブジェクト指向におけるクラスの概念 【Ruby基礎】クラスメソッドとインスタンスメソッドの違いについて簡単にまとめてみた
- 投稿日:2021-12-30T17:13:59+09:00
【Ruby のまずいコード】漢字を数える
お題 引数として与えられた文字列中の漢字の個数を数えて返すメソッドを書いてください。 同じ漢字が複数回出てきてもそれぞれカウントします。 文字列は UTF-8 であるとします。 コード def kanji_count(str) str.scan(/[一-龥]/).count end 講評 「すべての漢字」を表す正規表現として [一-龥] は使えません。これでは Unicode の漢字の一部分しか拾えません。 Unicode では,漢字がいくつものブロックに分かれて収録されていますが,そのうちの最も基本的なものが「CJK 統合漢字(CJK Unified Ideographs)」と呼ばれるブロックです。 CJK 統合漢字のブロックは,Unicode バージョン 1.0 のとき,「一」で始まり「龥」で終わっていました。 [一-龥] という正規表現はその時代(30 年前!)の名残でしょう。 改善 「すべての漢字」を表す文字クラスは \p{Han} と書けます。これを使うのが簡単です。 def kanji_count(str) str.scan(/\p{Han}/).count end なお,特定範囲の文字の個数を数えるのに String#count というメソッドがあります。 これを使うと,たとえば a〜z および A〜Z の個数を数えるのは str.count("a-zA-Z") のように書けます。 (ベンチマークテストはしていませんが)scan して count するよりも速いと思います。 ただ,String#count は正規表現が使えません。これで漢字を数えようとすると,Unicode 上のすべての漢字の範囲を調べて書く必要があり,少々面倒です。
- 投稿日:2021-12-30T16:24:34+09:00
【Rails】セレクトボックスを作る方法と注意点【select・collection_select】
Railsのセレクトボックスって、 引数の順番とか指定できる項目の種類が ごちゃごちゃしてて、なんとなーくの理解だったので、 色々調べてみました! なお、間違いやもっと良い方法がある...等ありましたら、 お気軽にコメントください。お願いいたします! 作成方法①:select メソッド select メソッドを使ったセレクトボックスの構文は下記です。 select(オブジェクト名, オプションタグの要素(配列 or ハッシュ), オプション, HTMLオプション) まず、コントローラーでselect に渡す要素を準備する必要があります。どのような方法でも良いですが、例として下記のような取得方法があるでしょう。 app/controllers/user_controller.rb @users = User.pluck(:name, :id) # => [["A太郎", 1], ["B太郎", 2], ["C太郎", 3]] @users = User.all.map { |user| [ user.name, user.id ] } # => [["A太郎", 1], ["B太郎", 2], ["C太郎", 3]] ビュー側は以下のように書きます。 app/views/user/index.html.erb <%= f.select :id, @users, {prompt: "選択してください"}, :class => 'form-control', :id => 'user_id' %> select に指定できるオプション オプションについては、Rails ドキュメント をご覧ください。 複数選択や選択を無効化するなどの詳細な設定ができますよ! 作成方法②:collection_select メソッド【おすすめ】 collection_select メソッドを使ったセレクトボックスの構文は下記です。 collection_select(オブジェクト名, メソッド名, 要素の配列, value属性の項目, テキストの項目, オプション, HTMLオプション, イベント属性) select メソッドの際は、DBから取得したデータを select 用に加工する必要がありましたよね。 もしくは、ビュー内でDBから直接データを取得するような指定をする必要があり、 MVCの分離ができていなかったですよね。 しかし「collection_select」メソッドは加工することなく、セレクトボックスを簡単に作成できます! app/views/user/index.html.erb <%= f.collection_select :id, User.all, :id, :name, {prompt: "選択してください"}, :class => 'form-control', :id => 'user_id' %> まあ、「User.all」だってDBから直接取得してるじゃないか!! と突っ込まれたら、何も言えないのですが...。 それにしても、簡単に記述できるので、個人的にはこちらの方が使いやすいかなーといった感じです。 collection_select に指定できるオプション オプションについては、Rails ドキュメント をご覧ください。 js用のオプションを指定できるのも便利ですよね。 具体的には「onclick」イベントなどですね。 オプション指定の順番は注意! さらには、オプションの指定には注意点もあります。HTMLオプションを指定する際には、先にオプションを必ず指定しなければ、うまく動きません(引数の順番が1つずれちゃうので、当たり前と言えば当たり前なのかもですが) app/views/user/index.html.erb # これはOK <%= f.collection_select :id, User.all, :id, :name, {prompt: "選択してください"}, :class => 'form-control', :id => 'user_id' %> # これもOK <%= f.collection_select :id, User.all, :id, :name, {}, :class => 'form-control', :id => 'user_id' %> # これはNG <%= f.collection_select :id, User.all, :id, :name, :class => 'form-control', :id => 'user_id' %> 参考 【Rails】セレクトボックスを作る方法と注意点【f.select】
- 投稿日:2021-12-30T15:42:46+09:00
【Rails】ルーティングのネストについて
ルーティングのネストとは ルーティングのネストとは、あるコントローラへのルーティングの記述の中に、別のコントローラへのルーティングを記述すること。 例えばTwitterの場合、ツイートに対してコメントできる機能があるその場合、このアプリケーションのルーティング(routes.rb)の記述は以下のようになる。 Rails.application.routes.draw do resources :tweets do resources :comments, only: [:create] end end tweetsコントローラへのルーティングの記述の中に、commentsコントローラへの記述が書かれている。こうすることによって、どのツイートに紐づいたコメントなのかをURLで判別できるようにしている。 メリット ①URLの階層構造ができる 先程のTwitterのルーティング記述により生成されるURLは、「/tweets/id/comments」になる。 もっと具体的に言うと、とあるツイートにコメントすると「/tweets/2/comments」のようなURLが生成される。この「2」という数字はツイートのid番号。 つまり、このコメントは2番のツイートに対するコメントということが、URLから判断することができる。 ②関係性のあるもの同士を紐づけることができる ツイートとコメントの関係のように、ルーティングをネストさせることによって関係性のあるもの同士をを紐づけることができる。 関係があるもの同士は、原則ルーティングをネストさせて関係性のあるURLを生成しよう モデルではこのような関係性を「has_many」と「belongs_to」で表現する 記入法 Rails.application.routes.draw do resources :親となるコントローラー do resources :子となるコントローラー ←階層を下げ、do,,,endで囲む end end 参考記事 【Rails基礎】プログラミング初学者がつまずきやすい「ルーティングのネスト」について簡単に解説 ルーティングのネスト
- 投稿日:2021-12-30T13:16:41+09:00
【Ruby のまずいコード】引数の扱い
お題 引数として文字列の配列が与えられたとき,それらをハイフンで繋いだ文字列を返すメソッドを書いてください。 配列の要素はすべて文字列であると仮定して構いません。 ただし,要素は空文字列の可能性があり,これを除いたものだけをハイフンで繋ぎます。 つまり,以下のような動作をするものとします: p join_with_hyphen(["foo", "bar", "", "baz"]) # => "foo-bar-baz" (メソッド名が英語として妥当でない気がしますが,英語が苦手なのでよく分かりません。得意な方は教えてください) コード def join_with_hyphen(strings) strings.delete("") strings.join("-") end 問題点 このメソッドはお題のとおりに動作します。 しかし,引数のオブジェクトを破壊的に変更しているのは非常に悪い流儀です。残念ながらこういうコードをよく目にします。 join_with_hyphen を呼び出す側では,与えた実引数を他の目的にも使うかもしれません。 その場合,オブジェクトを勝手に変化させられると困るのです。 これは分かりにくいバグの原因になります。 def join_with_hyphen(strings) strings.delete("") strings.join("-") end ary = ["foo", "bar", "", "baz"] p join_with_hyphen(ary) # => "foo-bar-baz" p ary.length # => 3 (えっ?!) 改善 原則として,メソッドは与えられた引数に対して破壊的な操作を行うべきではない,と(初心者のうちは)覚えておきましょう。 今回のお題については Enumerable#grep_v を使って def join_with_hyphen(strings) strings.grep_v("").join("-") end とすればいいでしょう。 一般に, def foo(arg) # なんとかかんとか end というメソッドで,arg が指しているオブジェクトを加工して結果を得たいというとき,非破壊的メソッドを使えばいいのですが,破壊的メソッドを使う必要がある場合は def foo(arg) arg = arg.dup # 複製を作る arg.some_destructive_method # 破壊的操作 # なんとかかんとか end と書くことができます。 さて,与えられた文字列に対してさまざまな変換を順次施す,ということがよくあります。このようなとき,すべて非破壊的メソッドを使って def foo(str) str.downcase .tr(chars1, chars2) .gsub(r1, s1) .gsub(r2, s2) .gsub(r3, s3) .gsub(r4, s4) end のように書くこともできますが,メソッドチェーンの中で次々と String オブジェクトが出来て,それら中間のオブジェクトがすべてガーベジ1になってしまいます。 たいがいそれで問題無いのですが,foo が多数回呼ばれ,str が非常に巨大な文字列であるような場合は望ましくありません。 破壊的メソッドを用いれば無駄なオブジェクトの増加が避けられますが,String#dup を用いずとも def foo(str) str = str.downcase str.tr!(chars1, chars2) str.gsub!(r1, s1) str.gsub!(r2, s2) str.gsub!(r3, s3) str.gsub!(r4, s4) str end のように最初だけ非破壊的メソッドを使えばいいと思います。 余談 この記事の「コード」節で掲げたコードは,うっかり引数を破壊した例でした。 しかし,引数の破壊的変更を意図したメソッドを設計することはあります。 組込みライブラリーでいうと,例えば ObjectSpace.#count_objects が該当します。 これは存在するオブジェクトを種類ごとに数えて,その結果をハッシュとして返すメソッドなのですが,このメソッドの実行によってハッシュが増えてしまうことを防ぐために,結果を格納するハッシュを引数で与えることができるようになっています。 永久に参照されることのないオブジェクト。 ↩
- 投稿日:2021-12-30T10:21:29+09:00
Ruby-FFIのManagedStructは、ObjectSpace#define_finalizerを利用して、GCにオブジェクトが回収される際に、予め登録されたメモリを解放する関数をFFIで呼ぶ
Ruby-FFIでは、C言語の構造体と、Rubyのクラスを関連付けることができる。Rubyのクラスのインスタンスオブジェクトが作成されると、C言語の構造体用のメモリが確保される。しかし、RubyのインスタンスがGCによって回収される時に、C言語の構造体のメモリが解放されるとは限らない。 そこでRuby-FFIでは、RubyのオブジェクトがGCに回収された時に、C言語側でメモリを自動的に解放してくれるクラスManagedStructというものが用意されている。ManagedStructはC拡張でゴリゴリと難しいことをしているのかと思いきや、意外にもシンプルにRubyで実装されている。 その内容を見てみると、ObjectSpace#define_finalizer というものを利用している。これはオブジェクトがGCに回収される時に呼び出されるメソッドであるようだ。コレが呼ばれた時に、メモリ解放用の関数が発火するように登録しておけばいい。 この記事は以上です。
- 投稿日:2021-12-30T10:17:46+09:00
【Ruby のまずいコード】素数判定
お題 引数として与えられた正の整数が素数かどうか判定するメソッドを書いてください。 prime ライブラリーは用いず,2 以上の整数で順に割り切れるかを調べるごくごく素朴な実装としてください。 コード def prime?(n) 2.upto(Math.sqrt(n)) do |k| return false if n % k == 0 end true end 2, 3, 4, 5, 6, ... で割った剰余を求め,割り切れたら false を返しています。 どれでも割り切れなかったとき true を返しています。 4 以上の偶数は試す必要がありませんが,お題ではそういった工夫もせず素朴に実装することを求めているのでそうしています1。 ただし,効率に関わる大きな工夫が一つあります。 除数として n までの整数を試すのではなく n の平方根までの整数を試すようにしているのです。 というのは,$n$ がもし $\sqrt n$ を超える整数で割り切れるならその商は $\sqrt n$ 未満であるはずだからです。$\sqrt n$ までの整数で割り切れないならそれより大きな整数で割り切れるはずがありません。 問題点 危ういのは,繰り返しの上限を Math.sqrt(n) としていることです。 理論上は n の平方根でいいのですが,問題は Math.sqrt が誤差を含むということです。 一般に浮動小数点演算は誤差を含み得ます。 Math.sqrt(n) が n の真の(つまり数学上の)平方根以上であればいいのですが,真の平方根未満の場合もあります。その場合,テストすべき除数を一つ省いてしまうことになります。 例えば,数学的には $\sqrt{10^{46}} = 10^{23}$ のはずですが, p Math.sqrt(10**46) < 10**23 # => true で分かるとおり,Math.sqrt で求めた $\sqrt{10^{46}}$ の値は真の値より小さくなります2。 これでは誤判定が起こり得ますね。 ただ,実際に誤判定が起こるのはかなり大きな n の場合です。 おそらくこのコードでまともな時間内に判定できる限界を遥かに超えているでしょう。 それでもこれはまずいコードと言うべきです。正しく動作するかどうかがコードを見ても直ちには分からないからです。 改善 もし「数学的に厳密な平方根を超えない最大の整数」を簡単に得ることができれば解決ですね。 Ruby にはそのためのメソッド Integer.sqrt があります: (7..10).each{ |n| p [n, Integer.sqrt(n)] } # => [7, 2] # [8, 2] # [9, 3] # [10, 3] これを使えば def prime?(n) 2.upto(Integer.sqrt(n)) do |k| return false if n % k == 0 end true end と書けます。 まあ,多くの Ruby ユーザーは(このような素数判定の練習問題を除けば)Integer.sqrt を使う機会は無いでしょう。私も実際の仕事で使ったことはありません。 この記事で伝えたかったことは,「繰り返しの上限に浮動小数点演算の結果を用いる際にはよく気をつけよう」ということです。もう少し一般化すると,「浮動小数点数の大小関係の判定には気をつけよう」となりますね3。 この記事で伝えたい以外のことをできるだけシンプルにしたかったわけ。 ↩ 浮動小数点演算の結果は環境によって変わりうるので,全ての環境でこうなるとは限りませんが,一例は示せました。もう少し小さい数だと,手元の環境では Math.sqrt(9904578032905937 ** 2) が 9904578032905937 よりも小さくなりました。 ↩ それが目的ならもっとシンプルな例題があるわけですが,素数判定の話を採用したのは,この記事のようなコードが多数投稿されているのを見たからです。 ↩
- 投稿日:2021-12-30T02:01:46+09:00
【 rails db:migrate実行時のエラー 】
カラムを新しく追加し、rails db:migrateを実行したが、エラー発生 deviseを導入し、ユーザー管理機能を実装していました。 login_idカラムを追加しようと思い、マイグレーションファイルにカラムを追記し、 rails db:migrateを実行したところ、 Mysql2::Error: Table 'users' already existsと表示される。 (usersテーブルは既に存在していますよ〜) なぜ? 原因はよく分からないまま、エラーをもとに検索。 結果 rails db:dorpで一度データベースを削除し、 rails db:createで再生成。 その後rails db:migrateを実行すると、エラーも無く、 login_idカラムも追加されていました。 反省 configディレクトリ内のdatabase.ymlファイルに記してある、 utf-8mb4をutf-8に変更していなかった事が原因と考えられます。 この二つではデータの保存形式が異なるそうです。