- 投稿日:2019-02-26T22:00:26+09:00
Ruby irb、テキストエディタ使用とエラー解決方法
動作環境はMacとなります。
主に自分の勉強用メモとして残しています。Rubyプログラム irb使用
文字列はシングルクオートかダブルクオートで囲む
irb(main):001:0> "Hello, World" => "Hello, World"#Rubyは最後の式を自動的に出力するので、プログラムとは言えない。putsは何かを出力する命令で、これをつけることでプログラムと言える。
irb(main):004:0> puts 'Hello World!'#これがプログラム Hello World! => nil#putsは必ずnilを返す数値の計算
irb(main):005:0> 1 + 1 => 2 irb(main):006:0> 10 - 1 => 9irbで記述したプログラムをクリアしたい時
clear か control+lキー
irbを終了したい場合
exit か contol+dキー
テキストエディタ使用
この環境ではAtom使用しています。
$はターミナルでの操作を表します。ファイル作成
$mkdir ruby_projects #mkdirはmake directoryの略でファイル作成の意味$ls #今いるディレクトリの中身を確認 $cd ruby_projects/ #今いるディレクトリから移動 $touch hello.rb #ファイル作成 $atom . #Atomを開く時のコマンドrubyプログラムを作成する場合、拡張子は.rbとする。
テキストエディタ
hello.rbputs 'Hello, World!'#行末に;をつけてもエラーにはならないが、rubyは改行で認識されるため通常書かない puts 10 + 3ターミナル
$ ruby hello.rb #ruby + ファイル名で出力 Hello, World! 13Linuxコマンド
現在のディレクトリをカレントディレクトリという。
ディレクトリとファイルはざっくり同じ。$ pwd #現在いるファイルの階層を表示 $ cd .. #現在いるフォルダの1つ上の階層に移動 $ ls #現在いるフォルダの中身を表示エラーを自力で取り除く方法
hello.rbputs 'Hello, World!' put 10 + 3 #putsをputと間違えたターミナル
$ ruby hello.rb Hello, World! #問題なく出力される。 hello.rb:2:in `<main>': undefined method `put' for main:Object (NoMethodError) #hello.rbファイルの二行目のputというメソッドがないよというエラー Did you mean? puts putc #もしかしてこういう意味?とエラーが教えてくれている。メソッドとは処理のまとまりのこと。
エラーメッセージをグーグル検索すると、解決の意図が見つかることが多い。
特にrubyは日本で広く普及されているので、情報が見つかりやすい。以上を踏まえて修正
hello.rbputs 'Hello, World!' puts 10 + 3 #putをputsへ修正ターミナル
$ ruby hello.rb Hello, World! 13正常にプログラムが実行された!
- 投稿日:2019-02-26T20:57:31+09:00
サーチアルゴリズムとソートアルゴリズムをRubyでやってみた。
データ構造とアルゴリズムをなぜ学ぶべきか
あるデータを探索して取得したいとか、この大量のデータをソートしたいとかなった時に、既存の関数を使うのも良いけど、データの構造やその都度のデータ量を考慮して、最適なアルゴリズムを取捨選択できるようになっていた方がいいよねといった形のフィードバックを、最近就活時の面接時に頂いて、なるほどね〜と思いつつ、やってみた所存。まだまだ、学んでいる途中ですので、今後追記していきます。
サーチアルゴリズム
いわば探索アルゴリズムですよね。配列中から、指定した値があるかどうか探索するアルゴリズムです。
リニアサーチ
一番シンプルなアルゴリズムで、配列中から指定した値を先頭から順に一致するかどうか検証して、一致したもののインデックスを返す仕様です。
def linear_search(searchArr,searchNum) index = 0 count = 0 searchArr.each do |arr| if arr == searchNum count += 1 else index += 1 end end if count == 0 puts "value:#{searchNum} Array:#{searchArr} There isn't it." else puts "value:#{searchNum} Array:#{searchArr} There is it at index\s#{index}." end endメリット
下記の通り、先頭から順に要素を探索するだけなので、実装が容易である。
デメリット
先頭から1個ずつ探索する故に、かなり速度が落ちる。
バイナリサーチ
中央の要素を取得して、探索したい値がそれより大きいか小さいかを比較して、検証する要素を右や左に移しつつ、探索するアルゴリズムです。少し複雑ですので、手順を紹介します。
手順
1.配列をソートする。(昇順でも降順でも可能)
2.配列の中央にある要素を目的の要素と比較する。
3.中央の要素が、目的の要素より大きければ、中央より後半を探索。
4.そうではない場合は、前半を探索する。
def binary_search(array,searchNum) array.sort! leftIndex = 0 rightIndex = array.index(array.last) midIndex = (rightIndex + leftIndex) / 2 loop do if searchNum == array[midIndex] puts "Array->#{array}:There is it at #{midIndex}!" break; elsif searchNum > array[midIndex] midIndex += 1 else midIndex -= 1 end end endメリット
リニアサーチは、先頭の要素から探索するのに対して、バイナリサーチは、ソート→中央の値を比較するという手順で、より速度が上がる。
デメリット
ソートする手順を踏む必要があるので、要素が「数値」である必要がある。
速度を比較してみた
benchmarkというライブラリを使って比較してみます。
require 'benchmark' def linear_search(array_size,searchNum) array = (1..array_size).to_a index = 0 count = 0 array.each do |arr| if arr == searchNum count += 1 else index += 1 end end if count == 0 # puts "value:#{searchNum} Array:#{array} There isn't it." else # puts "value:#{searchNum} Array:#{array} There is it at index\s#{index}." end end def binary_search(array_size,searchNum) array = (1..array_size).to_a array.sort! leftIndex = 0 rightIndex = array.index(array.last) midIndex = (rightIndex + leftIndex) / 2 loop do if searchNum == array[midIndex] # puts "Array->#{array}:There is at #{midIndex}!" break; elsif searchNum > array[midIndex] midIndex += 1 else midIndex -= 1 end end end p "---100 elements---" puts Benchmark.measure{linear_search(100,50)} puts Benchmark.measure{binary_search(100,50)} p "---1000 elements---" puts Benchmark.measure{linear_search(1000,100)} puts Benchmark.measure{binary_search(1000,100)} p "---10000 elements---" puts Benchmark.measure{linear_search(10000,1000)} puts Benchmark.measure{binary_search(10000,1000)}結果
ソートアルゴリズム
要素である数値を昇順、降順に並べるアルゴリズムです。
バブルソート
バブルソートは、そのソートの動きから由来しているそうです。やってみます。
手順
1.先頭の要素を1,その次の要素を2とします。まず、1と2を比較します。1の方が2より小さければ交換せず、そのまま、2の要素を1として、2の要素の次の要素を2として、同じように比較します。(つまり、比較する要素同士を右に移行します。)
2.隣り合う要素を比較して、1より2の要素の方が大きければ、要素を交換してあげます。そして上記のように、比較する要素同士を右に移行して同じように比較します。
3.1,2を繰り返します。
def bubble_sort(array) arrayLength = array.length for length in 1..arrayLength for nextIndex in 1..(arrayLength - length) if array[nextIndex-1] > array[nextIndex] t = array[nextIndex-1] array[nextIndex-1] = array[nextIndex] array[nextIndex] = t end end end p array endメリット
実装がかなり容易でかつ、わかりやすい。
デメリット
要素同士をスワップしながら、ソートしていくので、大量のデータをソートするので速度は落ちる。
選択ソート
ループ中に発見した最小値を予め変数に記憶しておくやり方で、ループ回数を抑えられるソートです。
手順
1.先頭の要素を仮の最小値として指定する。
2.次の要素から、1個ずつ比較していき、仮の最小値より小さい値があるとき、そのインデックスを記憶しておく。
3.さらに次の要素→次の要素と比較して、更に小さい値があれば、先ほどインデックスを記憶しておいた変数を更新する。更新した変数が最小値だった場合、先頭の要素とスワップする。
4.0番目の要素の仮最小値としての1,2,3の検証が終了したら、1番目の要素にずらして、1,2,3を繰り返す。
def selection_sort(array) len = array.length 0.upto(len-2).each do |i| #検証回数 minIndex = i #仮最小値設定 (i+1).upto(len-1).each do |j| #検証要素範囲 minIndex = j if array[j] < array[minIndex] #最小値を随時記憶→更新 end if i != minIndex temp = array[minIndex] array[minIndex] = array[i] array[i] = temp p array #現在の仮最小値でない場合SWAP end end return array endメリット
バブルソートに比べて交換する手順が1回だけで抑えられるので、若干バブルソートに比べて速度が上がります。
速度を比較してみた
require 'benchmark' def bubble_sort(array) arrayLength = array.length for length in 1..arrayLength for nextIndex in 1..(arrayLength - length) if array[nextIndex-1] > array[nextIndex] t = array[nextIndex-1] array[nextIndex-1] = array[nextIndex] array[nextIndex] = t end end end return array end def selection_sort(array) len = array.length 0.upto(len-2).each do |i| minIndex = i (i+1).upto(len-1).each do |j| minIndex = j if array[j] < array[minIndex] end if i != minIndex temp = array[minIndex] array[minIndex] = array[i] array[i] = temp end end return array end p "---100 elements---" puts Benchmark.measure{bubble_sort((1..100).to_a.shuffle)} puts Benchmark.measure{selection_sort((1..100).to_a.shuffle)} p "---1000 elements---" puts Benchmark.measure{bubble_sort((1..1000).to_a.shuffle)} puts Benchmark.measure{selection_sort((1..1000).to_a.shuffle)} p "---10000 elements---" puts Benchmark.measure{bubble_sort((1..10000).to_a.shuffle)} puts Benchmark.measure{selection_sort((1..10000).to_a.shuffle)}結果
参考記事
下記のリンクのサイトでは、動きでアルゴリズムがわかるようになっていて非常に理解しやすいです。是非使ってみてください。
https://visualgo.net/jaQiita参考
https://qiita.com/fukumone/items/5a0c0964f2c713b0699d1週間で学ぶデータ構造とアルゴリズム
http://sevendays-study.com/algorithm/index.html
- 投稿日:2019-02-26T20:44:13+09:00
Ruby on RailsでRoute Error
Route Errorがでた!
簡単なTweeterみたいなやつ作ってた時の話
スクールで初のエラーっぽいエラーに出会った。
原因
view側のForm_tagにてルートにリクエストを送っていたが、ルート側でgetとしてしまった。
解決
get → postに変更して解決
エラーに出会い思ったこと
スクールのカリキュラムでエラーに出会わず来ていたので
人生初エラーで少しテンション上がりました。
1回エラー出ると、そこの部分は凄い気を付けて見るようになるから
これからもどんどんエラーに出会っていきたい...sublimeTextで思ったこと
.html.erbのファイルからSCSSのクラスの定義に飛べるアドオンないかな
.htmlからcssファイルには飛べるのに、上記は飛べないの少し不便以上!
- 投稿日:2019-02-26T17:51:20+09:00
Daruを使う
この記事ではRubyのデータフレームであるDaruを紹介します。
Daruとは
Pythonにおけるpandas。Daruはインドのお酒の意味だそうです。
Daruの特徴
columnをvectorと呼ぶところ。
青い四角で強調されている部分がVectorです。関連プロジェクト
- Daru::View
- Google Charts(GoogleVisualr), Highcharts(lazy_high_charts) による可視化ライブラリ。
- Daru-IO
- 標準でサポートされていないデータ形式等の読み込み。完成度はそんなに高くないかも。
- Nyaplot
- Daru標準のプロッティングライブラリ。綺麗だが古いのでおすすめしにくい。
クラス
- Daru::DataFrame
- Daru::Vector
- Daru::Index
- Daru::DateTimeIndex
それから
- Daru::Core::GroupBy
- Daru::Core::MergeFrame
インストール
gem install specific_install gem specific_install https://github.com/SciRuby/daru
require 'daru'Jupyter Notebook + IRuby環境が推奨される。
データを読み込む
CSVファイル
Daru::DataFrame.from_csv "filepath"インターネット上のCSVファイル
require 'open-uri' url = 'https://raw.githubusercontent.com/pandas-dev/pandas/master/pandas/tests/data/iris.csv' df = Daru::DataFrame.from_csv(url)一般的なもの
Daru::DataFrame.new hogehoge Daru::DataFrame.rows 配列 # 数値がStringになる場合あり、何らかの改善の余地RのデータセットでDaruをためすとき。
require 'rdatasets' df = RDatasets.load :datasets, :irisそのほか
from_activerecord
from_excel
from_html
from_plaintext
from_sql
など
それでも困る場合はあまり期待せずにDaru-ioを見ると色々ある。データの状態を確認する
先頭の5行を表示する
df.head 5最後尾の5行を表示する
df.tail 5行数・列数を確認する
df.shape df.ncols # 列数 df.nrows # 行数 df.size # 行数列名の取得
df.vectors #<Daru::Index(5): {SepalLength, SepalWidth, PetalLength, PetalWidth, Name}>返却されるのはIndexクラスのオブジェクト。Vectorや配列ではない。
データを選択する
Ⅲ 列 Ⅲ
[]
でVectorの名前を指定df['vector1', 'vector2'] df.vector1
at
列数で指定df.at 0, 1
[列数]
は返却されたデータフレームのベクトルの名前も数になるdf[0, 1]≡ 行 ≡
row[]
でindexを指定# 個別 df.row[100] # df.row 100 はエラー df.row [100] もエラー # 範囲 df.row[100..105] # 複数 df.row[100,101,102] # indexは数値とは限らない df.row[:a]
row_at
で行数を指定df.row_at 100 df.row_at 100, 101 df.row_at 100..105
df.row[100]
等として、取り出すことは可能だが、取り出したベクトルの名前も100になる。行列同時指定
df['col1'][3]where で抽出
df.where(df.col1.eq "hoge") df.where(df.col1 > 10 ) df.where(df.col1 > 10 | df.col2 < 10) df.where((df.col1 > 10 | df.col2 < 10 ) & df.col3.eq 10) # and or df.where(((df.col1 > 10).or df.col2 < 10).and df.col3.eq 10)
gt
lt
eq
&
|
and
or
など
データフレーム全体を真偽値でマスクする方法はないかfilter で抽出
df2 = df.filter do |vector| vector.type == :numeric and vector.median < 50 end df2 = df.filter(:row) do |row| row[:a] + row[:d] < 100 endkeep_row_if / keep_vector_if
破壊的メソッド
df.keep_row_if do |row| row[:a] > 5 end # return [index, index, ...]要素のカウント
vector.value_counts df.Species.value_countsDaru::Vector(3)
setosa 50
versicolor 50
virginica 50vector.uniq欠損値を扱う
df.has_missing_data? df.include_values? nil vector.count_values nil df.filter_rows{|r| r.include? nil, Float::NAN} df.dup_only_valid df.clone_only_valid # 'shallow' copy df.collect{|v| v.count_values nil}補間
現状ではできないと思われる。
df.replace_values nil, Float::NAN df.rolling_fillna df.rolling_rillna!each map collect reduce inject all? any?
each
map
はaxis:
(:row
:vector
:column
)を指定する。all?
any?
も同様each_vector
,each_row
,map_vectors
,map_rows
each_vector_with_index
,each_row_with_index
,map_vectors_with_index
,map_rows_with_index
- collect の挙動は
map
と違い、vectorになる。- map は配列を返却する。
- map! は破壊的でdataframeになる
- recode は破壊的ではないが、dfを返却する
inject
はない。collect_matrix
clone / dup
名前の違うメソッドは、それぞれ微妙に挙動が違う傾向がある。
map
は配列を返し、collect
はVectorを返し、map!
はdfを返し破壊的、recode
もdataframeを返すが破壊的ではないという関係性はわかりにくい。ここに書いてあることも間違っているかもしれない。現状まだ把握しきれていない。apply_method
引数はsymbol。procも引数に取る。
aggregate
df.aggregate(num_100_times: ->(df) { (df.num*100).first }) df.aggregate(num: :mean)インデックス
列名の変更
rename_vectors
df.rename_vectors :a => :alpha, :b => :beta単なる
rename
はデータフレームの名称変更を意味するので要注意。df.name # データフレームの名前列の並び替え
df.order = ["col2", "col1"] df.order = Daru::Index.new["col2", "col1"]行の並び替え
df.reindex Daru::Index.new([1,3,2,4])特定の列をindexにする
df.set_index "vector"ソートする
df.sort ["col1"] df.sort ["col1", "col2"] df.sort ["col1"], assending: false df.sort! ["col1"]引数は配列をとるので注意する
df.sort [:b], by: {b: lambda { |a| a.length } }, handle_nils: trueマージする
Daruでは
join
というメソッドを使う。
how
で:inner
:outer
:right
:left
などを指定。new_df = df.join(df2, how: :inner, on: [:name])一方で
merge
という名前のメソッドもある。時系列インデックス
DateTimeIndex
from_csv => datetime => set_index
vecotor['2014']
month
などを利用して df["month"] = df.index.month
と行を追加して、`group_by("month") などとすれば月別の集計もできるかも。(現状は既知のバグあり)統計量
df.max df.min df.mean df.median df.std df.variance df.rolling_XXX #XXXにはmin, max, meanなどが入る df.standardize # etc表示が見にくいときは
round
が使える。df.group_by("Species").mean.round(3)describe
df.describe
summary
はterminal出力用group_by
gb = df.group_by [:col1, :col2] gb.mean gb.get_group :col1gbは
Daru::Core::GroupBy
クラスのオブジェクト。pivot
df.pivot_table(index: [:a], vectors: [:b], agg: :sum, values: :e)行や列の追加
最後の列に追加するとき
df["new_name"] = Vector.new
add_vector
add_row
などで列数・行数を指定できる。
列の追加はonly_numerics
type には
object
とnumeric
の2種類がある。
only_numerics
は便利で使用頻度が高いdf.only_numerics # 数値だけのデータフレームカテゴリー
split_by_category
可視化ライブラリ
Nyaplotを使いたい場合は erector もインストールする。
Nyaplotはかっこいいのだが、メンテナンスが事実上停止しており、今後に不安がある。
現状では実用性で Daru::View が頭一つ抜ける。Google Chartsは使いやすい。Daru::View.plotting_library = :googlecharts Daru::View::Plot.new(df, title: "TITLE", type: :line, vAxis: {title: "y"}, hAxis: {title: "x"}, width: 600, height: 500).show_in_irubyしかし、Rubyの可視化ライブラリは色々あるので、今後状況が変わるかもしれない。
書き出す
write_csv
おわりに
Daruの歩き方
困ったら
yard server -g
でドキュメントを読みます。
IRuby上でri
でドキュメントを見たり、show-source
や でソースコードに当たってみます。show-source Daru::DataFrame#from_csvとすれば、該当部分のソースがJupyterに表示される。
ソースコードは平易で、詳しくない人でもまあまあ読める。怪しいと思ったら、ソースコード全体をセルにコピペして、オープンクラスの仕組みを使ってデバッグ。バグを踏んだらぜひissueに投稿しよう。ri Daru::DataFrame.some_nice_methodDaruからの声
下記のポエムを頭の片隅におくと、Daruのメソッドの名付け方が理解しやすいかもしれない。
…きこえますか…きこえますか…ルビイストよ…Daruです。いいですか、データは大事です。データとは、単なる数字の列ではありません。それは誰かが膨大な時間をかけて収集した貴重な資料です。たとえば…パン10個、ゾウ10頭、10光年、10年、Version10…。いずれも同じ数字の 10 ですが意味はだいぶ違うでしょ? 実はデータには、数字以上の情報が含まれているのですよ!君は、その数字の列が本当は何を意味しているのか理解しなければなりません…。
…ベクトルをいくつか集めたのがデータフレームです。ベクトルの「名前」には、データを記述した人間が観測した目的や意図が、名前という形で刻印されています。データフレームとは、単なる行列計算の道具ではないのです。それは、数字に付与された人間の残存思念を、ベクトルの名称という形でサポートするツールなのです。だから、ベクトルは番号ではなく、なるべく「名前」で呼んであげなければなりません。Daruの世界ではベクトルはRow(行)などよりずっと偉いのです!
(※Daruの公式見解ではなく筆者がDaruをみて感じた感想です)
QiitaにおけるDaruの記事
参考資料(pandas)
- 投稿日:2019-02-26T14:46:54+09:00
React Nativeアプリをmacのjenkinsでビルドしたらxcprettyでエラー
iMacをjenkinsのスレーブとしている環境でReact Nativeアプリのビルドを使用としたところ、以下のエラーが出力されジョブがフリーズしてしまいました。
[14:11:54]: ▸ /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/parser.rb:429:in `===': invalid byte sequence in US-ASCII (ArgumentError) [14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/parser.rb:429:in `update_test_state' [14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/parser.rb:304:in `parse' [14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/formatters/formatter.rb:87:in `pretty_format' [14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/printer.rb:19:in `pretty_print' [14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/bin/xcpretty:84:in `block in <top (required)>' [14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/bin/xcpretty:83:in `each_line' [14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/bin/xcpretty:83:in `<top (required)>' [14:11:54]: ▸ from /usr/local/bin/xcpretty:22:in `load' [14:11:54]: ▸ from /usr/local/bin/xcpretty:22:in `<main>'解決方法はjenkinsの環境変数のインジェクトで
RUBYOPT=-EUTF-8
を設定しておくこと。
なんかrubyのこの問題よくハマる。
- 投稿日:2019-02-26T13:43:25+09:00
Sidekiqで処理待ち、処理中のjobを取得する
ActiveJob
でqueue adaptorにSidekiq
を使ってて、ジョブの状況を確認したいので調査しました環境
sidekiq (5.1.1)
コード
# 処理中のjob workers = Sidekiq::Workers.new workers.each do |_process_id, _thread_id, work| p work end # 処理待ちのjob queues = Sidekiq::Queue.all queues.each do |queue| queue.each do |job| p job.klass, job.args, job.jid end end以上です
参考
- 投稿日:2019-02-26T10:00:36+09:00
each内でのrenderパーシャルをcollectionに変更し高速化
問題
ビュー内で部分テンプレートをeachで呼び出す際に困ったこと。
例)ルート(welcome)画面でピックアップした商品一覧を表示(商品一覧は他の画面でも使うためパーシャル化)
welcome_controller.rb@pickups = Product.all.where.not(pickup_at: nil).joins_i18n(locale_id: @current_lang.id).includes(countries: :country_i18ns).shufflewelcome.html.erb#問題点のみ抜粋 <% @pickups.each do |p| %> <%= render partial: '/shared/products/index_item', locals: { p: p } %> <% end %> -->/shared/products/_index_item.html.erb<div class="col-xs-6 col-md-3"> <section class="product_list"> <%= link_to product_maker_path(id: p.maker_id, product_id: p.id) do %> <div class="photo_box"> <div class="photo_inner photo_contain" style="background-image:url(<%= asset_path p.main_image.thumb %>);" > <% p.campaigns.each do |camp| %> <div class="cam_lavel"><%= camp.i18n.label %></div> <% end %> </div> </div> <div class="pdt_list_ex"> #以下省略eachで回す度にパーシャルファイルを呼んでしまう。
読み込み速度が遅い。。。。。解決策
each内ではなく、collectionを使ってパーシャルをrenderする
welcome.html.erb<%= render partial: '/shared/products/index_item', collection: @pickups, as: :p %>パーシャルを呼び出すのは1回。
- 投稿日:2019-02-26T09:00:15+09:00
Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile. (Gem::LoadError)
bin/rails g ...
する際にsqlite3
のversion依存関係で怒られたので解決法を調べた。解決方法
Gemfile.- gem 'sqlite3' + gem 'sqlite3', '~> 1.3.6'$ bundle install原因
リンク先に詳細が記載されている。
- Railsを始めてsqlite3まわりのエラーで躓いている人たちへ
- 投稿日:2019-02-26T08:41:47+09:00
Error: qt@5.5: unknown version :mountain_lion
- 投稿日:2019-02-26T03:41:38+09:00
gsubで文字列変換
gsub
gsubは、パターンにマッチした文字列を置き換えたい時に使います。
また、パターンには、正規表現が使えます。使い方
文字列.gsub(パターン, 置き換え後の文字)phone = '090-1234-5678' phone.gsub(/-/, '')結果
=> "09012345678"* 2019/2/27追記
コメント欄にて、@scivolaさんにアドバイス頂いたので追記です!
単純な削除はString#deleteの方が良いとのこと。phone = '090-1234-5678' phone.delete('-') => "09012345678"benchmark_driverというgemでベンチマークするとgsubよりdeleteの方が速いらしいです。
ベンチマーク載せた方が良い記事書けそうですね。勉強になりました。gsub!
gsubと同じですが、破壊的メソッドです。
phone = '090-1234-5678' phone.gsub!(/-/, '')実行した時の返り値もgsubと同じ
=> "09012345678"破壊的メソッドなので、レシーバであるphoneの中身も返り値と同じになります。
print phone結果
=> "09012345678"ブロックを使ったgsub
gsub、gsub!は、パターンにマッチした文字列をブロックに渡して加工出来ます。
ブロックを使うとどんな文字列にも加工できそうです。文字列.gsub(パターン) { |ブロック| 処理 }試しにスネークケースで書かれた
hello_world
の先頭のhと_の後ろのwを大文字にしてキャメルケースのHelloWorld
にしてみましょう。snake = 'hello_world' snake.gsub!(/^.|(?<=_)./) { |camel| camel.upcase } # 先頭と_の後ろを大文字にします snake.gsub!(/_/, '') # _を削除します結果
=> "HelloWorld"例題だとgsubじゃなくてscanとかでも出来そうですが、scanはまたの機会にまとめます。
参考
https://www.sejuku.net/blog/14685#gsub-3
https://www.sejuku.net/blog/60033
- 投稿日:2019-02-26T03:38:51+09:00
Rubyでもbcryptはバイナリセーフではない
RubyのBCryptはバイナリセーフなのか
徳丸先生が注意喚起としてあげられていたこちらの記事に関して
“bcryptの72文字制限をSHA-512ハッシュで回避する方式の注意点 | 徳丸浩の日記” https://t.co/AA1yFVd0TH
— 徳丸 浩 (@ockeghem) 2019年2月24日
記事ではPHPの例が上がっていましたが、Rubyではどのような影響があるのかが気になります。BCryptは非常にメジャーなアルゴリズムで、例えば
Devise
やSorcery
などの定番の認証gemを使う場合、デフォルトでBCryptを利用する設定になっています。Railsアプリを開発されている方なら、ほとんどの方が使っているのではないでしょうか。詳しくは徳丸ブログを見ていただくとして、以下ではbcrypt-rubyについて、同じ現象が起こるのかどうか簡単に調査しています。
TL;DR
BCryptを使って バイナリをハッシュ化してはならない
- バイナリコード0x00にヒットすると以降のハッシュ化を止める
- それにより実際より短い部分文字列だけを照合してしまう可能性がある
- とくに冒頭に0x00を含むパスワードを指定すると、簡単に破られてしまう
BCryptに通す前に自前でハッシュ関数を適用したり、BCryptでバイナリをハッシュ化するというような特殊用途で使用している場合は要注意
- セキュリティ面の問題がある
- 未来のバージョンで、今まで通っていたパスワードが通らなくなる可能性がある(後述)
「ユーザーからの入力パスワードをテキストのままdeviseに渡す」というように、一般的な使い方をしている分には問題にならないと思われます。
実験してみる
徳丸ブログに指摘されている内容はいくつかのステップがあります。
それぞれRubyのBCrypt実装においても同様の現象が起こるかどうかを確認していきます。72文字を超えるパスワード文字列は切り捨てられるか
実験のために、BCryptでハッシュ化された長いパスワードを用意します。
(ストレッチ回数などはデフォルト設定です)>> require 'bcrypt' # 72文字を超える長いパスワードを用意する >> password1 = 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_password' >> password1.length => 81 # BCryptオブジェクトを作る >> bcrypted_password1 = BCrypt::Password.create(password1) => "$2a$10$gSsEckxXdY26QjgVuibuZ.J.nAxVpAyMVW6GWjKI9ouOVBEbPiitm"ここで作成したBCryptオブジェクトと候補文字列を
==
で比較すると、「ハッシュ化した時に同じハッシュを得られるかどうか(≒ 元のパスワードが一致するかどうか)」を判定してくれます。1パスワード認証を通す・通さないの判定に使われている仕組みですね。
# 一致する >> crypted_password1 == 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_password' => true # もちろん適当な文字列を入れても一致しない >> crypted_password1 == 'a_totally_different_thing' => falseところが・・
# パスワード後半を変更する >> crypted_password1 == 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_cucumber' => true >> crypted_password1 == 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooongSpeach' => true後半(73文字以降)がハッシュ化の際に捨てられていることがわかります。
もちろん72文字以前を変更した場合は不一致となります。
>> 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong'.length => 72 >> crypted_password1 == 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooonG_password' => false前提となるパスワード文字列の制限については、ドキュメントには明記されていないようですが、RubyのBCryptでも同様に起こることがわかります。
実装はcrypt(3)ライブラリに準拠していると思われるので当然といえば当然ですね。
長い文字列をハッシュ化するときの落とし穴
ここで何らかの事情で「どうしても72文字以上ある長い文字列のハッシュをBCryptで取りてえ」となったとします。
上記の切り捨てを回避するため、「別のアルゴリズムでいったん短いハッシュを作ってから、BCryptに食わせる」という手段が思い浮かびます。ハッシュが長いテキストだと結局また切り落としが発生するのですが、バイナリハッシュを使うと72文字以下におさめることができそうです 2
比較演算子
==
が使えるように、雑に実装してみたのが下記です。require 'digest' class VeryUnsafeDigest def initialize(password) @password = password @digest = create_digest end def create_digest temporary_digest = Digest::SHA512.digest(@password) BCrypt::Password.create(temporary_digest) end def ==(other) @digest == Digest::SHA512.digest(other) end end>> digest1 = VeryUnsafeDigest.new(password1) >> digest1 == "a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_password" => true >> digest1 == "a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_beard" => false今度は長いパスワードをうまく区別してくれました。
一見うまくいったように見えます。が・・
>> digest2 = VeryUnsafeDigest.new('good_looking_password_380') >> digest2 == 'RandomString105' => true >> digest2 == 'good_looking_password_420' => trueなんと、3つの完全に異なるパスワードが すべて衝突してしまいました。
これらの文字列は、バイナリハッシュの段階で冒頭に0x00が含まれてしまっています。
>> Digest::SHA512.digest 'good_looking_password_380' => "\x00\xB6\xE5\xE65\\\x91j\xD3\t\xB4\x98h\x81\xBD\x0E\xB1\\\xC3$\xCB\xD2\x80\x85\xB6\xEC\x18B\xCC\xE4\r\xC3\x93\xA1j*4P\x97\xF0y\x17\xD7P\x11h\x96iq\xFE\x7F\xDB\x10(\n\xDC\xB8\xAF8ESe>\xDD"つまりBCryptは、この文字列の冒頭が終端だと判断して処理を終了してしまい、残りのハッシュ化をしません(空文字列を渡された場合と実質同じ処理になる)。
やはりRuby実装でも、BCryptは0x00を終端文字列と解釈し、残りの文字列を切り捨ててしまうことが確認できます。
今後の動き
この問題についてはbcrypt-rubyリポジトリでもイシューがいくつか出ているようで、「文字列中に0x00が入ってきた場合には例外を吐かせる」というパッチを我らが
たこやきアーロン巨匠が提案してくれています。これによって、何かの間違いでうっかりBCryptにバイナリを食わせる仕様にしてしまったとしても、上で書いたような「後ろが切り捨てられるパスワード」についてはDBに投入される前に阻止できそうです。
みんなでいいねを押して応援しましょう!もし上記のようなオレオレ実装をしていた場合、このパッチが入るバージョン以降は一部のパスワードが通らなくなると思われるので、万が一そんなことがあった場合は注意してください。
セキュリティの分野において、ライブラリをハックしたりアルゴリズムを再発明するのは、かえって脆弱性を産むことになるので避けましょうということですね ?
じゃあどうすりゃいいの
RubyGemsで確認すると、Argon2のリポジトリがあるようです。徳丸先生のブログによると、PHPのArgon2は72文字制限はなくバイナリセーフであるようだ、となっています(Ruby実装については不明)
私には使える・使えないの判断ができるほど実装が理解できていない上に、まだまだ利用実績が少ないため(編集時点で64,607ダウンロード。対するBCryptは51,359,582ダウンロード)紹介するのは控えます。
もっと乱暴に「SHAだけを通す」という方法が考えられますが、今度は短いパスワードがレインボーテーブルで簡単に突破される、という状態になり極めて脆弱になります。(ためしに
SHA rainbow table
でググって出てきたサイトに、SHA512でハッシュ化した短いパスワードを投げてみたところ、ものの数秒で解読されました)。パスワード管理については流出リスクなどを考えて使わない方がよいと思われます。というわけで、素直にBCryptを使い、長いパスワードを末尾部分についてはあきらめて「72文字までの一致を見る」(入力パスワードが72文字を超えた場合はユーザーにエラーを返す)ということで割り切るのが、現時点ではもっとも素直な解決策のように思いました。
追記
2/26 ご指摘を受けてタイポ修正、一部わかりにくい表現を追記・編集しました
- 投稿日:2019-02-26T00:59:04+09:00
RubyのGem::FilePermissionErrorを解決する
Macでgemをインストールしようとするとなにやらパーミッションエラー。
$ gem install bundler ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory.sudoでパーミッションを与えてやる。
$ sudo gem install bundler ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /usr/bin directory.また別の場所でパーミッションエラーが出たので、次は保存場所もlocalにしてやる。
$ sudo gem install -n /usr/local/bin bundler Done installing documentation for bundler after 5 seconds 1 gem installedできた。
$ bundle init Writing new Gemfile to ~
- 投稿日:2019-02-26T00:16:26+09:00
BlackjackのGemを作ったった
経緯
何か簡単にアウトプットできる物ないかな
↓
Gemってどうやってできているんだ?(普段Rubyを主に使っているためGemが身近にあった)
↓
調べたらGemを公開するのに面倒な作業をする必要がなかった(webサイト等だとサーバを借りるなどの必要がある)
↓
・Qiitaの記事を見てブラックジャックを作りたくなる
↓
Gemでブラックジャック作ったらいいんじゃね????そして
やらない理由がない・・・・・・→ 実行
考慮した事
・だらだらと開発していると他のものに興味が移ってしまい開発が止まる危険性があった為、開発スピードを優先させた。
結果、実装時間は数時間で完了したので上手くいったかなと(^ ^)
・とりあえず最低限の機能でもいいからデプロイまでは完了させる(デプロイするとモチベはそこそこ保てるため)
これは正解だった。モチベありすぎてデプロイした後に見つかったバグを全て直してしまった^ ^
実装
まず初期段階でGemがどのように作られ、どのようにデプロイするのかが全くわからなかった為以下の記事を参考にした
後はコードを書いていくのみです! なんとシンプル!!
ロジック等はあえて載せません。(githubは最後にあります。)
ブラックジャックはとても良い勉強題材だと思うので、ぜひ初心者の方は自分で考えて実装してほしいと思います!あと僕の場合は最初にRSpecを入れ忘れてしまったので、
bundle gem プロダクト名 -t ← これtオプションを忘れないようにしてください!まぁRSpecなくてもデプロイはできますけどね(^_^;)
作ってみて感じた事
普段見るGemは規模が大きいものが多い為、Gem作成が難しいと思いがちだけどそんな事はなかった!
むしろ簡単な物だと、サーバの知識やRailsの知識もいらないので、今後Rubyやりたい初心者の方や僕みたいな駆け出しの方々に丁度良い題材なんじゃないかとも感じました!
最後に
やはり、簡単な物でも実際に作ってみると本を読むだけでは得られない知識、経験が得られるなぁと感じました。
これからもアウトプットは積極的にやっていきたいと思います!
コードはhttps://github.com/kSunaga/blackjack_jp に置いてあります。(汚いのであまり見られたくないのは秘密)
- 投稿日:2019-02-26T00:06:41+09:00
Ruby Silverに合格したので、勉強方法をまとめてみた(2019年2月版)
先日Ruby Silverに合格した。
86点という普通ちょい上感あふれる絶妙な点数。
需要の程は分からないが、勉強方法をまとめてみる。Ruby Silverは基本暗記Onlyで、
恐らく他のIT系の資格と比較して範囲も広くはない。
勉強にしてもできる事の幅は広くないので、
恐らく再現性は高いと思う。
(かといって決して楽だという話ではない)とりあえず結論
- 合格記事をさら〜っと眺める
- とにかく色々な問題集を解きまくる
- 各問題集で9割以上正答&理由も説明できるようにする
- 仕上げに先人が残してくれた要注意情報に目を通す
前提
とりあえず学習開始時点での私のスペックを記載する。
以下勉強方法を読む際の参考に。
- Rubyに関しては3ヶ月独学&研修で2週間
- Rubyの経験は無くはない、という程度
- 他言語の経験あり
- 大学時代 C/Javaを履修(?)
- 会社員になってから5年程度、たまーに業務でコードに触れる程度
- IT系の知識は薄く広く
- 基本情報は持っているとかその程度。
学習期間 & 学習時間
- 学習期間:約2週間
- 学習時間:約30時間
学習方法
1. 合格記事をさら〜っと眺める
"Ruby Silver", "勉強法"あたりでググって記事を眺めてみる。
勉強方法やら出題傾向やらが書いてあるので、
なんとなく雰囲気を理解する。例えば、
- String/Array/Hashクラスのインスタンスメソッドに関する出題が多い
- 同じ機能で別の名前を持つメソッドの記憶は必須
- 破壊的メソッドとそうでないメソッドの見分けも重要
- File/Dir/Timeクラスに関しての出題もそこそこ多い
などなど。。。細かく理解する必要はなく、そんな感じなんだなぁと思うくらいでOK。
適当にGoogle検索結果の上から5,6個いい感じのを、流し読みするくらい。2. とにかく問題集を解く
具体的な対策としては、問題集を周回するのが一番手っ取り早い。
ソシャゲのマラソンみたいなノリで、ひたすら走るべし。(ただしRubyのことを何も知らない、プログラミングもほとんどやったことない、
という人は対象外。Rubyの入門本を1冊消化した方が多分良い)1つだけじゃ無くて、色々な問題集を取っ替え引っ替えするのがオススメ。
やってるうちに、共通点やら傾向が自然と理解できる。私が実際に使った問題集は、主に以下の3つ。
公式模擬問題集
https://www.ruby.or.jp/assets/images/ja/certification/examination/exam_prep_jp.pdf
pdfと見せかけてどこぞのgithubリポジトリに飛ぶ。無料。
一番最初にやって7割弱取れた。恐らく一番難易度低め。REx
https://www.ruby.or.jp/ja/certification/examination/rex
受ける度に問題の内容が変わる。8割共通で残りの2割が入れ替わる。
もちろん以前の問題を再び受け直すことも可能。解説も割と書いてある。
スマホからも受けられて便利。もちろん無料。恐らく一番使った。
githubアカウントとの連携が必要。[改訂2版]Ruby技術者認定試験合格教本(Silver/Gold対応)Ruby公式資格教科書
https://www.amazon.co.jp/dp/B0756VF9Y3/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
これだけ普通の書籍。
3600円と結構なお値段がするが、十分その価値はある。
問題集だけでなく、出題範囲に関する一通りの知識も記載されているので、
リファレンスとしても活用できる。時間があるなら、3,4,5章あたりは一通り読んでおいても良い。
ただし問題集を解いた上で、不明な点に絞って読んだ方が遥かに効果がある。各問題集で9割以上正答&理由も説明できるようにする
上記で挙げたいくつかの種類の問題集で、
9割以上解ける&理由も説明できるようになるまで、
知識の完成度を高めていく。その際、理解するための手段は色々な方法を試すと良い。
- 問題集の解答を読む
- Ruby技術者認定試験合格教本の該当箇所を読む
- ネットで関連情報をググる(主にRubyリファレンスなど)
- irbで実際に動作を確かめる
などなど。。。
この辺りを広くやればやるほど、
応用の効く知識が身につき、恐らく本番に強くなる。
(例えば正答に関係ない選択肢でも、分からなければ積極的に調査する、など)逆に問題集の解答を丸暗記しただけだと、
本番で足元をすくわれる可能性が高くなる、と思う。仕上げに先人が残してくれた要注意問題に目を通す
本番の試験は、6,7割は問題集ほぼそのままの内容だったが、
残りは妙にひねった所のある、いやらしい内容だった。試験の前日などに、先人が残したくれた情報に目を通すとその辺りの対策になる。
私が実際に直前に確認して助かったのは以下2つの記事。
そのおかげで多分3問くらいは助かった。Ruby Silver試験前に見直すと幸せになれるメモ
http://tamata78.hatenablog.com/entry/2015/08/07/200454Ruby技術者認定試験Silver version 2.1を受けて…
https://qiita.com/motty93/items/413485469e4ec665c329