- 投稿日:2020-06-28T22:25:25+09:00
間違えやすいpathの見極め方
pathの見極め方
path名を探す時、普段使うのはターミナルでrails routesして、
Prefix Verb URI Pattern Controller#Action new_admin_session GET /admins/sign_in(.:format) admins/sessions#new admin_session POST /admins/sign_in(.:format) admins/sessions#create destroy_admin_session DELETE /admins/sign_out(.:format) admins/sessions#destroyprefixからpath名を探す。
path名がない
しかし、このようにpath名が書かれていない場合がある。
Prefix Verb URI Pattern edit_product GET /products/:id/edit(.:format) products#edit product GET /products/:id(.:format) products#show PATCH /products/:id(.:format) products#update PUT /products/:id(.:format) products#update DELETE /products/:id(.:format) products#destroy cart GET /carts/:id(.:format) carts#showproducts#destroy、products#updateのpath名が見つからない。この場合、path名はproduct_pathになる。prefixでpath名が出てくるまで上に遡っていけばいい。
- 投稿日:2020-06-28T21:46:34+09:00
Rubyの繰り返し処理で添字を任意の番号から始める方法
はじめに
タイトルのとおりですが、Rubyで配列をeach_with_indexで繰り返す際に添字を任意の番号から始める方法が個人的に便利だったので、やり方を残しておこうと思います。
やり方
each_with_index
をeach.with_index(n)
にするだけです。
nには開始番号を指定してください。
添字を1から始める場合は以下のように書きます。コード
juices = ["tea", "cola", "coffee"] juices.each.with_index(1) do |juice, i| puts "#{i}番目:#{juice}" end出力結果
1番目:tea 2番目:cola 3番目:coffee
each_with_index
だとi+1
などとする必要があるので、今回の方法だと地味に便利ですよね。
- 投稿日:2020-06-28T20:14:58+09:00
【devise】rails メモ 基本設定 初期設定
【ゴール】
devise 初期、基本設定まとめ
意外と、ごちゃっとなってしまうので、、、【メリット】
■ 作業効率UP
■ devise理解度向上【開発環境】
■ Mac OS catalina
■ Ruby on Rails (5.2.4.2)
■ Virtual Box:6.1
■ Vagrant: 2.2.7【コマンド】
deviseを初期化
$ rails g devise:install ※エラーが出れば、 $ bundle exec spring stopmodelを作成
$ rails g devise:アプリ名各viewを作成
$ rails g devise views アプリ名各controller作成
$ rails g controllers devise:アプリ名メール承認機能作成の場合
※Confirmableのコメントアウト外す
db/migrate/devise_creat_アプリ名.rb## Confirmable t.string :confirmation_token t.datetime :confirmed_at t.datetime :confirmation_sent_at t.string :unconfirmed_email # Only if using reconfirmableAPIのログイン機能作成の場合
※Trackableのコメントアウト外す
db/migrate/devise_creat_アプリ名.rb## Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip以上
【合わせて読みたい】
■ 【環境変数】 rails 環境変数とは
https://qiita.com/tanaka-yu3/items/7bf03fee906b80367be9■ 最速 rails mail devise ウェルカムメール送信機能実装 action mailer不要 一番簡単
https://qiita.com/tanaka-yu3/items/2def7760fd67fe73091a■ 【devise】 管理者 ユーザー ログイン、ログアウト分ける
https://qiita.com/tanaka-yu3/items/046bf30e08e48fbc42ca
- 投稿日:2020-06-28T20:14:43+09:00
【Vue Rails】Vue + Railsで"Hello Vue!"表示
Vue + Railsアプリ作成
◆ Railsアプリ作成
// "-webpack=vue"オプションでVue.js使用可能 $ rails new <アプリケーション名> -webpack=vue◆ model作成
// カラム名:name データ型:text $ rails g model sample name:text◆ migrationファイル編集(Hello.Vue!表示には不要)
db/migrate/20200627045139_create_sample.rbclass CreateSample < ActiveRecord::Migration[6.0] def change create_table :sample do |t| t.text :name, null: false, default: "" end end end◆ マイグレーション
$ rails db:create //データベース作成 $ rails db:migrate //マイグレーション実施◆ controller作成
app/controllers/home_controller.rbclass HomeController < ApplicationController def index end end◆ routes.rb編集
config/routes.rbRails.application.routes.draw do root to: 'home#index' end◆ index.html.erb編集
app/views/home/index.html.erb<%= javascript_pack_tag 'hello_vue' %> <%= stylesheet_pack_tag 'hello_vue' %>◆ hello.vue.js(デフォルトで設定済)
app/javascript/packs/hello_vue.jsimport Vue from 'vue' import App from '../app.vue' document.addEventListener('DOMContentLoaded', () => { const el = document.body.appendChild(document.createElement('hello')) const app = new Vue({ el, render: h => h(App) }) console.log(app) })◆ app.vue(デフォルトで設定済)
app/javascript/app.vue<template> <div id="app"> <p>{{ message }}</p> </div> </template> <script> export default { data: function () { return { message: "Hello Vue!" } } } </script> <style scoped> p { font-size: 2em; text-align: center; } </style>備忘録
◆ before_action
- メソッドを定義して、
before_action
にセットするlogin_controller.rbclass LoginController < ApplicationController before_action :set_answer def set_answer @sample = "Hello World!" end end◆ rescue_from
例外処理
。エラー処理を行う画面を設定するapp/controller/application_controller.rb
に記述するapp/controller/application_controller.rbclass ApplicationController < ActionController::Base rescue_from ActiveRecord::RecordNotFound, with: :rescue404 end遭遇したエラー
◆ エラー内容①
Webpacker::Manifest::MissingEntryError in Home#index解決策:Webpackインストール
$ yarn $ bin/yarn $ webpack $ webpack◆ エラー内容②
Error: vue-loader requires @vue/compiler-sfc to be present in the dependency tree.解決策:vue-loaderダウングレード
$ npm remove vue-loader $ npm install --save vue-loader@15.9.2 $ yarn add vue-loader@15.9.2◆ エラー内容③
Sprockets::Rails::Helper::AssetNotFound in Home#index <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>解決策:app/views/layouts/application.html.erb編集
app/views/layouts/application.html.erb<!-- javascript_include_tagの行を削除 --> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>参考文献
- 投稿日:2020-06-28T18:44:39+09:00
【Rails】フリマアプリ商品編集機能について(プレビュー編集・DB更新)
はじめに
草野と申します。
今回の投稿は、プログラミングスクールでチーム開発にて行ったフリーマーケット系ECサイトのクローンアプリ商品編集機能についてです。自分用のメモのため、文章は拙いですが、少しでも初学者の助けになればと考えています。
内容は、表題にもあるとおり、プレビュー編集とDB更新についてです。未熟な点も多いと思います。不備等ありましたらご指摘ください。随時改善して行こうと思います。ちなみに私は、スクール自体は卒業しており、学習した内容の振り返りとして投稿させて頂いております。完成品
商品編集画面(プレビュー画像部)
商品編集画面(カテゴリー部)
商品編集画面(販売手数料・利益部)
更新成功時の遷移画面
実装手順
1.ルーティング編集
- update_doneのルート設定(更新成功時の遷移画面)
2.コントローラー編集
- editメソッド設定
- updateメソッド設定
- エラーハンドリング
- 画像削除
- update_daneメソッド設定
3.ビュー編集・作成
- プレビュー画像呼び出し
- カテゴリー呼び出しの調整
- 更新成功時の遷移画面
4.JS編集
- プレビュー画像及びinputタグの生成、削除
- 販売手数料・利益の表示
1.ルーティング編集
update_doneルートを生成します。
これは、更新成功時の遷移画面を表示するためのルーティングです。config/routes.rbresources :items do resources :comments, only: [:create, :destroy] resources :favorites, only: [:create, :destroy] collection do get 'get_category_children', defaults: { fomat: 'json'} get 'get_category_grandchildren', defaults: { fomat: 'json'} get 'search' get 'post_done' get 'delete_done' get 'detail_search' get 'update_done' # これを追加 end end2.コントローラー編集
今回編集したコントローラーの記述は下記の通りです。
app/controller/items_controller.rbclass ItemsController < ApplicationController before_action :category_parent_array, only: [:new, :create, :edit] before_action :set_item, only: [:show, :edit, :update, :destroy] before_action :show_all_instance, only: [:show, :edit, :destroy] # 中略 def edit grandchild = @item.category child = grandchild.parent if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158 else @parent_array = [] @parent_array << @item.category.parent.parent.name @parent_array << @item.category.parent.parent.id end @category_children_array = Category.where(ancestry: child.ancestry) @child_array = [] @child_array << child.name @child_array << child.id @category_grandchildren_array = Category.where(ancestry: grandchild.ancestry) @grandchild_array = [] @grandchild_array << grandchild.name @grandchild_array << grandchild.id end def update if item_params[:images_attributes].nil? flash.now[:alert] = '更新できませんでした 【画像を1枚以上入れてください】' render :edit else exit_ids = [] item_params[:images_attributes].each do |a,b| exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i end ids = Image.where(item_id: params[:id]).map{|image| image.id } delete__db = ids - exit_ids Image.where(id:delete__db).destroy_all @item.touch if @item.update(item_params) redirect_to update_done_items_path else flash.now[:alert] = '更新できませんでした' render :edit end end end def update_done @item_update = Item.order("updated_at DESC").first end # 中略 private def item_params params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id) end def set_item @item = Item.find(params[:id]) end def category_parent_array @category_parent_array = Category.where(ancestry: nil).each do |parent| end end def show_all_instance @user = User.find(@item.user_id) @images = Image.where(item_id: params[:id]) @images_first = Image.where(item_id: params[:id]).first @category_id = @item.category_id @category_parent = Category.find(@category_id).parent.parent @category_child = Category.find(@category_id).parent @category_grandchild = Category.find(@category_id) end endまずeditメソッドを設定します。
他のメソッドで使用しているインスタンス変数を利用するのでリファクタリングのため、before_actionから呼び出しがあります。
使用しているインスタンス変数は下記の通りです。
② 親カテゴリーのnameとidが代入された配列
③ categoryモデル内の全ての子カテゴリー
④ 子カテゴリーのnameとidが代入された配列
⑤ categoryモデル内の全ての孫カテゴリー
⑥ 孫カテゴリーのnameとidが代入された配列
⑦ 該当商品情報
⑧ categoryモデル内の全ての親カテゴリー
⑨ 該当商品の画像
⑩ 該当商品のcategory_id(孫の数値)app/controller/items_controller.rbclass ItemsController < ApplicationController before_action :category_parent_array, only: [:new, :create, :edit] before_action :set_item, only: [:show, :edit, :update, :destroy] before_action :show_all_instance, only: [:show, :edit, :destroy] #中略 def edit # ▼ ①ここで該当商品の子・孫カテゴリーを変数へ代入 grandchild = @item.category child = grandchild.parent if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158 else # ② ▼ 親カテゴリーのnameとidを配列代入 @parent_array = [] @parent_array << @item.category.parent.parent.name @parent_array << @item.category.parent.parent.id end # ③ ▼ 子カテゴリーを全てインスタンス変数へ代入 @category_children_array = Category.where(ancestry: child.ancestry) # ④ ▼ 子カテゴリーのnameとidを配列代入 @child_array = [] @child_array << child.name # ⑤で生成した変数を元にname・idを取得 @child_array << child.id # ⑤ ▼ 孫カテゴリーを全てインスタンス変数へ代入 @category_grandchildren_array = Category.where(ancestry: grandchild.ancestry) # ⑥ ▼ 孫カテゴリーのnameとidを配列代入 @grandchild_array = [] @grandchild_array << grandchild.name # ⑤で生成した変数を元にname・idを取得 @grandchild_array << grandchild.id end end #中略 private def item_params params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id) end def set_item @item = Item.find(params[:id]) # ⑦ 該当の商品情報をインスタンス変数へ代入 end def category_parent_array @category_parent_array = Category.where(ancestry: nil) # ⑧ 親カテゴリーを全てインスタンス変数へ代入 end def show_all_instance @user = User.find(@item.user_id) @images = Image.where(item_id: params[:id]) # ⑨ 該当商品の画像をインスタンス変数へ代入 @images_first = Image.where(item_id: params[:id]).first @category_id = @item.category_id # ⑩ 該当商品のレコードからカテゴリーidを取得し、インスタンス変数へ代入(この際に取得するidは孫カテゴリーidです。) @category_parent = Category.find(@category_id).parent.parent @category_child = Category.find(@category_id).parent @category_grandchild = Category.find(@category_id) endそれぞれを分類分けして並べ替えると下記の通りになります。
商品情報をinputタグに初期値として表示させるためのもの
⑦ 該当商品情報
商品画像をプレビューに初期値として表示させるためのもの
⑨ 該当商品の画像
カテゴリーをinputタグに初期値として表示させるためのもの
親・子・孫のname・idを取得し、ビュー側のcollection_selectで利用する情報
⑩ 該当商品のcategory_id(孫の数値)
② 親カテゴリーのnameとidが代入された配列
④ 子カテゴリーのnameとidが代入された配列
⑥ 孫カテゴリーのnameとidが代入された配列再入力時にビュー側のcollection_selectで利用する情報
⑧ categoryモデル内の全ての親カテゴリー
③ categoryモデル内の全ての子カテゴリー
⑤ categoryモデル内の全ての孫カテゴリー次にupdateメソッドの設定です。
editと同様に更新したい商品情報については、before_actionにて呼び出しを行っています。app/controller/items_controller.rbclass ItemsController < ApplicationController before_action :set_item, only: [:show, :edit, :update, :destroy] # 中略 def update # ① if item_params[:images_attributes].nil? flash.now[:alert] = '更新できませんでした 【画像を1枚以上入れてください】' render :edit else # ② exit_ids = [] item_params[:images_attributes].each do |a,b| exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i end ids = Image.where(item_id: params[:id]).map{|image| image.id } # ③ delete__db = ids - exit_ids Image.where(id:delete__db).destroy_all # ④ @item.touch if @item.update(item_params) redirect_to update_done_items_path else flash.now[:alert] = '更新できませんでした' render :edit end end end # 中略 private def item_params params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id) end def set_item @item = Item.find(params[:id]) end endまずこちらの記述は、画像が1枚もない時に更新できないようにif文でエラーハンドリングを記述しています。
item_params[:images_attributes].nil?の記述でparams内の画像が空か確かめています。
.nil?メソッドで空の場合はtureとなり、renderで編集画面に戻りflash.now[:alert]でエラーメッセージを表示します。①if item_params[:images_attributes].nil? flash.now[:alert] = '更新できませんでした 【画像を1枚以上入れてください】' render :edit else先ほどのif文でfalseとなった場合に②が動きます。
記述している内容は、exit_idsが更新ボタンを押した時点で入力されている画像のid、idsがDB内に保存されている更新前画像のidとなります。
exit_idsという配列を生成し、item_params[:images_attributes]という多次元配列内に含まれるidの値を取り出したいのでeach文でキーと値を順番に展開します。(|a,b| → a キーのこと b 値のこと)
そして多次元配列から値を取り出すために使用するのがdigメソッドです。
item_params[:images_attributes](多次元配列).dig(:"#{a(親キー)}",:id(子キー)).to_i(数値にする)という記述でidを取り出し、配列に代入します。
そしてidsにはDBから更新前の該当するレコードを取得し、mapメソッドにてidを抽出し代入します。②exit_ids = [] item_params[:images_attributes].each do |a,b| exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i end ids = Image.where(item_id: params[:id]).map{|image| image.id }ちなみにbinding.pryを使用してitem_params[:images_attributes]の中身を確認すると下記のように表示されます。画像1枚で更新ボタンをクリックしました。②のexit_idsに代入したい値は、子の配列内にある"322"という値です。
ターミナル(コンソール起動)[1] pry(#<ItemsController>)> item_params[:images_attributes] => <ActionController::Parameters {"0"=><ActionController::Parameters {"id"=>"322"} permitted: true>} permitted: true>③では、先ほどのexit_idsとidsを比較し、DBから初期値として編集画面に呼び出されていた画像を削除した場合にDB内の該当データを削除します。idsからexit_idsを引くことで削除されているidだけ残すことができます。それをdelete__dbに代入し、それを元にDBからレコードを検索し、destroy_allメソッドを使って削除します。
_allとしているのは複数レコードの場合も削除できるようにするためです。③delete__db = ids - exit_ids Image.where(id:delete__db).destroy_all④では、商品情報の更新を行っています。if文のエラーハンドリングにより更新できた場合には、update_dineルートを通り更新成功を伝える画面に遷移します。更新でなかった場合には編集画面に戻り、エラーメッセージを表示します。
一番最初の行に記述している@item.touchはitemsテーブルのupdate_atカラム(更新日時)も含めて更新するためのものです。
これを記述する理由は、後ほどご説明します。④@item.touch if @item.update(item_params) redirect_to update_done_items_path else flash.now[:alert] = '更新できませんでした' render :edit end次にupdate_doneメソッドの設定です。
更新成功を伝える画面には更新した商品詳細ページのリンクを設置しています。
先ほどのupdateメソッドの④で@item.touchを記述することによりitemsテーブルのupdate_atカラム(更新日時)を更新しました。orderメソッド、firstメソッドを使い、update_atカラム内を降順に一番目のものを@item_updateに代入します。app/controller/items_controller.rbdef update_done @item_update = Item.order("updated_at DESC").first end3.ビュー編集
全ての記述を載せると長くなってしまうのでここでは割愛して記述させて頂きます。
app/views/items/_form_edit.html.haml# ▼ 商品画像についての記載 .new__page__header = link_to image_tag("logo/logo.png", alt: "logo"), root_path = form_for @item do |f| = render 'layouts/error_messages', model: f.object .name__field#1 .form__label .lavel__name 出品画像 .lavel__Required [必須] #image-box-1{class:"#{@images.last.id}"} # ▼ ① プレビュー画像の表示 - @images.each do |img| .item-image{id:img.id} = image_tag(img.image.url,{width:"188",height:"180"}) .item-image__operetion .item-image__operetion--edit__delete__hidden 削除 %label.img-label{for: "img-file"} #image-box__container{class:"item-num-#{@images.length}"} #append-js-edit = f.fields_for :images do |image| .js-file_group{"data-index" => "#{image.index}"} = image.file_field :image, type: 'file', value:"#{image.object.id}",style: "", id:"img-file", class:'js-file-edit',name: "item[images_attributes][#{@item.images.count}][image]", data:{index:""} %i.fas.fa-camera # 中略 # ▼ カテゴリーについての記載 .append__category .category =f.collection_select :category_id, @category_children_array, :id, :name, {selected:@child_array}, {class:"serect_field"} - if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158 .category__grandchild#children_wrapper =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {},{selected:@grandchild_array, id:"child__category",class:"serect_field"} - else .category__child#children_wrapper =f.collection_select :category_id, @category_children_array, :id, :name, {},{selected:@child_array, id:"child__category", class:"serect_field"} .category__grandchild#grandchildren_wrapper =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {selected:@grandchild_array}, {class:"serect_field"} # 省略下記の記述でプレビュー画像を表示しています。
.item-image_operetion--editdelete_hidden 削除 のhiddenという記述がポイントです。js編集の際にご説明します。①- @images.each do |img| .item-image{id:img.id} = image_tag(img.image.url,{width:"188",height:"180"}) .item-image__operetion .item-image__operetion--edit__delete__hidden 削除下記の記述でカテゴリーを表示しています。
if文で孫なしの場合と孫ありの場合で条件分岐させています。
collection_selectタグに中身の内{}を記述していますがこれはidを付与するにあたり、オプションを記述する際の引数の順番の関係で記述しています。カテゴリー.append__category .category =f.collection_select :category_id, @category_children_array, :id, :name, {selected:@child_array}, {class:"serect_field"} - if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158 .category__grandchild#children_wrapper =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {},{selected:@grandchild_array, id:"child__category",class:"serect_field"} - else .category__child#children_wrapper =f.collection_select :category_id, @category_children_array, :id, :name, {},{selected:@child_array, id:"child__category", class:"serect_field"} .category__grandchild#grandchildren_wrapper =f. :category_id, @category_grandchildren_array, :id, :name, {selected:@grandchild_array}, {class:"serect_field"}下記の記述は、更新成功時遷移画面のビューファイルになります。
app/views/items/_form_edit.html.haml= render "top/header" .done#fullsize .done__title 商品情報を更新しました .done__backlink = link_to 'トップページへ戻る', root_path, class: 'link' .done__backlink = link_to '更新した商品を確認する', item_path(@item_update), class: 'link' .done__backlink = link_to '出品中の商品一覧を見る', users_path, class: 'link' = render "top/lower-photo" = render "top/footer" = render "top/btn"4.JS編集
下記の通りjsファイルを編集します。
app/assets/javascript/edit_items.js$(function(){ var dataBox = new DataTransfer(); var file_field = document.getElementById('img-file') $('#append-js-edit').on('change','#img-file',function(){ $.each(this.files, function(i, file){ //FileReaderのreadAsDataURLで指定したFileオブジェクトを読み込む var fileReader = new FileReader(); //DataTransferオブジェクトに対して、fileを追加 dataBox.items.add(file) var num = $('.item-image').length + 1 + i var aaa = $('.item-image').length + i // ① var image_id = Number($('#image-box-1').attr('class')) var append_div_count = Number($('div[id=1]').length) var noreset_id = image_id + append_div_count fileReader.readAsDataURL(file); //画像が10枚になったら超えたらボックスを削除する if (num == 10){ $('#image-box__container').css('display', 'none') } //読み込みが完了すると、srcにfileのURLを格納 fileReader.onloadend = function() { var src = fileReader.result // ② var html= `<div class='item-image' data-image="${file.name}" data-index="${aaa}" id="${noreset_id-1}"> <div class=' item-image__content'> <div class='item-image__content--icon'> <img src=${src} width="188" height="180" > </div> </div> <div class='item-image__operetion'> <div class='item-image__operetion--edit__delete__file'>削除</div> </div> </div>` const buildFileField1 = (num)=> { // ③ const html = `<div class="js-file_group" data-index="${num}" id=1> <input class="js-file-edit" type="file" name="item[images_attributes][${append_div_count+9}][image]" id="img-file" data-index="${num}value="${noreset_id}" > </div>`; return html; } $('.js-file-edit').removeAttr('id'); //image_box__container要素の前にhtmlを差し込む $('.img-label').before(html); $('#append-js-edit').append(buildFileField1(num)); }; //image-box__containerのクラスを変更し、CSSでドロップボックスの大きさを変えてやる。 $('#image-box__container').attr('class', `item-num-${num}`) }); }); // ④ // 10枚登録されていた場合にボックスを消す $(document).ready(function(){ var image_num = $('.item-image').length if (image_num==10){ $('#image-box__container').css('display', 'none') } }); // ⑤ $(document).ready(function(){ $('.js-file-edit').removeAttr('id'); var num = $('.item-image').length - 1 var image_id = Number($('#image-box-1').attr('class')) var append_div_count = Number($('div[id=1]').length) var noreset_id = image_id + append_div_count const buildFileField = (num)=> { const html = `<div class="js-file_group" data-index="${num}" id=1> <input class="js-file-edit" type="file" name="item[images_attributes][100][image]" id="img-file" data-index="${num}" value="${noreset_id}" > </div>`; return html; } $('#append-js-edit').append(buildFileField(num)); }); // ⑥ $(document).on("click", '.item-image__operetion--edit__delete__hidden', function(){ //削除を押されたプレビュー要素を取得 var target_image = $(this).parent().parent(); //削除を押されたプレビューimageのfile名を取得 var target_id = $(target_image).attr('id'); var target_image_file = $('input[value="'+target_id+'"][type=hidden]'); //プレビューを削除 target_image.remove() target_image_file.remove() //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更 var num = $('.item-image').length $('#image-box__container').show() $('#image-box__container').attr('class', `item-num-${num}`) }) // ⑦ $(document).on("click", '.item-image__operetion--edit__delete__file', function(){ //削除を押されたプレビュー要素を取得 var target_image = $(this).parent().parent(); var target_id = Number($(target_image).attr('id')); //削除を押されたプレビューimageのfile名を取得 var target_image_file = $('#append-js-edit').children('div').children('input[value="'+target_id+'"][type=file]'); //プレビューを削除 target_image.remove() target_image_file.remove() //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更 var num = $('.item-image').length $('#image-box__container').show() $('#image-box__container').attr('class', `item-num-${num}`) })1行目の記述で一番最後に保存された画像idを取得しimage_id変数に代入します。2行目でビューファイル内のdivでid=1と付与されているタグの数を数えappend_div_count変数に代入します。3行目でそれを足し合わせ、
noreset_id変数に代入します。noreset_idは、画像を追加した際に新たに表示されるinputタグのvalueオプションにセットするためのものです。これを利用して削除の動作を行います。また、プレビュー画像の親となるdivタグにも同じ数値のものを狙って削除するため、idオプションにセットします。(②)①var image_id = Number($('#image-box-1').attr('class')) var append_div_count = Number($('div[id=1]').length) var noreset_id = image_id + append_div_count下記の記述は、画像データをinputタグに入力した際にイベント発火して生成されるプレビュー画像のHTMLです。
削除の記述でfileという記述がポイントです。ビュー編集時に言っていたhiddenの記述と見分け、新た生成されたinputタグなのか、最初から表示されているinputタグなのか判断します。②var html= `<div class='item-image' data-image="${file.name}" data-index="${aaa}" id="${noreset_id-1}"> <div class=' item-image__content'> <div class='item-image__content--icon'> <img src=${src} width="188" height="180" > </div> </div> <div class='item-image__operetion'> <div class='item-image__operetion--edit__delete__file'>削除</div> </div> </div>`下記の記述は、画像データをinputタグに入力した際にイベント発火して生成されるinputタグのHTMLです。
③const html = `<div class="js-file_group" data-index="${num}" id=1> <input class="js-file-edit" type="file" name="item[images_attributes][${append_div_count+9}][image]" id="img-file" data-index="${num}value="${noreset_id}" > </div>`;下記の記述は、readyメソッドにより画面がロード完了するとイベント発火し、プレビュー画像の数を数えて10枚だった場合に画像を入力するボックスを削除するという記述になります。
④// 10枚登録されていた場合にボックスを消す $(document).ready(function(){ var image_num = $('.item-image').length if (image_num==10){ $('#image-box__container').css('display', 'none') } });下記の記述は、readyメソッドにより画面がロード完了するとイベント発火し、inputタグが生成される記述になります。これを行わない場合、最初のinputタグへの入力が、既存の表示されているinputタグに入力されてしまいズレが生じてしまうため、画面ロード時に生成する必要があります。
⑤$(document).ready(function(){ $('.js-file-edit').removeAttr('id'); var num = $('.item-image').length - 1 var image_id = Number($('#image-box-1').attr('class')) var append_div_count = Number($('div[id=1]').length) var noreset_id = image_id + append_div_count const buildFileField = (num)=> { const html = `<div class="js-file_group" data-index="${num}" id=1> <input class="js-file-edit" type="file" name="item[images_attributes][100][image]" id="img-file" data-index="${num}" value="${noreset_id}" > </div>`; return html; } $('#append-js-edit').append(buildFileField(num)); });下記の記述は、editで呼び出した画像データが入力されているinputタグとプレビュー画像をプレビュー画像の左下に表示されている削除をクリックした際に削除するものです。
⑥$(document).on("click", '.item-image__operetion--edit__delete__hidden', function(){ //削除を押されたプレビュー要素を取得 var target_image = $(this).parent().parent(); //削除を押されたプレビューimageのfile名を取得 var target_id = $(target_image).attr('id'); var target_image_file = $('input[value="'+target_id+'"][type=hidden]'); //プレビューを削除 target_image.remove() target_image_file.remove() //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更 var num = $('.item-image').length $('#image-box__container').show() $('#image-box__container').attr('class', `item-num-${num}`) })下記の記述は、新たに画像がinputタグに入力された際に生成呼び出した画像データが入力されているinputタグと新たに生成されたプレビュー画像をプレビュー画像の左下に表示されている削除をクリックした際に削除するものです。
⑦$(document).on("click", '.item-image__operetion--edit__delete__file', function(){ //削除を押されたプレビュー要素を取得 var target_image = $(this).parent().parent(); var target_id = Number($(target_image).attr('id')); //削除を押されたプレビューimageのfile名を取得 var target_image_file = $('#append-js-edit').children('div').children('input[value="'+target_id+'"][type=file]'); //プレビューを削除 target_image.remove() target_image_file.remove() //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更 var num = $('.item-image').length $('#image-box__container').show() $('#image-box__container').attr('class', `item-num-${num}`) })下記の記述は、価格が入力された際に販売手数料、販売利益を計算して出力するものです。
編集した箇所は、2段目、4段目に記述してる内容で、readyメソッドにより画面ロード時に販売手数料、販売利益を計算し、表示させるというものです。app/assets/javascript/sales_commission.js$(function() { var input=$("#item_exhibition_price"),fee=1/10,feeIncluded=$("#sales_commission_price"); input.on("input",function(){ feeIncluded.text(Math.floor($(this).val() * fee).toLocaleString()); if($('sales_commission_price').present!=0){ sales_commission_price.append("円"); } }); $(document).ready(function(){ feeIncluded.text(Math.floor($("#item_exhibition_price").val() * fee).toLocaleString()); if($('sales_commission_price').present!=0){ sales_commission_price.append("円"); } }); }); $(function() { var input=$("#item_exhibition_price"),tax=9/10,salesProfit=$("#sales_profit_proce"); input.on("input",function(){ salesProfit.text(Math.ceil($(this).val() * tax).toLocaleString()); if($('sales_commission_price').present!=0){ sales_profit_proce.append("円"); } }); $(document).ready(function(){ salesProfit.text(Math.ceil($("#item_exhibition_price").val() * tax).toLocaleString()); if($('sales_commission_price').present!=0){ sales_profit_proce.append("円"); } }); });これで編集機能完成です。
ここまで読んでくださり、ありがとうございます。
- 投稿日:2020-06-28T16:46:08+09:00
Rakefileの動きをRunbookで書く
はじめに
卒業研究の一環として,研究室で使われているPDFを作るためのRakefileを,Runbookの形式で書かく.
- Patrick Blesi, Github, runbook
ここでは書き換えの過程とできたものの使い方を書く.実行環境
- MacBook Air
- MacOS High Sierra(バージョン 10.13.3)
- Rubyバージョン 2.7.0
Runbookとは
手順を明確にし一つ一つのステップを踏みながら進んでいくプログラム.
RubyのDSLの一つとして段階的に自動化を書いていくことができる.
- Patrick Blesi, Runbook: A Ruby DSL for Gradual System AutomationなぜRakeじゃなくてRunbook?
私のように初心者でCUIを使うことにまだ慣れていない人間からすれば,Rakefileを実行すると勝手に処理が進み何をしているのかがわからなくなる.そこでRunbookを使うことにより手順が明確になり自分が何をしているのかがわかるようになる.
また,Rakefileでは実行途中でおかしく思っても止めることを最初からプログラムに書いていないと難しい,しかし,Runbookであれば細かく分かれた手順の中で毎回ユーザーに入力を求めて続けるかを選ぶことが出来る.プログラムの概要
Runbookの基本
基本はタイトル,セクション,ステップからなるプログラムで,ステップの中にプログラムを書いていく.
Runbookのコマンドにスタートのための雛形があるのでそれをインストールしてRunbookの基本を確かめる.> runbook generate runbook my_first_runbook
これによりディレクトリ内にmy_first_runbook.rbが生成される.
my_first_runbook.rbrequire "runbook" runbook = Runbook.book "My First Runbook" do description <<-DESC This is a runbook that... DESC section "SECTION" do step "STEP" do # Add statements here end end end if __FILE__ == $0 Runbook::Runner.new(runbook).run else runbook end実行すると下図のように続けるかを聞かれるだけのものが実行される.
> runbook exec my_first_runbook.rb Executing My First Runbook... Description: This is a runbook that... Section 1: SECTION Step 1.1: STEP Continue? (enter "h" for help) [c,s,j,P,e,h]> runbook exec my_first_runbook.rb Executing My First Runbook... Description: This is a runbook that... Section 1: SECTION Step 1.1: STEP Continue? (enter "h" for help) [c,s,j,P,e,h] c >> Continue to execute this step> runbook exec my_first_runbook.rb Executing My First Runbook... Description: This is a runbook that... Section 1: SECTION Step 1.1: STEP Continue? Continue to execute this stepRakefileをRunbookに移植
研究室使われているRakefileの構成は主に
- orgファイルの読み込み
- latexに変換
- report用の形式にlatexを変更
- platexを用いてPDFの作成の四つなので,これらをステップとしてRunbookを書く.
make_pdf.rbRunbook.book "Make PDF" do description <<-DESC This is a make PDF from org DESC section "Make pdf" do step "Load org file" step "Make tex file" step "Load and Convert tex file" step "Make pdf" end end実行結果
> runbook exec make_pdf.rb Executing Make PDF... Description: This is a make PDF from org Section 1: Make pdf Step 1.1: Load org file Continue? Continue to execute this step Step 1.2: Make tex file Continue? Continue to execute this step Step 1.3: Load and Convert tex file Continue? Continue to execute this step Step 1.4: Make pdf雛形ができたのでこれに中身を足していく.
セクションをlatexを作るものとPDFに変換するものに分けたほうが止めやすいと思ったのでそのようにし,中身を足す.
make_pdf.rbrequire "./convert" require "colorize" Runbook.book "Make PDF" do description <<-DESC This is a make PDF from org DESC section "Make latex" do $t_file = "report" step "Load org file" do note "Load org file" ruby_command do $file = Dir.glob("*.org")[0].match(/(.*).org/)[1] puts "your org file is " + $file.red + "." end end step "Make tex file" do note "Make tex file" ruby_command do system "emacs #{$file}.org --batch -f org-latex-export-to-latex --kill" end end step "Load and Convert tex file" do ruby_command do $lines = File.readlines("#{$file}.tex") $lines = convert_thesis($lines) File.open("#{$t_file}.tex", "w") do |f| $lines.each { |line| f.print line } end end end section "Make PDF" do step "Make pdf" do note "Make pdf" ruby_command do commands = ["platex #{$t_file}.tex", "bibtex #{$t_file}.tex", "platex #{$t_file}.tex", "dvipdfmx #{$t_file}.dvi"] commands.each { |com| system com } end end end endこれで,"Load org file" でディレクトリ内のorgファイルを読み込み,latexに変換しPDFを作るRunbookのプログラムができた.
convert.rb
は"Load and Convert tex file"内で使っている,convert_thesis
の関数を使うためのプログラム.内容はorgから作成したlatexをレポート用の雛形に変換するものである.convert.rbdef convert_thesis(lines) head = <<'EOS' \documentclass[a4j,twocolumn]{jsarticle} \usepackage[dvipdfmx]{graphicx} \usepackage{url} \setlength{\textheight}{275mm} \headheight 5mm \topmargin -30mm \textwidth 185mm \oddsidemargin -15mm \evensidemargin -15mm \pagestyle{empty} \begin{document} \title{} \author{学科 \hspace{5mm} 学籍番号 \hspace{5mm} your name} \date{} \maketitle EOS head2 = <<'EOS' {\small\setlength\baselineskip{15pt} % 参考文献は小さめの文字で行間を詰めてある \begin{thebibliography}{9} \bibitem{} \end{thebibliography} } \end{document} EOS new_line = [head] lines[31..-1].each do |line| new_line << line end new_line.each do |line| line.gsub!('\end{document}', head2) line.gsub!('\tableofcontents', "") end return new_line end指摘
研究室メンバーからの指摘
- orgファイルが複数ある場合選べるようにしたい.
- ディレクトリ内にできたファイル(.auxや.div)が自分で変更することがないのに残っていて見にくいので消したり,まとめたりして欲しい.
orgの選択を"Load org file"のなかで出来るようにする.
make_pdf.rbrequire "./convert" require "colorize" Runbook.book "Make PDF" do ... $t_file = "report" step "Load org file" do note "Load org file" ruby_command do str = Dir.glob("*.org") str.each do |name| puts "your org file is " + name.red + " ? (y or n)" res = $stdin.gets.chomp if res == "y" $file = name.match(/(.*).org/)[1] break elsif res == "n" if name == str[str.size - 1] puts "This directory not have the objective file".red exit end end end end end ... endorgファイルの名前ををメッセージとしてターミナル上に表示し,"y"か"n"を入力することで選べるようにした.全て”n”なら強制終了する.
次に一番最後に"Move report"とし,できたレポートをreportというディレクトリを作り移動させるようにした.
make_pdf.rbrequire "./convert" require "colorize" Runbook.book "Make PDF" do ... section "Make PDF" do step "Make pdf" do ... step "Move report" do note "Move report" ruby_command do commands = ["mkdir report", "mv -f #{$t_file}.* ./report", "open ./report/#{$t_file}.pdf"] commands.each { |com| system com } end end end end教授からの指摘
- 入力をせずにノンストップで全てを実行できる方法が無いのかどうかを調べて確認しろとの指摘.
gem RunbookのGithubに載っているマニュアルに記述があった.
> runbook exec --auto my_runbook.rbこれで実行すると入力なしに最後まで実行できる.
使い方
Runbookの導入
gemでインストールする.
gem install runbook
実行方法
runbook exec runbook.rb
とするとrunbook.rbが実行される.
- runbook.rbは自分のプログラム.
- プログラムのある場所のパスが分かればどのディレクトリでも実行可能
- 投稿日:2020-06-28T15:45:47+09:00
ruby on rails : gem install sqlite3 のコマンドが通らない
昨日(2020/6/27)からrailsの勉強をしているのですが、
gem install sqlite3 のコマンドでエラーが起きて困っております・・。↓エラー文
ERROR: While executing gem ... (Gem::FilePermissionError)
You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.下記参考動画を真似して環境構築したのですが(←どこかでおかしなことしてしまった?)、うまく環境変数の設定ができておらず、echo $PATH で変数の中身を確認してみると、
/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
と、なっていて、環境変数が上手く設定できていません。動画のように環境構築進めたのですが、途中でnode.jsをインストールしなさい、的なエラーが出たりで、
何度か環境変数の設定
echo 'eval "$(rbenv init -)"' > ~/.bash_profile
source ~/.bash_profile
を打ち込んだのを覚えています。原因としては.bash_profileの
/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin と2重になっているのを
/usr/local/bin:/usr/local/sbin
に変更できたらgem install sqlite3 のコマンドが通るのかなと思ったのですが、いかがでしょうか?
詳しい方がいらっしゃいましたら、ご教示いただけますと幸いです。
※足りない情報がありましたら申し訳ございません。。念のため 各バージョンも明記しておきます。
ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]
Rails 6.0.3.2
rbenv 1.1.2
Homebrew 2.4.2▼参考動画
https://www.youtube.com/watch?v=OHycvUQ4VNQ&t=1431s【追記】
railsのサーバーを立ち上げたりなどはできております。
※ただ、rails s だと localhost:3000に繋げないので、
rails s -b 0.0.0.0 でつないでいます。
- 投稿日:2020-06-28T15:18:41+09:00
Rubyでイーブイを進化させる
こんにちは。そうはぶです。
ちょうど2ヶ月前の4/27プログラミングを始めました。
現在はもっぱらGoを勉強していますが、プログラミングを始めた初日はProgateのRuby講座をやったのでRubyから入りました。
最初にRubyを選んだ理由は、2ヶ月前の未経験の自分でも知っているくらい有名な言語だったので特に深く考えず長いものに巻かれろみたいなノリで選びました。
Ruby講座をおよそ2日間かけて2周しました。後半から全然理解できなかったのをいまでもはっきり覚えています。笑
その後定着させるためすぐに作った初めてのオリジナルアプリがフォルダ整理していたら出てきたので記録として残しておきます。コード
その名も「イーブイ」です。そうですあのイーブイです。イーブイに水の石と炎の石どちらを使うか選んで進化させて、それと同時に新しい技を覚えるというだけのアプリです笑。
コードがこちらです。eevee.rbclass Pokemon attr_accessor :name, :sex, :skill def initialize(name:, sex:, skill:) @name = name @sex = sex @skill = skill end def who "初めまして。#{@name}だよ。\n僕の性別は#{@sex}だよ。\n必殺技は#{skill}だよ。" end end eevee = Pokemon.new( name: 'イーブイ', sex: 'オス', skill: 'しっぽをふる' ) puts eevee.who class Evolution < Pokemon attr_accessor :new_name def name_change @new_name = "hanako" end def who_e "おめでとう!イーブイは#{name}に進化したよ!\n新しく#{skill}を覚えたよ!" end end fires = Evolution.new( name:'ファイアーズ', sex: nil, skill:'かえんほうしゃ' ) showers = Evolution.new( name:'シャワーズ', sex:nil, skill:'れいとうびーむ' ) puts "どの石を使いますか?\n選択してください。\n\n1.炎の石\n2.水の石" answer = gets.chomp.to_i if answer == 1 puts fires.who_e elsif answer == 2 puts showers.who_e end #=================================@nameに入力した名前を表示を代入できなかった。================== # puts "ニックネームをつけますか?\n選択してくだい。\n\n1.つける\n2.つけない" # nickname_answer = gets.chomp.to_i # if nickname_answer == 1 && answer ==1 # puts "新しい名前を入力してください。" # fires.name_change # puts "名前が#{@new_name}に変わったよ!" # elsif nickname_answer == 1 && answer ==2 # puts "新しい名前を入力してください。" # showers.name_change # puts "名前が#{@new_name}に変わったよ!" # else # puts "終わりです。" # end感想
実際のポケモンみたいにニックネームを付けようとしたもののつけることができなくて苦労したコメントまでついてました。笑
このときは頭混乱してなぜかできたと言う感じだったので、2ヶ月前に比べると自分も成長したなあと感じています。
5月頭にRailsでツイッター風アプリを作ってからはGoに乗り換えてずっとGoをやっていますが、またRubyも完全に忘却する前に触りたいです。参考
Rubyの書籍はこれのKindle版しか買いませんでした。すぐ乗り換えてしまったためあまり読めてないですが、Progateを終えたばかりの自分にはちょうどよかったです。
- 投稿日:2020-06-28T10:42:32+09:00
【備忘録】本番環境への更新[heroku]
はじめに
本番環境へ更新する時、毎回緊張しているたかしです。
今回はherokuで初の本番環境へ更新しました。
初めてということで案の定エラーが出て焦りまくりだったんですが、原因はとても簡単でした。。今回はその解決方法を自分が忘れないためにも、他の人がこのエラーに遭遇してすぐ解決できるように記事として投稿いたします。
エラー画面
現状では、pushできる環境ではありません的なことを言っています。
解決方法
herokuでのデプロイはgitと連動しているため、変更内容があった場合は下記コマンドを実行して変更内容を反映できるようにしましょう。
$ git add $ git commit -m "Update application" $ git push heroku master #マイグレーションの変更等をした際は下記コマンド実行 $ heroku run rails db:migrate終わりに
上記のとおりすごい簡単な話でした。
毎度本番環境には変な緊張感を持ってしまいます。手順的にはすごい簡単なんですが、もしつまずいてる人がいたら是非参考にしてみてください。
- 投稿日:2020-06-28T10:13:44+09:00
gem 'rails-i18n', '~> 6.0.0'�を追加し、bundle install でエラーが出た時の解決方法
エラーの出現
Rails5
でエラーメッセージの日本語化をするために
Gemfile
に下記を追加gem 'rails-i18n', '~> 6.0.0'次に、ターミナルにて
$ bundle installを実行したところ、下記のエラーが発生しインストールができない。
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies... Bundler could not find compatible versions for gem "railties": In snapshot (Gemfile.lock): railties (= 5.2.4.3) In Gemfile: coffee-rails (~> 4.2) was resolved to 4.2.2, which depends on railties (>= 4.0.0) rails (~> 5.2.4, >= 5.2.4.3) was resolved to 5.2.4.3, which depends on railties (= 5.2.4.3) rails-i18n (~> 6.0.0) was resolved to 6.0.0, which depends on railties (>= 6.0.0, < 7) sass-rails (~> 5.0) was resolved to 5.1.0, which depends on railties (>= 5.2.0) web-console (>= 3.3.0) was resolved to 3.7.0, which depends on railties (>= 5.0) Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict.エラー原因
gem 'rails-i18n', '~> 6.0.0'のバージョン指定が間違っていた。
上記バージョンはrails6
の場合だった。解決方法
今回の
rails
のバージョンはrails5
だったので、その場合は、gem 'rails-i18n', '~> 5.1'とすればOK!!
その後、
$ bundle installを実行したら無事インストールできた。
- 投稿日:2020-06-28T09:03:13+09:00
ある範囲内の整数にカプレカ数があるかRubyで調べてみた
背景
高校までは数学が一番得意で、大好きでした。
なのに文系の大学に入った私は数学とは縁遠い生活を送り
そして社会人になり早十年…
微積分も三角関数も忘れてしまった左脳だけ発達してる系エンジニアですが、
最近はYouTubeのおかげで数学に触れる機会が少しずつ増えました。
入試問題を解いたり、知られざる大学数学の世界を気軽に触れられるいい時代です。本日は以下の動画で紹介されていた面白い性質を持った整数「カプレカ数」について
Rubyを使って検証していきます。※元ネタ
予備校のノリで学ぶ「大学の数学・物理」
最大から最小を引いて元通り!【カプレカ数】カプレカ数とは
カプレカ数(カプレカすう、Kaprekar Number)とは、次のいずれかで定義される整数である。
1. 2乗して前の部分と後ろの部分に分けて和を取ったとき、元の値に等しくなるもの。
2. 桁を並べ替えて最大にしたものから最小にしたものの差を取ったとき、元の値に等しくなるもの。引用元:Wikipedia
今回調べるのは定義2の方です。
例えば495を例に考えてみます。
495を並べ替えて作れる最大の数は954、最小の数は459です。
954 - 459 = 495ですから、元の数字495と等しくなりました!
面白いですね!このような数字をカプレカ数といい、
我々が普段目にする桁数の数字では数えられるほどしかありません。では本題のRubyプログラムに入っていきましょう。
任意の整数がカプレカ数かどうかを判定する
kaprekar.rbnum = 627 # 配列に格納 arr = num.to_s.split('').map!(&:to_i) p arr # => [6, 2, 7] # 並べ替えて最大と最小を作る max = arr.sort{ |a, b| a <=> b }.join('').to_i min = arr.sort{ |a, b| b <=> a }.join('').to_i p max # => 762 p min # => 267 # 最大 - 最小が元の数字と同じかどうかの判定 diff = max - min p diff == num # => falseこれで
num
を書き換えることで任意の整数1つのみの判定を行えるようになりました。特定の範囲の中にカプレカ数かどうかを判定する
ここから繰り返し処理を用いて特定の範囲の中にカプレカ数があるかどうかを探してみましょう。
kaprekar.rbresult = [] num = 1 final = 1_000_000 while num < final do arr = num.to_s.split('').map!(&:to_i) min = arr.sort{ |a, b| a <=> b }.join('').to_i max = arr.sort{ |a, b| b <=> a }.join('').to_i diff = max - min if diff == num result.push("【#{num}】 #{max} - #{min} = #{diff}") end num += 1 end if result.empty? result.push("該当なし") end puts result(可読性重視で敢えて三項演算子は使っていません)
terminal【495】 954 - 459 = 495 【6174】 7641 - 1467 = 6174 【549945】 995544 - 445599 = 549945 【631764】 766431 - 134667 = 631764上記例では6桁の整数までで試したところ、4つのカプレカ数が見つかりました。
5桁には存在しないというのもまた不思議!余談
PCのスペックにもよりますが、100万回も繰り返し処理したら結構時間かかります。
冒頭で紹介した495という数字を見て
495 = 99 × 5がすぐに思いついたあなたは数学的センスをお持ちですね。
そう、カプレカ数はどれも9の倍数です。なので100万回やらなくても、9の倍数のみ検証すればもっと処理は軽減できます。
もっといい書き方がありましたらコメントで教えてください!
(2020/06/29 追記)
早速コメントをいただき改良版をコメント欄に投稿しました!
- 投稿日:2020-06-28T08:46:47+09:00
Ruby と Python で解く AtCoder ABC172 C 累積和 二分探索
はじめに
AtCoder Beginner Contest 172 に参加しました。
AtCoder さん、AtCoder Problems さん、ありがとうございます。今回のお題
AtCoder Beginner Contest C - Tsundoku
Difficulty: 878今回のテーマ、累積和 + 二分探索
最初は、動的計画法と考え解いていましたが
TLE
を貰い大幅な時間のロス、二分探索に切り替えました。
入力例1ですと、次の表の各行で、机Aと机Bの累積和がK分を超えない本数の最大値を求めます。
1 2 3 4 5 6 7 60 90 120 80 150 80 150 60 90 80 150 80 150 60 80 150 80 150 80 150 80 150 Ruby
ruby.rbn, m, k = gets.split.map(&:to_i) a = gets.split.map(&:to_i) b = gets.split.map(&:to_i) c = Array.new(n + 1, 0) n.times do |i| c[i + 1] = c[i] + a[i] end d = Array.new(m + 1, 0) m.times do |i| d[i + 1] = d[i] + b[i] end cnt = 0 0.upto(n) do |i| break if c[i] > k e = d.bsearch_index{_1 > k - c[i]} if e.nil? cnt = i + m if cnt < i + m else cnt = i + e - 1 if cnt < i + e - 1 end end puts cntruby.rbc = Array.new(n + 1, 0) n.times do |i| c[i + 1] = c[i] + a[i] end d = Array.new(m + 1, 0) m.times do |i| d[i + 1] = d[i] + b[i] end累積和です。
ruby.rbe = d.bsearch_index{_1 > k - c[i]} if e.nil? cnt = i + m if cnt < i + m else cnt = i + e - 1 if cnt < i + e - 1 end二分探索です。配列の最大値を超えた場合、
nil
を返しますので、その処理が入っています。Python
python.pyfrom sys import stdin import bisect def main(): input = stdin.readline n, m, k = map(int, input().split()) a = list(map(int, input().split())) b = list(map(int, input().split())) c = [0] * (n + 1) for i in range(n): c[i + 1] = c[i] + a[i] d = [0] * (m + 1) for i in range(m): d[i + 1] = d[i] + b[i] cnt = 0 for i in range(n + 1): if c[i] > k: break e = bisect.bisect_right(d, k - c[i]) if cnt < i + e - 1: cnt = i + e - 1 print(cnt) main()python.pye = bisect.bisect_right(d, k - c[i]) if cnt < i + e - 1: cnt = i + e - 1二分探索です。配列の最大値を超えた場合、配列の
要素数+1
を返します。
Ruby Python コード長 (Byte) 433 570 実行時間 (ms) 349 246 メモリ (KB) 44860 47636 まとめ
- ABC 172 C を解いた
- Ruby に詳しくなった
- Python に詳しくなった
参照したサイト
bisect --- 配列二分法アルゴリズム