20220228のRubyに関する記事は9件です。

【令和最新版】Array#sample と Random#rand ってどっちが速いんかな

背景 以前に sample と rand についてベンチマークを比較した記事を書きましたが、いくつか見直すべき点があったため改めて投稿したいと思います。 検証 Kernel.#rand より Random.rand 前記事では Random.rand と言いながら Kernel.#rand(つまり単に rand) を混同して使っていました。 なので改めて以下のように Kernel.#rand と Random.rand を比較してみます。 num = 10000 count = 1000000 Benchmark.bm 20 do |r| r.report 'Kernel.#rand' do count.times do rand(num) end end r.report 'Random.rand' do count.times do Random.rand(num) end end end 結果 user system total real Kernel.#rand 0.190434 0.000193 0.190627 ( 0.190932) Random.rand 0.084481 0.000194 0.084675 ( 0.084819) Random.rand のほうが2倍以上高速でした。 range より +1 rand の引数には Range オブジェクトを受け取ることが出来ますが、1以上10000以下のような範囲なら 1..10000 の Range より 10000 の Ingeger を与えて +1 するほうが高速です(rand(10000) は 0〜9999 の値をランダムで返すため)。 Benchmark.bm 20 do |r| r.report 'Random.rand(range)' do num.times do Random.rand(range) end end r.report 'Random.rand(+1)' do num.times do Random.rand(10000) + 1 end end end 結果 user system total real Random.rand(range) 0.243092 0.000319 0.243411 ( 0.244819) Random.rand(+1) 0.084825 0.000072 0.084897 ( 0.085032) 3倍近く早くなってます。 結果 以上を踏まえて Array#sample も加えてベンチマークを取ってみます。 num = 10000 range = (1..num) array = range.to_a count = 1000000   Benchmark.bm 20 do |r| r.report 'Array#sample' do count.times do array.sample end end r.report 'Kernel.#rand(range)' do count.times do rand(range) end end r.report 'Kernel.#rand(+1)' do count.times do rand(num) + 1 end end r.report 'Random.rand(range)' do count.times do Random.rand(range) end end r.report 'Random.rand(+1)' do count.times do Random.rand(num) + 1 end end end 結果 user system total real Array#sample 0.143643 0.000186 0.143829 ( 0.144033) Kernel.#rand(range) 0.137866 0.000146 0.138012 ( 0.138163) Kernel.#rand(+1) 0.194007 0.000154 0.194161 ( 0.194374) Random.rand(range) 0.244600 0.000151 0.244751 ( 0.244960) Random.rand(+1) 0.086753 0.000071 0.086824 ( 0.086926) 結果的には Random.rand に +1 するのが予想通り最速でしたが、Array#sample もそこまで遅いわけではないようです。 また、Range オブジェクトを渡す場合は Random.rand より Kernel.#rand のほうが速いというのは意外でした。もし1始まり以外の範囲を使いたい場合は Kernel.#rand を使っても良さそうです。それと range.to_a はループの度にやると非常に遅いので事前に用意しておくのが重要です。 最後に ランチェスターではパフォーマンスにうるさいサーバーサイドエンジニア社員を募集しております! まずはざっくばらんにお話しさせてください。下記からご応募お待ちしております。 ▼https://herp.careers/v1/lanchester/Bucw0mXRKogc ▼https://www.wantedly.com/companies/lanchester ▼採用動画について:https://moovy.jp/job/651
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails Zeitwerkについて

はじめに 通常のRubyでは、親要素を継承した子要素を定義する(依存関係のある)場合はファイルをrequireして、明示的に読み込む必要があります。しかし、Railsでは、親要素のファイルをrequireせずに継承しているものがあることに気づいたので、そのことについて調べてみました。 #rubyでは、本来以下のような記述が必要だが、rails では不要。 #require ‘/application_controller.rb’ class UsersCotroller < ApplicationController ・・・ end 結論 Rails 6.0以降は、Zeitwerkモードでの自動読み込み(オートロード)および再読み込みの仕組みにより、requireせずとも、ファイルが自動読み込みされている。 Zeitwerkとは Ruby用に作成されたコードローダーの仕組み プロジェクトで定義されたクラスやモジュールを必要な時(autoloding)もしくは、事前に一括で(eager loading)読み込みます。 ファイルの認識方法 クラスやモジュール名を名前空間に持ったファイル名を定数として表すことで、ファイル構造を認識します。 lib/my_gem.rb -> MyGem lib/my_gem/foo.rb -> MyGem::Foo lib/my_gem/bar_baz.rb -> MyGem::BarBaz lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo 参考:fxn/zeitwerk Rails Zeitwerkモードについて Rails 6.0以降のバージョンでは、コードローダーとして、Zeitwerkが導入されました。 config.autoload_paths Rails では、中身が自動読み込みの対象となるアプリケーションディレクトリ(app/modelsなど)の配列を自動読み込みパス(autoload path)として内部で定義しています。 デフォルトでは、あるアプリケーションの自動読み込みパスは次のもので構成されています。 アプリケーションの起動時にappの下にあるすべてのサブディレクトリ(assets、javascripts、viewsは除外) アプリケーションが依存する可能性のあるエンジンの自動読み込みパス。 また、自動読み込みパスは、appの下のあらゆるカスタムディレクトリを自動的に扱います。たとえば、アプリケーションにapp/presentersがあると、自動読み込みの設定を変更しなくてもapp/presentersの下にあるものをすぐ利用できます。 自動読み込みパス(配列)の拡張方法 config/application.rbまたはconfig/environments/*.rb内で config.autoload_pathsに追加することで拡張可能です。 module MyApplication class Application < Rails::Application config.autoload_paths << "#{root}/extras" end end ファイル名の定数化(String#camelize) 自動読み込みパス(autoload path)のディレクトリはルート名前空間Objectを表します。 自動読み込みパス(ルート名前空間Object)の下にあるファイル名は、Zeitwerkのドキュメントに記載されているとおりに定義された定数と一致しなければなりません。そのため Rails では、ファイル名を定数に変換するString#camelizeメソッドが用意されています。 例えば、app/controllers/users_controller.rbは以下のように、UsersControllerという定数を定義します。 "users_controller".camelize # => UsersController 自動読み込みのパフォーマンスを上げる設定 自動読み込みパスはデフォルトで、$LOAD_PATHという環境変数に追加されます。しかし、Zeitwerkを使用する場合は、別に絶対ファイル名で定義された自動読み込みパス(autoload path)のみを使用するので、この環境変数は不要となります。 config.add_autoload_paths_to_load_path = false こうすることで探索量が削減されて、正しいrequire呼び出しがわずかに高速化される可能性があります。また、アプリケーションでBootsnapを使っている場合も、ライブラリの不要なインデックス構築や、必要なメモリ量が節約されます。 再読み込み Railsアプリケーションのファイルが変更されると、クラスやモジュールを自動的に再読み込みします。 正確に言うと、Webサーバーが実行中の状態でアプリケーションのファイルが変更されると、Railsは次のリクエストが処理される直前に、mainオートローダが管理しているすべての定数をアンロードします。これによって、アプリケーションでリクエスト継続中に使われるクラスやモジュールが自動読み込みされるようになり、続いてファイルシステム上の現在の実装が反映されます。 再読み込みの制御 再読み込みは有効にも無効にもできます。この振る舞いを制御するのはconfig.cache_classes設定です。これはdevelopmentモードではデフォルトでfalse(再読み込みが有効)、productionモードではtrue(再読み込みが無効)になります。 Zeitwerkの自動読み込みパスに属さないもの libディレクトリにあるものや、Ruby標準ライブラリ、Ruby gemなど これらは、デフォルトではZeitwerkの自動読み込みパスに属さないため、requireが必要です。 libディレクトリは、Zeitwerkの自動読み込みパスに属しませんが、$LOAD_PATHには属しているため、requireするだけで読み込めます。再読み込みができないクラスやモジュールを起動時に読み込みたいなどの場合は、このディレクトリを使用しましょう。 参考:定数の自動読み込みと再読み込み (Zeitwerk) - Railsガイド (railsguides.jp) まとめ Rails6.0以降では、Zeitwerkを導入しているため、依存関係のあるファイルをrequireで明示的に読み込む必要がない。 Zeitwerkとは、Ruby用の自動読み込み(オートロード)および再読み込みの仕組み。 Zeitwerkは、クラスやモジュール名を名前空間に持ったファイル名を定数として表すことで、ファイル構造を認識する。 Railsでは、ファイル構造を定数化するメソッドが用意されている。(String#camelize) Railsでは、appディレクトリ配下のファイルが自動読み込み対象ファイルとなる。ただし、(assets、javascripts、views)は除外される。 自動読み込みパス(autoload path)は、配列として定義されている。 libディレクトリにあるものや、Ruby標準ライブラリ、Ruby gemなどZeitwerkの自動読み込みパスに属さないものがある。 参考 fxn/zeitwerk 定数の自動読み込みと再読み込み (Zeitwerk) 【Rails】Zeitwerkとの歩き方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

devise ユーザー情報変更をcurrent_passwordなしで行う。

はじめに Deviseのデフォルトの状態だと、ユーザーのアカウントをアップデートするには、current_passwordが必要です。 ただ名前やメールアドレスといった基本情報の編集のたびにパスワードを入力させるのは、UIの面を考慮するとあまりよろしくないです。そこで、現在のパスワードを入力しなくてもプロフィールの情報を編集できるように実装します。また、パスワードを変更する場合は、現在のパスワードを入力するような仕様にします。 Devise::RegistrationsController#updateの実装について Devise::RegistrationsController#updateでは、以下のとおり、Devise::RegistrationsController#update_resource が呼び出されています。 registrations_controller.rb def update resource_updated=update_resource(resource,account_update_params) end 参考: devise/app/controllers/devise/registrations_controller.rb Devise::RegistrationsController#update_resource の実装 # By default we want to require a password checks on update. # You can overwrite this method in your own RegistrationsController. def update_resource(resource, params) resource.update_with_password(params) end 参考:devise/app/controllers/devise/registrations_controller.rb 説明書きから次のことがわかります。 デフォルトで、アップデート時にパスワードをチェックされる。 RegistrationsControllerオーバーライドできる。 つまり、このupdate_resourceメソッドを自身の定義するRegistrationsControllerでオーバーライドすれば、ユーザー情報のアップデート時の振る舞いをカスタマイズできそうです。 update_resourceメソッドの中では、update_with_passwordメソッドが呼ばれていることがわかります。次にこのメソッドを確認します。 また、update_resourceメソッドをオーバーライドするにあたって、参考のため、そのデフォルトの挙動についても確認しておきます。 Devise::Models::DatabaseAuthenticatable#update_with_passwordの実装 # Update record attributes when :current_password matches, otherwise # returns error on :current_password. # # This method also rejects the password field if it is blank (allowing # users to change relevant information like the e-mail without changing # their password). In case the password field is rejected, the confirmation # is also rejected as long as it is also blank. def update_with_password(params, *options) if options.present? ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc [Devise] The second argument of `DatabaseAuthenticatable#update_with_password` (`options`) is deprecated and it will be removed in the next major version. It was added to support a feature deprecated in Rails 4, so you can safely remove it from your code. DEPRECATION end current_password = params.delete(:current_password) if params[:password].blank? params.delete(:password) params.delete(:password_confirmation) if params[:password_confirmation].blank? end result = if valid_password?(current_password) update(params, *options) else assign_attributes(params, *options) valid? errors.add(:current_password, current_password.blank? ? :blank : :invalid) false end clean_up_passwords result end 参考:devise/lib/devise/models/database_authenticatable.rb 説明書きによると、次のことがわかります。 :current_passwordに存在した時に要素を更新する。 :current_passwordに存在しなかった場合は、:current_passwordについて、エラーを発生させ、更新させない。 password フイールドが空白であった場合も同様に、更新させない。ただし、パスワードの変更を伴わない、e-mailといった情報の変更は、この限りではない。 password_confirmation フィールドが空白だった場合も同様に更新させない。 具体的に処理の中身を確認します。今回は、引数にオプションがあった場合の処理については、触れません。 上から順番に確認します。 current_password = params.delete(:current_password) ここでは、変数current_passwordにparams.delete(:current_password)の値を代入しています。 deleteメソッドについて key に対応する要素を取り除き、その要素を返します。 参考:instance method Hash#delete つまり、params.delete(:current_password)では、フォームに入力された値を格納したハッシュ(params) から:current_passwordに紐づく要素を取得していることになります。 次の処理を確認します。 if params[:password].blank? params.delete(:password) params.delete(:password_confirmation) if params[:password_confirmation].blank? end params[:password]に紐づく要素が存在しなかった場合は、:passwordと:password_confirmationをキーに持つ要素を取り除いています。 次の処理を確認します。 result = if valid_password?(current_password) update(params, *options) else assign_attributes(params, *options) valid? errors.add(:current_password, current_password.blank? ? :blank : :invalid) false end 変数current_passwordがパスワードとして妥当であれば、paramsハッシュの内容で情報を更新しています。 妥当でなかった場合は、更新前の要素をassign_attributesメソッドで再度割り当てを行い、エラー原因を条件分岐させて該当するエラーを発生させています。 resultという変数に条件式の結果を代入しています。 参考:assign_attributes 参考:【Rails】errors.addって何? 最後の部分を確認します。 clean_up_passwords result clean_up_passwordsはdevise内部で定義されているメソッドで、パスワードの値をクリアしています。 # Set password and password confirmation to nil def clean_up_passwords self.password = self.password_confirmation = nil end 参考:devise/lib/devise/models/database_authenticatable.rb 最後に、変数resultに格納されていた、条件式の結果を返しています。 ここまでで、params[:current_password]の値をdeleteメソッドで取り出して、その値で条件分岐しているということがわかりましたので、実際にメソッドを自分で定義してみます。 Users::RegistrationsController#update_resource の実装 それでは、実際にメソッドをオーバーライドします。 Controllerクラスのフォルダー構造は次のとおりです。usersフォルダーを作成して、その下にregistrations_controller.rbファイルを作成しています。 まず、Devise::RegistrationsControllerを継承したUsers::RegistrationsControllerクラスを定義します。 Users::RegistrationsControllerは、users/registrations_controller.rbというファイル構造を表しています。 参考:定数の自動読み込みと再読み込み (Zeitwerk) update_resourceメソッドをオーバライドします。メソッド内部でカスタムメソッドのupdate_without_current_passwordメソッドを呼び出す記述を行います。 users/registrations_controller.rb class Users::RegistrationsController < Devise::RegistrationsController protected def update_resource(resource, params) resource.update_without_current_password(params) end end それでは、update_without_current_passwordメソッドの処理を記述します。ご覧いただいてわかるかと思いますが、Devise定義のupdate_with_passwordメソッドの処理を少し変えただけです。 今回実現したいこと それでは、今回実現したいことをもう一度再確認します。 実現したい実装は以下の2点でした。 現在のパスワード(current_password)の入力なしでユーザー情報を更新したい。 パスワードの変更の場合は、現在のパスワードの入力を求める。 RegistrationsController#update_without_current_passwordの実装 user.rb class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable validates :name, presence: true, length: { maximum: 50 } def update_without_current_password(params, *options) if params[:password].blank? params.delete(:password) params.delete(:password_confirmation) if params[:password_confirmation].blank? params.delete(:current_password) result = update(params, *options) else current_password = params.delete(:current_password) result = if valid_password?(current_password) update(params, *options) else assign_attributes(params, *options) valid? errors.add(:current_password, current_password.blank? ? :blank : :invalid) false end end clean_up_passwords result end end 処理としては、次のような感じです。 if フォームのパスワード欄が空欄?    パラメータのパスワード情報を削除    パラメータのパスワード確認情報を削除    パラメータの現在のパスワード情報を削除    結果=self.update(名前情報,メールアドレス情報) else #(フォームのパスワード欄に入力が有りの場合)    current_password=パラメータの現在のパスワード情報    result= if 現在のパスワードが正しい? 結果=self.update(名前情報,メールアドレス情報,パスワード情報,パスワード確認情報) else#(フォームの現在のパスワード欄に入力誤りの場合) 更新中の値(名前情報,メールアドレス情報,パスワード情報,パスワード確認情報)をフォームの各欄に戻す      現在のパスワードがエラーになった理由を返す。    end end これでメソッドの記述は以上となります。 最後にルーティングを編集して、Devise::RegistrationsControllerではなく、カスタムしたRegistrationsControllerを参照するようにします。 Rails.application.routes.draw do devise_for :users, controllers: { registrations: 'users/registrations' } end Deviseのルーティングをカスタマイズするためには、 devise_forで、controllerオプションにハッシュを指定します。 routes.rbでdevise_forと記述することで、deviseがルーティングを設定してくれています。 devise_for :users, controllers: { Deviseのコントローラー名 : 'カスタムコントローラー名' } controllers: the controller which should be used. All routes by default points to Devise controllers. However, if you want them to point to custom controller, you should do: devise_for :users, controllers: { sessions: "users/sessions" } 引用:Method: ActionDispatch::Routing::Mapper#devise_for これで、実装完了です! まとめ Deviseのデフォルトの挙動を変更するためには、オーバーライドが必要。 デフォルトのソースコードを確認して、できるだけそれを活用すれば、案外簡単に変更が可能。 カスタムメソッドを参照するとように、routes.rbを編集する必要がある。 参考 heartcombo/devise Method: ActionDispatch::Routing::Mapper#devise_for Devise でユーザーがパスワードなしでアカウント情報を変更するのを許可
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mysql5.7での所有権エラーを直そう!!

rails でmysqlを使ったらエラー出た。。 Mysql2::Error::ConnectionError (Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)): ERROR! The server quit without updating PID file (/usr/local/var/mysql/**.local.pid) 環境 MySQL : 5.7.37 macOS : 10.14.6 ruby : 2.7.2 rails : 6.0.3 調べると、所有権に問題があるというので、ログインするため下のコマンドを試してみました。 まずは、ログイン mysql -u root -p パスワードを何度打っても通らなかったので、パスワードがわからなくなったか、何らかの変更が加わったと思ったので、 mysql5.7を入れ直すことにしました。 まず入れ直すときに、大事なのはもともとあるファイルをしっかり削除することです。入れ直す場合、前のファイルが残っていると、エラーを引き起こし、スムーズにいかなくなる可能性があります。 ※削除すると,DBの情報が削除されるので、大事な情報を入れている場合は気おつけてください。 ファイル削除 sudo rm -rf /usr/local/var/mysql sudo rm -rf /usr/local/Cellar/mysql* sudo rm -rf /usr/local/bin/mysql* sudo rm -rf /usr/local/var/mysql* sudo rm -rf /usr/local/etc/my.cnf sudo rm -rf /usr/local/share/mysql* sudo rm -rf /usr/local/opt/mysql* sudo rm -rf /etc/my.cnf mysql.sock mysql.sock.lock も削除する。 cd /tmp ls # ファイル確認。 sudo rm -rf mysql.sock mysql.sock.lock プロセスを削除してください。 ps aux | grep mysql # 確認 kill -9 ???? # 権限を消す プロセスは、自分が実行したプログラムのことです。 mysql@5.7 アンインストールする。 brew uninstall mysql@5.7 アンインストールされているか心配ならコマンドで確かめてみてください。 mysql@5.7 インストールする。 brew install mysql@5.7 which mysql #mysqlが入っているか確認 インストールした後パスというのを通さないと、スムーズに進まないので、しっかりとコマンドを打っておきましょう!! パスを通す。 echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.bash_profile パスとは、、 macの中のメモ帳に書いておいて、いつでも取り出せるようにしておくということ。。 パスを有効にするコマンド source ~/.bash_profile ログイン mysql -u root -p ログインして、パスワードをしっかりと変更する。変更するコマンドはこれです。 パスワード変更 UPDATE user SET authentication_string=password('新しいパスワード') WHERE user='root'; Query OK, 1 row affected, 1 warning (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 1 変更を反映 flush privileges; Query OK, 0 rows affected (0.00 sec) データベースを作る。 rails db:create めちゃめちゃ大変だったと思いますが、これで完成です!! ローカルサーバーを立ち上げる。 rails s 特にエラーの内容がよくわからない場合、結構さまようと思いますが、落ち着いて検索し、メモしつつ整理していけば、解決できることが多いので、落ち着いて頑張ってください!! お世話になった記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画面遷移するとjQueryが動かないときはTurbolinksが悪さをしているかもしれない

環境: Ruby 2.7.3 Rails 6.1.4.4 問題 jQueryを使って、例えば「ボタンをクリックしたらモーダルウィンドウが表示される」といったアクションを実装したとする。すると、最初にページに訪れたときは正常に動くのに、ページ遷移を挟んだら動かなくなり、ページをリロードするとまた動き出す...といった不具合が発生する。 サンプルはこんな形↓(「open」というIDを持つオブジェクトをクリックすると、モーダルウィンドウが表示されるような処理) $(function () { $("#open").click(function(){ -----処理------ }); }); 原因 Turbolinksがjsの読み込みを邪魔している。 まず、 $(function() {}); 1行目のこれはコードの省略形で、正確に書くと以下のようになる。 $(document).ready(function { //処理 }); これが意味するのは、「ページが読み込まれたら、以下のファンクションを起動します」というもので、謂わばトリガー条件を書いていることになる。ただ、ここでTurbolinksの仕様と競合してしまう。 Turbolinksでは、ページを遷移する際にすべてを再度読み込むのではなく、headやbodyの中身だけをリロードすることによって処理の高速化を実現している(詳しくいえばもっと工程があるが割愛する)。そのため、Turbolinksで遷移したときはJSが読み込まれない問題が発生する。 解決方法 1)jQueryを丁寧に記述する $(document).ready(function { //処理 }); これが諸悪の根源なので、 $(document).on('turbolinks:load', function(){ //処理 }); と、「Turbolinksでロードが入ったときに発火させる」と丁寧に書いてあげれば解決。 2)Turbolinksを切る そもそもTurbolinksがなければ問題は起きないので、application.jsでturbolinksを無効化しておく(強硬手段ではある) // 変更前 //= require turbolinks  // 変更後 // require turbolinks 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ARC136 D問題をRuby(NArray)で計算

AtCoder Regular Contest 136 : D - Without Carry 私のレベルだと普段はこの辺の問題は解けないのですが、これに関してはうまくいった(比較的簡単な方法で解けた)のでメモしておきます。なお、公式解説のほうは難しくてまだ理解できていません。 コード Ruby で多次元配列を簡単・高速に扱うために、 Numo::NArray を使っています。 require 'numo/narray' L = 6 S = 10**L - 1 # == 999999 n = gets.to_i a = gets.split.map!(&:to_i) na = Numo::UInt32.zeros(*Array.new(L, 10)).inplace! a.each { |ai| na[ai] += 1 } na.ndim.times { |k| na = na.cumsum(k) } cnt = a.sum { |ai| na[S - ai] } cnt -= na[S / 9 * 4] cnt /= 2 puts cnt 各次元の大きさが 10 の L 次元配列 na を作り、その累積和を計算しています。計算量は O(N + L · 10L) です。 考え方 簡単のため、最大2桁(L=2)で考えてみます。 以下の入力を例にとります。 input 9 31 41 59 26 53 58 97 93 23 例えば 41 に足して繰り上がりの起きない数は [31, 41, 26, 53, 58, 23] の6つです。というのも、これらは 99 - 41 == 58 よりも 各桁で数字が小さい(または同じ) ためです。 また、例えば「 53 が繰り上がりを起こさないなら [23, 31, 41] も起こさない」ということが言えます。そのためより大きな数値にはその下の情報も合算しておくのが良さそうです。今回の場合は累積和で簡単に作れます。 ある数 s よりも各桁で数字が小さい(または同じ)要素数を表にしてみます。各桁は別々に考えられるので、桁数ぶんの多次元配列を作ればいいです。 #--- aの要素数を各桁の数字別にカウント ---# Numo::UInt32#shape=[10,10] [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 1, 0, 0]] #--- その累積和 ---# Numo::UInt32#shape=[10,10] [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 2, 2, 2, 2], [0, 1, 1, 2, 2, 2, 3, 3, 3, 3], [0, 2, 2, 3, 3, 3, 4, 4, 4, 4], [0, 2, 2, 4, 4, 4, 5, 5, 6, 7], [0, 2, 2, 4, 4, 4, 5, 5, 6, 7], [0, 2, 2, 4, 4, 4, 5, 5, 6, 7], [0, 2, 2, 4, 4, 4, 5, 5, 6, 7], [0, 2, 2, 5, 5, 5, 6, 7, 8, 9]] この表を使うと、例えば 41 に足して繰り上がりの起きない数の個数は na[9-4, 9-1] == 6 というふうに O(L) で求まります。全要素に対してカウントすると O(NL) です。間に合うか微妙ですが一旦これで行きます。 最後に設問に合わせてカウントを補正します。聞かれている個数は i < j の組のみですが、上の数え方だと i == j や i > j も含んでいます。 i == j というのは「自分同士を足して繰り上がりが起きない」ということです。これは全桁が 0〜4 だけの数値が該当します。まずはこの個数を引きます。(全要素に対して判定してもいいですが、既に累積和があるので na[4, 4] を参照すれば一発です。) i == j を引いた後は、 i < j と i > j というふうに必ず二重カウントしています。なので2で割れば設問通りの個数が求まります。 表へのアクセス高速化 上の考え方で提出したら、いくつかTLEになってしまいました。累積和の計算部分は N に依らず O(L · 10L) なので、与えられた数を L 桁に分解して多次元配列にアクセスするのが遅いようです。(Rubyなので尚更) 考えた表を見返してみると、各次元の大きさが 10 なので、 na[5, 8] というのは na[58] と同じことです。つまり配列を1次元とみなしてアクセスすれば桁を分解する必要がありません。 na[9-4, 9-1] も na[99-41] と書き直せます。これなら表へのアクセスは O(L) でなく O(1) で済みます。 C言語などであれば、最初から1次元配列として作っておき、累積和の計算時のみ擬似的に多次元配列として扱えばいいと思います。 ※ 公式解説のコードは、結果的にこの累積和を計算しているのでしょうか?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】匿名とニックネームを自由に切り替えれる機能をwebサービスに追加してみた

はじめに 1か月前にLisdomという投稿サービスをリリースしました。 ゆるーく自由なサービスと謳っていたのですが、昨日、ニックネームとかプロフィールが公開されていたら投稿したりコメントするのに億劫になると友人からの意見、、、 どうしようかな~と考えていたら、切り替え出来たら便利じゃん!と思い、忘れないうちに実装するため、早速実装してみました。 匿名とニックネーム表示の切り替え機能 今までは、以下のように、投稿主とコメント主のプロフィール画像とニックネームが公開されていました。 匿名希望さんのボタンをONにして投稿すると↓↓↓ このようにニックネームとプロフィール画像は匿名用のモノに置き換えられます。 投稿とコメントに関しては切り替えが可能であり、通知にも誰がコメントしたかは分からないようになっています。 また、投稿に関しては編集画面でも切り替えられます。 実装としては、boolean型のカラムを一つ追加し、匿名にするかどうかの判定を取っています。 class AddTokumeiToPosts < ActiveRecord::Migration[6.1] def change add_column :posts, :Tokumei, :boolean, default: false, null: false end end 開発環境 ruby ruby on rails6 heroku ※今回の機能に関してはgemをつかっていません 終わりに 友人からの意見でこの度実装することになりましたが、改めて周りからの意見は大事だな~と思いました。 機能としてはboolean型のカラムを一つ追加するだけでできてしまう簡単なものでしたが、意見がなければ実装は叶いませんでした。友人に感謝です。 最後までご覧いただきありがとうございました。 もしよろしければ匿名切り替えボタンを使って投稿やコメントしてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby OpenSSL::PKCS5.pbkdf2_hmac_sha1 をPHPで置き換えたときのメモ

def self.encrypt(data, pass, salt) cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC") key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, 1000, cipher.key_len+cipher.iv_len) key = key_iv[0, cipher.key_len] iv = key_iv[cipher.key_len, cipher.iv_len] cipher.encrypt cipher.key = key cipher.iv = iv encryptvalue = cipher.update(data) + cipher.final return Base64.b64encode(encryptvalue) end def self.decrypt(data, pass, salt) enctyptvalue = Base64.decode64(data) cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC") key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, 1000, cipher.key_len+cipher.iv_len) key = key_iv[0, cipher.key_len] iv = key_iv[cipher.key_len, cipher.iv_len] cipher.decrypt cipher.key = key cipher.iv = iv cipher.update(enctyptvalue) + cipher.final end function encrypt($data, $pass, $salt) { $key_iv = openssl_pbkdf2($pass, $salt, 32+16, 1000); $key = substr($key_iv, 0, 32); $iv = substr($key_iv, 32, 16); $decrypted_text = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv); return $decrypted_text; } function decrypt($data, $pass, $salt) { $key_iv = openssl_pbkdf2($pass, $salt, 32+16, 1000); $key = substr($key_iv, 0, 32); $iv = substr($key_iv, 32, 16); $decrypted_text = openssl_decrypt($data, 'AES-256-CBC', $key, 0, $iv); return $decrypted_text; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】enumとransackを使って複数条件での検索を実装する

環境 Ruby 3.0.0 Rails 6.1.4.4 ransack 2.5.0 enum_help 何ができるようになるか 上記の画像のようにタスクをタスク名とステータスからAND検索できるようになる。 また、ステータスはenumを使って表現できるようになる。 手順 ①enumを導入する ここでは、name(タスク名)を持つTaskモデルにstatus(タスクのステータス)カラムを追加します。 今回は、Taskモデルに未着手、着手中、完了の3つのステータスを持たせることにします。 その場合、statusにそのまま文字列で保存するやり方もあるのですが、それだと効率が悪いためActiveRecordの機能であるenumを使用して、それぞれのstatusに数値を与えることとします。 not_started_yet: 0, under_start: 1, completion: 2 まずはmigrarionファイルの作成です。 % rails g migration AddStatusToTasks status:integer statusは数値を保存するため、integer型とします。 カラムを追加したら、enumクラスメソッドにカラム名、対応する値を定義します。 app/models/task.rb class Task < ApplicationRecord (略) enum status: { not_started_yet: 0, under_start: 1, completion: 2 } end これで、enumの設定は完了です。 ②enumの日本語化 次に先ほど定義したシンボルを日本語化します。 日本語化には、enum_helpというgemが便利です。 gem 'enum_help' をGemfileに追加 % bundle install gemをインストールしたら、locales/ja.ymlに日本語訳を記述していきます。 locales/ja.yml ja: enums: task: status: not_started_yet: "未着手" under_start: "着手中" completion: "完了" 参考: https://github.com/zmbacker/enum_help ③ransackを使って検索機能の実装 ransackを使うことで検索機能を簡単に実装することができます。 gem 'ransack' をGemfileに追記 % bundle install 先にコントローラの完成形を記します。 app/controllers/tasks_controller.rb class TasksController < ApplicationController before_action :set_q, only: [:index, :search] def index @statuses = Task.statuses_i18n end def search @tasks = @q.result(distinct: true) end private def set_q @q = Task.ransack(params[:q]) end end ここでは、set_qアクションで送られてきたパラメータを元にransackがデータの検索を行います。 そのデータがsearchアクションにてActiveRecord形式に変換され、@tasksに格納されます。 次にviewを記します。 app/views/tasks/index.html.erb <%= search_form_for @q, url: search_tasks_path do |f| %> <%= f.label :name_cont %> <%= f.search_field :name_cont %> <% @statuses.each do |status| %> <%= f.check_box :status_eq_any, { multiple: true, include_hidden: false }, status[0] %> <%= f.label status[1] %> <% end %> <%= f.submit %> <% end %> ここでのポイントは検索条件の指定方法です。 何やらname_contやstatus_eq_anyのようにカラム名に続く文字が存在しています。 これは検索条件を表しており、 contだと〜の文字列を含む eq_anyだと完全一致した一つ以上のパラメータ という意味を持ちます。 参考: https://github.com/activerecord-hackery/ransack#search-matchers まとめ gemを使うことで、簡単に検索機能を実装することができました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む