- 投稿日:2019-05-02T23:32:16+09:00
RubyでOpenWeatherMapを使って天気を取得してみた
はじめに
Rubyを使って何かできないか考え、ふと天気が取得できたらいいなと思ったのがきっかけです。
環境
macOS Mojave(10.14.4)
Ruby 2.6.3OpenWeatherMapについて
現在の天気と天気予報を行ってくれるサイトです。
またAPIを利用することで、以下機能を無料で実行できます。
- 20万以上の都市を含む、あらゆる場所の現在の天気データにアクセス
- 4万以上の気象観測所からのデータと、グローバルモデルに基づいて天気を頻繁に更新
- JSON、XML、HTMLフォーマットでデータを取得可能
- 3時間毎の天気を5日後まで取得可能
API Key取得
OpenWeatherMapにアクセスします。
アカウントを作成すると、登録したメールアドレス宛にメールが届きます。
その中にAPI KEYがあります。取得処理
こちらの記事を参考にさせていただきました。
無料天気予報APIのOpenWeatherMapを使ってみる調べたい場所は東京を指定しました。
response = open(URL + "?q=Tokyo,jp&APPID=#{API_KEY}") puts JSON.pretty_generate(JSON.parse(response.read))取得結果
5/2 22時過ぎごろに実行したときの取得結果です。
{ "cod": "200", "message": 0.008, "cnt": 40, "list": [ ・・・略・・・ { "dt": 1556841600, "main": { "temp": 291.03, "temp_min": 291.03, "temp_max": 292.081, "pressure": 1015.3, "sea_level": 1015.3, "grnd_level": 1012.9, "humidity": 40, "temp_kf": -1.05 }, "weather": [ { "id": 803, "main": "Clouds", "description": "broken clouds", "icon": "04d" } ], "clouds": { "all": 83 }, "wind": { "speed": 1.19, "deg": 84.806 }, "sys": { "pod": "d" }, "dt_txt": "2019-05-03 00:00:00" }, ・・・略・・・ "city": { "id": 1850147, "name": "Tokyo", "coord": { "lat": 35.6828, "lon": 139.759 }, "country": "JP", "population": 8336599 }5/2 24時ごろは曇りのようですね。
broken cloudsが83%となっているので、ちぎれ雲のようなものが空のほとんどを占めているようです。比較のためにヤフー天気を見てみました。
雲ひとつないですね、全く逆ですね。。。まとめ
予報は外れているものの、APIを使用した取得は面白かったです。
他にも天気予報のAPIを公開しているところがあるようなので、OpenWeatherMapと他を組み合わせながら出力すると面白いと思います。またOpenWeatherMapは都市のIDや、郵便番号や緯度経度も指定できるようなのでそちらも試してみます。
その際には天気のアイコンも出したいですね。
- 投稿日:2019-05-02T17:37:12+09:00
Rubyの改行はそのまま出力される
- 投稿日:2019-05-02T16:55:58+09:00
本番環境にデプロイできない時の対処法(初心者向け)
AWSで本番環境にデプロイできない時の対処法(初心者向け)
前提
AWSを利用してwebサイトを公開し、開発したものをデプロイすると本番環境に反映されないことや、サイトが閲覧できない状態(We're sorry ~ )になることがよくあるので、対処法を解説します。
開発環境
Ruby 2.3.1
Rails 5.2.2.1
AWS EC2(無料枠使用)
※一旦デプロイが完了し、自動デプロイの設定が完了している前提で解説します。
自動デプロイには、capistranoを使用しています。対処法(1) EC2のインスタンスを再起動
何度も自動デプロイを行なっていると、EC2側で変更が反映されず、場合によっては変更箇所が見れないことがあります。(ex.ユーザーログインの機能を実装したのに反映されていない等)
その場合、AWSのマネージメントコンソールから
EC2 → インスタンス → 該当のインスタンスをクリック → アクションのインスタンスの状態 → 再起動 を行います。
その後、再度ターミナルからEC2にログインし
sudo service nginx start
sudo service mysqld start
のコマンドを実行し、WEBサーバのnginxとmysqlを立ち上げます。
その後、ローカルで自動デプロイのコマンドを実行すると、アプリケーションサーバのunicornが立ち上がり、サイト上に変更が反映され、閲覧できるようになります。対処法(2) ローカルでエラーが起きていないか確認する
ローカルでは反映されないエラーが、本番ではエラーと認識されることがあります。
例えば、ECサイトを作成し商品一覧は問題なく表示されるが、商品の詳細画面にアクセスするとsyntaxエラー(hamlで記述している場合はインデント等)が起きている場合、本番ではエラーがあると認識されサイト全体が閲覧できないことがあります。
ローカルで確認し、エラーの箇所が分からなければ、サーバー側から
less log/production.log
less log/unicorn.log
のコマンドでログを確認し、エラーの箇所を特定します。
ここにsyntaxエラーやFATALと記載されている項目があれば、そのエラーを解消した上で再度デプロイします。
具体的な例としては、gemでfont-awesome-railsを使用していると、記述の方法によって、ローカルでエラーは出ないが本番ではエラーとなることがあり、このようなエラーはログを確認しなければ特定することは困難です。対処法(3) AWSの無料枠を確認する
直接的なエラーではありませんが、AWSの無料枠(メモリの容量等)を越えるとデプロイできなくなることがあります。
AWSの無料枠はあくまでお試しのような枠なので、同時に2つサイトを公開するとなると容量が足りなくなることがあり、その結果デプロイできないことや変更内容が反映されないことがあります。
また、gemもメモリに影響を与えるので、一度確認しておきましょう。
私の場合、無料枠を利用して2つ目のアプリケーションを公開し、gemのfont-awesome-sassを導入しようとしたら、ターミナルでメモリの容量が足りないというエラーが発生しました。font-awesome-sassを削除し、再度デプロイするとエラーが解消されました。以上が初歩的な本番環境で発生するエラーの解消方法です。
他にも対処法は多くありますので、参考程度にして下さい。
- 投稿日:2019-05-02T16:16:12+09:00
【Rails】ローカルにないエラーがherokuで表示された時の対処法
「ローカル環境での挙動はバッチリ、さあ herokuへアップロードだ」
と思ったら、アップロード後にはなんのヒントもないエラーページ。
オリジナルアプリケーション開発で遭遇したシチュエーションをもとに、このような時の解決法を整理します。
1.エラー画面を表示する
config/environments/production.rbconfig.consider_all_requests_local = false本番環境だとエラー画面のデフォルトが下記のように設定されています。
これではエラー内容がわかりづらいので、本番環境でもエラーを表示できるようにしましょう。エラー解決後は、速やかに戻しましょう。
config/environments/production.rbconfig.consider_all_requests_local = true2.ログを確認する
heroku logs -t #-tでも-tailでもOKログを確認すると、
- 本番環境ではカラムが追加されていない(※
heroku rails db:migrate
で解消)などのエラー内容ががつかめるはずです。
- 投稿日:2019-05-02T16:03:22+09:00
【Ruby】コードを少し変えてYARV命令の違いを確認する
はじめに
自分が書いたRubyのコードがどのような道のりを辿って実行されるのか興味を持ったことはあるでしょうか。
RubyのしくみによるとRubyコードは以下の流れでYARV命令へと変換されます。
Rubyコード ↓ 字句解析 ↓ 構文解析 ↓ コンパイル ↓ YARV命令この記事では、YARV命令の部分をピックアップし、YARV命令がコードによってどう変わるのか簡単に見ていきます。
なお、今回利用したRuby処理系、Rubyバージョンは以下の通りです。
Ruby処理系 Rubyバージョン MRI 2.6.1 YARV命令表示
まず、YARV命令を表示してみます。
簡単なRubyのコード
4 + 3
のYARV命令をみてみましょう。YARV命令はRubyVM::InstructionSequence
を使うことで見ることができます。irb(main):001:0> puts RubyVM::InstructionSequence.compile('4+3').disasm == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,3)> (catch: FALSE) 0000 putobject 4 ( 1)[Li] 0002 putobject 3 0004 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache> 0007 leave => nil
putobject
でスタックに値をプッシュし、opt_plus
で加算を実行するのが確認できます。すなわち、以下の流れでコードが実行されます。
4
をスタックにプッシュ3
をスタックにプッシュ- 加算(
4 + 3
)
4 + 3 * 2
vs(4 + 3) * 2
続いて、
4 + 3 * 2
と(4 + 3) * 2
のYARV命令を表示して結果を比べてみます。irb(main):001:0> puts RubyVM::InstructionSequence.compile('4+3*2').disasm == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE) 0000 putobject 4 ( 1)[Li] 0002 putobject 3 0004 putobject 2 0006 opt_mult <callinfo!mid:*, argc:1, ARGS_SIMPLE>, <callcache> 0009 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache> 0012 leave => nilirb(main):001:0> puts RubyVM::InstructionSequence.compile('(4+3)*2').disasm == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE) 0000 putobject 4 ( 1)[Li] 0002 putobject 3 0004 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache> 0007 putobject 2 0009 opt_mult <callinfo!mid:*, argc:1, ARGS_SIMPLE>, <callcache> 0012 leave => nil上記2つを比べると
opt_plus
の位置が変わっていることに気がつきます。それぞれ以下の流れでコードが実行されます。
・
4 + 3 * 2
4
をスタックにプッシュ3
をスタックにプッシュ2
をスタックにプッシュ- 乗算(
3 * 2
)- 加算(
4 + 6
)・
(4 + 3) * 2
4
をスタックにプッシュ3
をスタックにプッシュ- 加算(
4 + 3
)2
をスタックにプッシュ- 乗算(
7 * 2
)命令の順番によって、計算結果が変わることが確認できます。
まとめ
コードを少し変えることでYARV命令がどう変わるのかみてきました。
普段YARVの内部スタックの状態を意識することはなかったですが、より深いところまで理解できるよう知識を深めていきたいものです。
- 投稿日:2019-05-02T15:49:35+09:00
RubyKaigi 2019 Cookpad Daily Ruby Puzzles を全パターン試して解く
RubyKaigi 2019のクックパッドブースで"Cookpad Daily Ruby Puzzles"というのをやっていたらしくて、イベント終了後に正解と解説というエントリーも出ていました。
RubyKaigi 2019 Cookpad Daily Ruby Puzzles の正解と解説
この問題、結果的には全て問題のコードに1文字を追加するだけで解けるらしいので、全パターン試して解くという方法を試してみました。こんなコードです。
solv.rbCODES = {} CODES["Example"] = <<'EOS' def foo "Hello world" if false end puts foo EOS CODES["Problem 1-1"] = <<'EOS' # Hint: Use Ruby 2.6 puts "#{"Goodbye" .. "Hello"} world" EOS CODES["Problem 1-2"] = <<'EOS' puts&.then { # Hint: &. is a safe # navigation operator "Hello world" } EOS CODES["Problem 1-3"] = <<'EOS' include Math # Hint: the most beautiful equation Out, *, Count = $>, $<, E ** (2 * PI) Out.puts("Hello world" * Count.abs.round) EOS CODES["Problem 2-1"] = <<'EOS' def say -> { "Hello world" } # Hint: you should call the Proc. yield end puts say { "Goodbye world" } EOS CODES["Problem 2-2"] = <<'EOS' e = Enumerator.new do |g| # Hint: Enumerator is # essentially Fiber. yield "Hello world" end puts e.next EOS CODES["Problem 2-3"] = <<'EOS' $s = 0 def say(n = 0) $s = $s * 4 + n end i, j, k = 1, 2, 3 say i say j say k # Hint: Binary representation. $s != 35 or puts("Hello world") EOS CODES["Problem 3-1"] = <<'EOS' def say s="Hello", t:'world' "#{ s }#{ t } world" end # Hint: Arguments in Ruby are # difficult. puts say :p EOS CODES["Problem 3-2"] = <<'EOS' def say s, t="Goodbye " # Hint: You can ignore a warning. s = "#{ s } #{ t }" t + "world" end puts say :Hello EOS CODES["Problem 3-3"] = <<'EOS' def say "Hello world" if false && false # Hint: No hint! end puts say EOS CODES["Extra 1"] = <<'EOS' Hello = "Hello" # Hint: Stop the recursion. def Hello Hello() + " world" end puts Hello() EOS CODES["Extra 2"] = <<'EOS' s = "" # Hint: https://techlife.cookpad.com/entry/2018/12/25/110240 s == s.upcase or s == s.downcase or puts "Hello world" EOS CODES["Extra 3"] = <<'EOS' def say s = 'Small' t = 'world' puts "#{s} #{t}" end TracePoint.new(:line){|tp| tp.binding.local_variable_set(:s, 'Hello') tp.binding.local_variable_set(:t, 'Ruby') tp.disable }.enable(target: method(:say)) say EOS require 'timeout' CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a + %w( ! ? # % & | + - * / ^ ' . , < > = ~ $ @ _ " : ` \\ ; ) + [' ', "\n", "Dz"] CODES.each do |name, code| puts puts "#{name}" puts Time.now (0...code.length).each do |i| putc '.' CHARS.each do |char| code_inserted = code.clone.insert(i, char) File.open("code.rb", "w") { |f| f.puts(code_inserted) } begin Timeout.timeout(1) do result = `ruby code.rb 2>/dev/null` if result == "Hello world\n" puts puts '-----' puts code_inserted puts '-----' end end rescue Timeout::Error end end end endこのコードと、実行結果はgistに置いておきました。
最初はevalでコードを実行して試していたのですが、evalで実行した結果、たとえば
!
を再定義してしまって、次のループでの実行結果に影響を及ぼすことがあったりして、別ファイルにしてバッククオートでrubyコマンドで実行することにしました。正解と解説に書いてありますが、"Problem 1-3"には「ブルートフォースよけ」が仕込まれています。まんまとはまってしまいました。
Timeout.timeout(1) do ... end
で囲って、一秒以上かかる場合は諦めるようにしました。CHARSには数字、アルファベット、記号、空白文字、改行文字、そして"Dz"を入れています。"Dz"は"Extra 2"のためだけに入れました。。。("Extra 2"はヒントがなければ全パターン試したつもりでも解けませんね。。。)
全パターン試して解こうと思ったモチベーションの1つは、別解を見つけることだったのですが、"Example"以外は別解が見つかりませんでした。"Example"の別解は、
if !false
の代わりにif :false
を使うというものです。ただ、"Example"は解答が発表されているわけではないので、厳密には別解というわけではないですね。別解が見つからないということは、問題作成者の方も私がやったような全パターンを試すスクリプトを書いて事前に確認しているのだと思います。
"Extra 3"は1文字解答が2つあるらしいのですが、まだ解答が1つしか見つかっていません。全パターン試しているはずなのに、なんででしょうかね。。。(追記:2019/05/03 08:07)
コードにミスがありました。バックスラッシュ(\
)とセミコロン(;
)を別々の文字列にしたかったのに、\ ;
と書いていて、空白とセミコロンの2文字の文字列ということになっていました。\\ ;
に書き換えました。それにより、"Extra 3"の2つ目の1文字解答も見つけることができました。
- 投稿日:2019-05-02T15:25:21+09:00
臭うコードを消臭しよう Railsのリファクタリング
こんにちは。じゅんといいます。(ついったー)
普段仕事でRubyを使っています。フレームワークはRailsです。ゼロイチのフェーズのプロジェクトに関わっていると、スピードをとにかく重視することがあります。
その結果、これまでに私は数々の「臭うコード」を生み出してしまいました。スピードを重視していたとしても、「臭うコード」を生み出さずクリーンなコードを生み出し続ける優秀なエンジニアもいます。
そう。私はまだまだエンジニアとして未熟なので「臭うコード」を生み出してしまっていたのです。エンジニアになって一年が経とうとしているので、いつまでも未熟とは言っていられません。
これを機にこれまで私が生み出してきた「臭うコード」を消臭していきたいと思います。(随時追加していきます)
1, evalを使って消臭(リファクタリング)してみる
元々のコードがこちら。(メソッド名や変数名などは変えてあります。)
profile.rbclass Profile < ApplicationRecord #色ごとの勝利数を変更するメソッド #red_win_num, blue_win_num, white_win_num, green_win_numというカラムが存在するよ def change_win_num(color) if color == "red" self.red_win_num += 1 elsif color == "blue" self.blue_win_num += 1 elsif color == "white" self.white_win_num += 1 elsif color == "green" self.green_win_num += 1 end end endこのchange_win_numというインスタンスメソッドですが、ほとんど同じ処理を繰り返し書いています。
色(color)によって処理が違うならまだしも、今回の場合やっている処理は「その色のwin_numを+1する」だけで全て共通です。
このように、if文で分岐しているのに繰り返し同じ処理を記述している場合は、evalを使えないか検討してみましょう。evalの詳しい説明はリファレンスをみていただくとして、簡単に説明すると、evalは第一引数として渡した文字列をRubyプログラムとして実行してくれます。
以下の例をみていただければevalがやっていることが分かると思います。
(文字列 "p message" をevalに渡すとそれを実行してくれるという例)evalの例message = "I love Ruby" eval "p message" # => "I love Ruby"今回の「臭うコード」にはぴったりそうです。
早速evalを使って先ほどのコードをリファクタリングしていきます。profile.rbclass Profile < ApplicationRecord def change_win_num(color) eval "self.#{color}_win_num += 1" end endめちゃくちゃスッキリしましたね。
colorをいちいちベタがきしていたらそこでバグを埋め込んでしまう可能性もありますが、これなら安心です。万が一他の色が増えたとしても、このメソッドを変更する必要はありません。(若干の懸念点としては、カラムに存在しないcolorをこのメソッドの引数として渡された場合、エラーが起きてしまいます。)
(また、そもそもProfileに色ごとの勝利数のカラムを持たせていることへの懸念も残ります。このままだと色が増えて行ったらそれだけカラムを増やさないといけなくなってしまいます。それを考慮すると、「profile_id」と「color」と「win_num」の3つのカラムをもつ別テーブルを作った方がよりいい気がします。が今回はevalを使ったリファクタリングのみで一旦やめておきます。)
戻り値が複数のものを1つにしてみよう
Rubyでは、戻り値を複数持つことができます。
この場合、メソッドの呼び出し側でも複数の受け皿(変数)を用意しておく必要があります。class Sample < ApplicationRecord #2つの数字を渡すと、足し算、引き算、掛け算、割り算した値を返してくれるクラスメソッド #本来それぞれは別のメソッドにすべきだが、複数の戻り値をもつメソッドの例として無理やり1つのメソッドにしました def self.calculate(num_1, num_2) add_result = num_1 + num_2 #足し算 substract_result = num_1 - num_2 #引き算 multiple_result = num_1 * num_2 #掛け算 divide_result = num_1 / num_2 #割り算 #複数の結果をまとめて返す return add_result, substrac_result, multiple_result, divide_result end end呼び出す方はこんな感じで呼び出せます。
add, substract, multiple, divide = Sample.calculate(30, 10) p add #=> 40 p substract #=> 20 p multiple #=> 300 p divide #=> 3今回の例だと4つの戻り値があります。これがさらに増えると、メソッドの呼び出し元のコードがかなり汚くなります。
呼び出すたびに、その戻り値分の受け皿を用意する必要があるからです。また、戻り値の順番も気にしないといけなくなるので、これも厄介です。
今回の例だと、1つ目の戻り値が足し算の結果で、2つ目が引き算で、3つ目が掛け算で、4つ目が割り算ということを呼び出し元が認識しておく必要があります。これを間違えると事故が起きる可能性があります。これらの問題を解消する為に、戻り値を1つにしてみましょう。
class Sample < ApplicationRecord def self.calculate(num_1, num_2) add_result = num_1 + num_2 #足し算 substract_result = num_1 - num_2 #引き算 multiple_result = num_1 * num_2 #掛け算 divide_result = num_1 / num_2 #割り算 calculated_result = { add: add_result, substract: substract_result, multiple: multiple_result, divide: divide_result } return calculated_result end endやっていることは一緒ですが、結果的に戻り値がcalculated_resultという1つのハッシュだけになりました。
呼び出し元を見てみましょう。
data = Sample.calculate(30, 10) p data[:add] #=> 40 p data[:substract] #=> 20 p data[:multiple] #=> 300 p data[:divide] #=> 3戻り値が1つなので、呼び出す際にかなりスッキリしました。また、戻り値の順番を機にする必要もなくなりました。
これで多少は消臭(リファクタリング)できたかと思います。今後随時追加していきます(週に一個のペースで追加していく予定です)
- 投稿日:2019-05-02T15:14:01+09:00
竹内関数をメモ化で高速化する工程 (Python には負けたくない)
はじめに
Ruby で竹内関数をメモ化によって、高速にしようとあれこれやります。
竹内関数とは
これです。Ruby で書くとこんな感じです。
Rubydef tarai(x, y, z) return y if x <= y tarai( tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y) ) end引数をたらい回しにして、再帰しまくることにより、重い処理になっています。
迂闊に、
tarai(30, 15, 0)とかすると、後悔します。
メモ化とは
竹内関数が遅いのは、引数をたらい回しにして再帰的に関数を実行することで、引数が同じなのに、何度もなんども同じ計算をしなければならないためです。
そこで、ある引数の組合せで計算を一度してしまえば、その答えをメモしておいて、もう一度同じ引数の計算をしなければならなくなった時に、実際に計算するのでなく、メモに記録した答えを返すようにすることで、速く答えを出す、というものです。
で、何をしようというの?
Fibonacci 数を求める
fibo(n)
のように引数がひとつの時には、特に問題はありません。Rubydef make_fibo cache = {} fibo = lambda do |n| return n if n < 2 cache[n] ||= fibo.call(n - 2) + fibo.call(n - 1) end end fibo = make_fibo fibo.call(100)すればOKです。
ここでは、計算結果を、引数をキーにした Hash オブジェクトcache
にメモしています。ちなみに、cache
をグローバル変数にしたくないので、lambda
を使ってクロージャを作っています。
def ... end
で囲まれた空間は、局所変数の独立した 名前空間 を作るので、def
で作ったメソッドは外側の局所変数をキャプチャーできず、クロージャにはなりません。その代わり、
lambda
等を使ってクロージャを作る時には、def ... end
の中は、局所変数に関してはクリーンルームになるので、クロージャを返すメソッドを作った上で、クロージャを作るのがオススメのはずです。多分。話が飛んでしまいましたが、Fibonacci の場合と違い、竹内関数の場合、引数が3つあります。これをどう Hash オブジェクトに登録するかです。
Python ならどうするか
Python なら、当然引数を tuple にして、dict オブジェクトに登録するはずです。
まずは、クロージャを使って、
Pythondef make_tarai: cache = {} def tarai(x, y, z): if x <= y: return y key = (x, y, z) val = cache.get(key) if val is not None: return val val = tarai( tarai(x-1, y, z), tarai(y-1, z, x), tarai(z-1, x, y) ) cache[key] = val return val return tarai tarai = make_tarai() tarai(100, 50, 0)あるいは、Python のデフォルト引数は関数を定義した時に一度だけ評価されるので、
Pythondef tarai(x, y, z, cache={}): if x <= y: return y key = x, y, z val = cache.get(key) if val is not None: return val val = tarai( tarai(x-1, y, z, cache), tarai(y-1, z, x, cache), tarai(z-1, x, y, cache), cache ) cache[key] = val return val tarai(100, 50, 0)でもOKでしょう。
tarai
関数を何度実行しても、常に同じdict
オブジェクトが使われるので、一度計算結果をメモしてしまえば、次にtarai
を実行した時にもそのメモが使えます。手元で計算すると、
tarai(100, 50, 0)
で約0.03 秒程度です。Ruby で同じようなことをすると
Rubyの場合、デフォルト引数はメソッドを実行するたびに評価されるので、Python と違い、メソッドを実行するたびに一からメモを取り直すことになってしまいます。
どちらがいいのか私にはよく分かりません。Python が変わっているような印象ですが、どちらが勝ちで、どちらが負けという話でないことは間違いなさそうです。
そういうわけで、Ruby の場所、クロージャでいきます。
Ruby には tuple がないので、配列でやるしかありません。Rubydef make_tarai cache = {} tarai = lambda do |x, y, z| return y if x <= y cache[[x, y, z]] ||= tarai.call( tarai.call(x - 1, y, z), tarai.call(y - 1, z, x), tarai.call(z - 1, x, y) ) end end tarai = make_tarai tarai.call(100, 50, 0)できるにはできましたが、Python に比べると圧倒的に遅いです。手元で計算すると、0.09 秒前後かかってしまいました。完敗です。
Rubydef time start = Time.now result = yield diff = Time.now - start puts "Elapsed time: #{diff} secs" p result end time { tarai.call(100, 50, 0) }で時間を計算しましたが、以下略。
Python の tuple は見た目が同じなら同じオブジェクトです(少なくとも中身がintの場合)。なので高速に dict のキーを比較することができます。
それに対し、Ruby の配列は見た目が同じでも別のオブジェクトになりうるので、キーの比較に時間がかかってしまうのが大きな原因だと思います。
ここからが知恵の出しどころです。
計算結果を多重 Hash に登録する
配列をキーにするから遅いのなら、メモを多重 Hash にして、Integer である、個々の
x
,y
,z
をキーにして、計算結果をメモすることにします。戦略的には、まず、引数
x
のためのcache_x = {}
を用意します。
そして、cache_x[x]
は引数y
のための Hash オブジェクトcache_y
を返して欲しいのですが、引数x
が初見であればnil
が返ってきます。
そこで、cache_y = cache_x[x] ||= {}
とすることで、x
が初見の場合には、新たな Hash オブジェクトをcache_[x]
に登録した上で、変数cache_y
に代入することができます。そうすると、こんな感じになります。
Rubydef make_tarai cache_x = {} tarai = lambda do |x, y, z| return y if x <= y cache_y = cache_x[x] ||= {} cache_z = cache_y[y] ||= {} cache_z[z] ||= tarai.call( tarai.call(x - 1, y, z), tarai.call(y - 1, z, x), tarai.call(z - 1, x, y) ) end end tarai = make_tarai tarai.call(100, 50, 0)0.012 秒前後です。
やりました。Python に勝ちました。圧勝です。ちなみに、Python でこの方法を使うと少し遅くなってしまいました。恐るべし、Python の tuple 。
しかし、Ruby が勝ったとはいえ、
Rubycache_y = cache_x[x] ||= {} cache_z = cache_y[y] ||= {} cache_z[z] ||= tarai.call(...の部分がまだだるいです。どうせなら
cache[x][y][z] ||= tarai.call( ...
としたいです。Hash のデフォルト値を使う
Ruby の場合、
Rubycache = Hash.new { |hash, key| hash[key] = {} }とすることで、未登録のキーに出くわしたとき、新たな Hash オブジェクトを作って、そのキーに登録するような Hash オブジェクトを作ることができます。
これを多重にすればいいわけです。Rubydef make_tarai cache = Hash.new do |hash, key| hash[key] = Hash.new do |hash, key| hash[key] = {} end end tarai = lambda do |x, y, z| return y if x <= y cache[x][y][z] = tarai.call( tarai.call(x - 1, y, z), tarai.call(y - 1, z, x), tarai.call(z - 1, x, y) ) end end tarai = make_tarai tarai.call(100, 50, 0)これでOKです。
とはいえ、このcache
の定義もだるいです。何とかスマートにしたいものです。そこで、
cache
の右辺をよく見ると、ラスボス的な{}
をHash.new { |hash, key| hash[key] = ... }
でラップしていけば良さそうだと分かります。メタプログラミングで何とかしましょう。メタプログラミング登場
方針としては、何重にもなった
Hash.new {}
の文字列を作って、eval
することにします。引数の数が3つの場合、
{}
を2回Hash.new {}
でラップすればいいから、メモ化する際の引数の数をarity
とすると、Rubydef make_cache(arity) seed = "{}" (arity - 1).times do seed = "Hash.new { |hash, key| hash[key] = #{seed} }" end eval(seed) endこの
make_cache
があれば、簡単に多重 Hash を作ることができます。Rubydef make_cache(arity) seed = "{}" (arity - 1).times do seed = "Hash.new { |hash, key| hash[key] = #{seed} }" end eval(seed) end def make_tarai cache = make_cache(3) tarai = lambda do |x, y, z| return y if x <= y cache[x][y][z] ||= tarai.call( tarai.call(x - 1, y, z), tarai.call(y - 1, z, x), tarai.call(z - 1, x, y) ) end end tarai = make_tarai tarai.call(100, 50, 0)かなりいい感じになりました。でも、もっと高速を目指したいです。
いろんな事情があって、Proc オブジェクトはメソッドより遅いと聞きます。そこで、Proc オブジェクトではなく、メソッドでクロージャを作ってみます。
メソッドでクロージャを作る
すでに述べたように、
def
でクロージャは作れません。そこで、def
を使わずにメソッドを作ることにします。
define_method
の登場です。Rubydef make_cache(arity) seed = "{}" (arity - 1).times do seed = "Hash.new { |hash, key| hash[key] = #{seed} }" end eval(seed) end def make_tarai cache = make_cache(3) define_method(:tarai) do |x, y, z| return y if x <= y cache[x][y][z] ||= tarai( tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y) ) end private :tarai end make_tarai tarai(100, 50, 0)トップレベルで
make_tarai
を定義すると、定義式の中のself
はmain
になります。define_method
は Module クラスのメソッドなので、一般的にはmodule ... end
かclass ... end
の中でしか使えないのですが、main
には同名のシングルトン・メソッドが定義されていて、実行すると、Object クラスにメソッドが登録されます。ここでは、トップクラスでメソッドを定義したときのように、private にしました。
ただ、メソッドにしてもほとんど速度は変わりません。Proc にしろ、メソッドにしろ、環境を背負っている分、遅いのでしょうか。
そこで、この辺りでクロージャに見切りをつけ、
cache
をインスタンス変数で持つ方法を試してみます。メモをインスタンス変数に格納する
Rubydef make_cache(arity) seed = "{}" (arity - 1).times do seed = "Hash.new { |hash, key| hash[key] = #{seed} }" end eval(seed) end class Tarai def initialize @cache = make_cache(3) end def call(x, y, z) return y if x <= y @cache[x][y][z] ||= call( call(x - 1, y, z), call(y - 1, z, x), call(z - 1, x, y) ) end end tarai = Tarai.new tarai.call(100, 50, 0)ついに、0.007 秒の世界に突入しました。
ぶっちぎりの速さです。関数型言語に惑わされてなのか、メモ化というとすぐにクロージャだと思い込んでいましたが、Purely Object-Oriented Programming Language である Ruby の場合、素直にオブジェクトを使えばよかったんですね。
まとめ
Ruby でメソッドをメモ化する時には、クロージャではなく、普通にインスタンスを作って、インスタンス変数にメモを格納する。
引数が複数あるときは、配列にして Hash オブジェクトのキーにするのではなく、引数の数だけ多重 Hash にする。
- 投稿日:2019-05-02T14:41:50+09:00
連番のインスタンス変数をinstance_variable_getメソッドでDRYにする
DRY後
- コントローラー側で
@day_1
と@day_2
の2つのインスタンス変数をセットしているとします。- 下のコードでは
_day.html.erb
パーシャルを呼び出していますが、こう書くことで動的に生成したインスタンス変数名を指定できます。schedule.html.erb<% 2.times do |n| %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <% day_i = "@day_#{n + 1}" %> <%= render 'day', day: instance_variable_get(day_i) %> <% end %>DRY前
- 下記のように
render
メソッドを並べても動きますが、中略にした部分も並べないといけませんので、やはりDRYにした方が良い感じですね。schedule.html.erb...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: @day_1 %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: @day_2 %>参考
- 投稿日:2019-05-02T14:41:50+09:00
連番のインスタンス変数をinstance_variable_getメソッドでDRYにしようとしたけど用途が違った
追記:DRY後(最終形)
@znz さんにコメントでご指摘をいただきました。ありがとうございます。
結局instance_variable_get
メソッドを使うより普通に配列でやるべきでした。
以下のように書けました。schedule_controller.rb# day_1,day_2 に値をセットする過程は省略 @days = [day_1,day_2]schedule.html.erb<% @days.each do |day| %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: day %> <% end %>DRY後(一回目)
- コントローラー側で
@day_1
と@day_2
の2つのインスタンス変数をセットしているとします。- 下のコードでは
_day.html.erb
パーシャルを呼び出していますが、こう書くことで動的に生成したインスタンス変数名を指定できます。schedule.html.erb<% 2.times do |n| %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <% day_i = "@day_#{n + 1}" %> <%= render 'day', day: instance_variable_get(day_i) %> <% end %>DRY前
- 下記のように
render
メソッドを並べても動きますが、中略にした部分も並べないといけませんので、やはりDRYにした方が良い感じですね。schedule.html.erb...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: @day_1 %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: @day_2 %>参考
- 投稿日:2019-05-02T14:24:58+09:00
Railsで多対多のリレーションを作るときにはまったこと
概要
Railsで多対多モデルのリレーションを貼る際にハマったことを記載しておきます。
環境
- Ruby 2.5.1
- Rails 5.2.3
モデル
下記のようなモデルを作るとします。
+------------+ +------------+ | PostTag | | Post | +------------+ +------------+ +------------+ 1 * | id | | Tag | | id |--------| post_id | * 1 +------------+ | content | | tag_id |--------| id | +------------+ +------------+ | name | +------------+ハマった内容
通常はPostからTag(その逆も)を参照できるように以下のようなリレーションを定義すると思います。
class Post < ApplicationRecord has_many :post_tags has_many :tags, through: :post_tags endclass PostTag < ApplicationRecord belongs_to :post belongs_to :tag endclass Tag < ApplicationRecord has_many :post_tags has_many :posts, through: :post_tags endこの際、上記のような記述順ではうまくいきますが、Postモデル、Tagモデルの
has_many
の順番が逆の場合は、上記の環境ではうまくいきませんでした。class Post < ApplicationRecord has_many :tags, through: :post_tags has_many :post_tags end多対多で検索すると下記のような順番で記述している例もたくさんあるのですが、Rails5になって厳しくなったんでしょうかね。
ActiveRecord::HasManyThroughOrderError
エラーが出てしまうようです。[1] pry(main)> Post.find(1).tags Post Load (0.4ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1 ActiveRecord::HasManyThroughOrderError: Cannot have a has_many :through association 'Post#tags' which goes through 'Post#post_tags' before the through association is defined. from /mnt/e/Development/gunplus/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.3/lib/active_record/reflection.rb:943:in `check_validity!'
- 投稿日:2019-05-02T10:44:27+09:00
jsonapi-resources で nil を返したい
やりたいこと
Rails で jsonapi-resources を用いて REST API を構築した場合 controller-resource-model がそれぞれ密接に連携した状態になるため(まぁ json api specification 準拠だしそういう趣旨のライブラリだという前提はあるんですが)、以下のようなかたちで
nil
を返そうとするとInternal Server Error: undefined method 'id' for nil:NilClass
が出てしまう。if something_wrong? jsonapi_render json: nil, status: 500 and return endこれは返却しようとしている
json:
データをjsonapi-resources
が再構築する際、json:
データがモデルのインスタンスであることを期待されているにもかかわらず nil になってしまっていることから.id
メソッドが参照できすエラーとなるかたちだ。方法
jsonapi-resources のコントローラで
nil
を返したい場合は、if something_wrong? jsonapi_render_errors status: 500 and return endとしてやる。こうすると
500 Internal Server Error
かつnil
のデータが返される。この
jsonapi_render_errors
は依存ライブラリである jsonapi-utilsのヘルパメソッド となっているため、jsonapi-resources の API ドキュメントには掲載されていない。ここで挙げた例の他に、例えば
Bad Request
の返送なども jsonapi-utils で実装されているので、必要な場合は参考にされたい。
- 投稿日:2019-05-02T04:26:57+09:00
Ruby 配列とブロックについて〜チェリー本4章〜
Ruby 配列とブロックについて〜チェリー本4章〜
※この記事は初心者の私がチェリー本で学習するにあたって新しく知ったことや考えたことアウトプットするための場として書いています。
この記事は以下の情報を参考にして記述し、一部引用しています。
プロを目指す人のためのRuby入門ブロックに使うメソッド
delete_if
irb(main):003:0> a = [1,2,3,4,5] => [1, 2, 3, 4, 5] irb(main):004:0> a.delete_if do |n| irb(main):005:1* n.odd? irb(main):006:1> end => [2, 4]
delete_if
メソッドはブロック内で指定した条件で配列の数字や文字等を削除するメソッドです。irb(main):007:0> numbers = [1,2,3,4,5] => [1, 2, 3, 4, 5] irb(main):008:0> sum = 1 => 1 irb(main):009:0> numbers.each do |n| irb(main):010:1* sum_value = n.even? ? n * 10 : n irb(main):011:1> sum += sum_value irb(main):012:1> end irb(main):013:0> sum => 70このようにブロック内は複数行に渡って条件を書くことやブロック外からの参照も可能(ただしブロック内から外への参照は不可)。
sum_value = n.even? ? n * 10 : n
あとブロックとは関係無いですが、最初この文の2つ目に出てくる?が何を意味してるものなのか意味がわからなかったですが、1つ目の?がeven?で一つのメソッドになっていて、2つ目の?は条件演算子の?です。map
irb(main):035:0> numbers = [1,2,3,4] => [1, 2, 3, 4] irb(main):036:0> new_numbers = numbers.map {|n| n * 10} => [10, 20, 30, 40]他の配列を利用してブロック処理をしたもの新しい配列に作るメソッドです。
select / reject
even_numbers = numbers.select{|n| n.even?} => [2, 4] irb(main):038:0> odd_numbers = numbers.reject{|n| n.odd?} => [2, 4]
select
はブロックの戻り値の中で条件式が一致したものだけを取り出すメソッドになります。
reject
は逆でブロックの戻り値の中で条件式が一致したものだけを除外するメソッドになります。find
irb(main):040:0> find_numbers = numbers.find{|n| n.even?} => 2ブロックの戻り値が最初に真になった最初の要素を返します。
each_with_index
drink = %w(coffee tee water cola) => ["coffee", "tee", "water", "cola"] drink.each.with_index{|d, i| puts "#{i}:#{d}" } 0:coffee 1:tee 2:water 3:cola drink.each.with_index(1){|d, i| puts "#{i}:#{d}"} 1:coffee 2:tee 3:water 4:colaeach文で処理する結果に
.with_index
でインデックスの数字をつけてます。
インデックスの数字は指定しないと0から始まるので引数に1を指定してます。
簡単に書くとブロック処理の結果にもう一つ何か処理を一緒に加えたい時に使えるメソッドです。
ちなみにeach以外にもmapやdelete_ifと組み合わせてmap_with_indexやdelete_with_index,その他のメソッドと組み合わせて使うことができます。
リファクタリングに使える配列の書き方
do...endの代わりに{}を使う
numbers = [1,2,3,4] numbers.each do |n| sum += n end sum => 10 numbers.each {|n| sum += n} sum => 10ブロック内の条件式が1行で終わる場合は
do...end
の代わりに{}
を使って1行で終わらせる方が見やすいです。(&:メソッド名)でもっと簡潔に書く
[1,2,3,4,5,].select{|n| n.even?} => [2, 4] [1,2,3,4,5,].select(&:even?) => [2, 4]1.ブロック引数が1つで
2.ブロックの中で呼び出すメソッドには引数が無く
3.ブロックの中ではメソッドを一回呼び出す以外の処理が無い
場合にのみこの方法が使えます!
あとがき
この記事でなんとか2日目も書くこともができた!
けど4時間もかかってしまった、主に記事構成を考えるのと記事のトピック毎にどのように書こうかというところに時間がかかってしまっているのでどうしようか...
このままだと時間の無い平日とかに続けるのが厳しそうなのでなんとか解決策を見つけます!
2日目でも既にただ本を読んでただけの頃と比べるとちゃんと自分の頭で考えて消化出来てると感じるので明日も書きます。
- 投稿日:2019-05-02T03:21:42+09:00
【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加
概要
今回は、2019年のGW期間(10日間)を全て費やして取り組む
ポートフォリオの製作過程
を
取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)全体通した取り組みの詳細については、前回までの記事をご参照ください。
【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映今日一日の作業内容
ここからは、今日1日で取り組んだ作業内容をご説明します。
CKEditorへ画像アップロード機能を追加
先日実装したCKEditorで、画像を利用した編集が出来る様に
画像アップロード機能を実装しました。画像アップロードについては
shrine
をベースに下記のgemを導入しています。gem 'shrine' gem 'image_processing' gem 'mini_magick'最終的に出来上がったものが、こちらです。
'shrine'は初めて利用したという事もあり、かなり苦労しました。
その辺りのエピソードは、「今日の失敗」にてご紹介します。テストコードの記述
テストについては、
単体テスト(モデル・コントローラー)と統合テストの
両方を書いて行きます。テストには、
Rspec
を利用しています。$ rails g rspec:installまだ全てのテストを描ききれていませんが、
参考にPostモデルのテストコードを掲載しておきます。spec/models/post_spec.rbrequire 'rails_helper' RSpec.describe Post, type: :model do describe '#create' do let!(:user) { create(:user) } context 'can save' do it 'is valid with title' do expect(build(:post, user_id: 1)).to be_valid end end context 'can not save' do it 'is invalid without title' do messages = build(:post, title: nil, user_id: 1) messages.valid? expect(messages.errors[:title]).to include('を入力してください') end it 'is invaid without user_id' do messages = build(:post, user_id: nil) messages.valid? expect(messages.errors[:user]).to include('を入力してください') end end end endまだまだ触り程度ですが、
やっぱりテストコードはいいですね!!
ゲームみたいで書いていて楽しいです!!!!今日の失敗
ここからは今日の失敗をまとめて行きます。
CKEditorへ画像アップロード機能追加で手間取る
401エラー
一通りコードを書いて、実際にアップロードを試そうとすると、
下記の様なエラーが発生しました。ターミナルを見てみると、下記の様な処理結果が出ていました。
Started POST "/images?image_relation=DescriptionImages&csrf_token=" for ::1 at 2019-05-01 13:25:52 +0900 Processing by ImagesController#create as */* Parameters: {"upload"=>#<ActionDispatch::Http::UploadedFile:0x00007fbcffb81848 @tempfile=#<Tempfile:/var/folders/m0/h9_nmc1n5lg1ghdq5s4_4jc40000gn/T/RackMultipart20190501-49423-16e02tc.png>, @original_filename="pop_reiwa_hatsu.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"upload\"; filename=\"pop_reiwa_hatsu.png\"\r\nContent-Type: image/png\r\n">, "ckCsrfToken"=>"xJ5fKnWr0dw2IepjwvbFBWvHbJ7o3TqH9qpYKyK7", "image_relation"=>"DescriptionImages", "csrf_token"=>""} Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)
401
エラーは認証に失敗している事を表しますが、
慌ててページを更新すると、ログイン状態が解除されていました。そういえば、初期データを投入し直した(bin/setup)ので、
その時にログインが解除されていたのですが、ページを更新していなかったので、
それに気づかなかったという間抜けなエラーです・・・image_processingの導入漏れ
次にログインしてアップロードを試しましたが、同じ動きになりました。
再度ターミナルを見ると、下記の様なエラーでした。Completed 500 Internal Server Error in 16ms (ActiveRecord: 0.4ms) LoadError (cannot load such file -- image_processing/mini_magick): app/uploaders/image_uploader.rb:1:in `<top (required)>' app/models/image.rb:2:in `<class:Image>' app/models/image.rb:1:in `<top (required)>' app/controllers/images_controller.rb:6:in `create'また、gemの導入漏れです・・・何回やったら気が済むのでしょう・・・
imageableの追加漏れ
次に発生したエラーは下記の内容です。
ちなみにエラーが発生した箇所は、
下記のコントローラーのimage.save
です。controllers/images_controller.rbdef create image = Image.new( image: params[:upload], image_relation: params[:image_relation] ) if image.save render json: { url: image.image[:standard].url, uploaded: true } else render json: { error: { message: image.errors.full_messages }, uploaded: false } end end今回、Imageテーブルは、Postの画像以外にも、Userのアイコンなどでも
利用する可能性がある事から、ポリモーフィック関連付けを使用しています。なので、生成したImageインスタンスには、
imageable_type
とimageable_id
のデータを持たせる必要があるのですが、
pryで生成されたインスタンスの中身を確認してみるとnil
になっていました。正直、このポリモーフィック関連付けの仕組みを理解しておらず、
本来はPostDescriptionの情報を持たせるべきなのでしょうが
新しく記事を投稿する際など、idが確定していない状態で、
どの様に紐づければ良いのか、判断が付きませんでした・・・なので、一旦
current_user
の情報を持たせることにしました。controllers/images_controller.rbdef create image = current_user.images.new( image: params[:upload], image_relation: params[:image_relation] ) if image.save (中略)とりあえずUserのアイコンは、現時点で実装の予定は無いので、
一先ずはこれで動作する状態に持って行きたいと考えています。Turbolinksの干渉
下記の通り、新しく編集ページを開くと、CKEditorが表示されず、
再読み込みを行うと表示されるというバグが発生しました。コンソールのエラーを確認すると、どうやらTurbolinksが干渉している様でした。
なので、一旦Turbolinksは外しています。
明日の予定
- 現時点で実装済みの機能のテストコードを全て記述
- 検索機能(ransack)の実装
- いいね機能実装の下準備
ransack
は使った事が無いのですが、
またそれで予定が狂いそうな予感がしています・・・
見積もりスキルをきちんと伸ばしたいですね。※追記:六日目を投稿しました
【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装おまけ
最後になりますが、現在、私は下記の目標を立てて学習に取り組んでいます。
- 3年間で「10,000時間」をプログラミングに費やす
- その間、毎日ブログの投稿を行う
Twitterでは、その過程で学んだ事などを発信しています。
もし宜しければフォローしてみてください。
- 投稿日:2019-05-02T00:33:58+09:00
【競技プログラミング】偶数・奇数の配列を取得する方法| Ruby |+α Pyhton
はじめに
- 以前AtCoder(ABC124)で偶数を含む配列・奇数を含む配列を使用したのでまとめました
- 言語はRubyを紹介していますが、競技プログラミングではPythonの使用者も多いと思うのでPythonもおまけ程度にまとめてます
Ruby ver1_1 #group_by
- group_byでまとめた場合、Hashとして返ってきます
- ここで配列のようにしていますが、keyが1や0で値を取得しています
ary = [1,2,3,4,5,6] odd = ary.group_by{|i| i%2}[1] #Keyが1というだけ #=> [1, 3, 5] even = ary.group_by{|i| i%2}[0] #Keyが0というだけ #=> [2, 4, 6]Ruby ver1_2 #group_by
- 先ほどのHashを#valuesメソッドで値を取得し、配列を取得します
ary = [1,2,3,4,5,6] odd = ary.group_by{|i| i%2}.values[0] #配列の1番目 #=> [1, 3, 5] even = ary.group_by{|i| i%2}.values[1] #配列の2番目 #=> [2, 4, 6]Ruby ver2 #select #odd|#even
- 次に目的とする配列を取得する#selectメソッドを使用し、その後#odd|#evenメソッドを使用します
ary = [1,2,3,4,5,6] odd = ary.select(&:odd?) #=> [1, 3, 5] even = ary.select(&:even?) #=> [2, 4, 6]Ruby ver3 #select #with_index
- 次に目的とする配列を取得する#selectメソッドを使用し、その後#with_indexメソッドを使用します
ary = [1,2,3,4,5,6] odd = ary.select.with_index{|e, i| i % 2 == 0} #=> [1, 3, 5] even = ary.select.with_index{|e, i| i % 2 == 1} #=> [2, 4, 6]Ruby ver4 #each_slice
- 次に#each_sliceメソッドを使用します
- 以前にQiitaでも紹介があった記事を参考にしています
- 注意ですが、each_sliceは配列の個数が偶数の場合に成り立ちますが、奇数個の場合#lastメソッドを使用した際に、偶数の配列に奇数が含まれてしまうので競技プログラミングでは使用しないようにして下さい
#配列の個数が偶数のとき ary = [1,2,3,4,5,6] odd = ary.each_slice(2).map(&:first) #=> [1, 3, 5] even = ary.each_slice(2).map(&:last) #=> [2, 4, 6] #配列の個数が奇数のとき ary = [1,2,3,4,5,6] odd = ary.each_slice(2).map(&:first) #=> [1, 3, 5, 7] even = ary.each_slice(2).map(&:last) #偶数配列に奇数が含まれてしまう #=> [2, 4, 6, 7]Python 内包表記
- おまけです
ary = [1,2,3,4,5,6] odd = [i for i in ary if i%2 == 1] #=> [1, 3, 5] even = [i for i in ary if i%2 == 0] #=> [2, 4, 6]