default_scopeは悪だとRuboCopに怒られたので理由を探る(Rails)

【結論】

・あるモデル全体にスコープを適用したい時、default_scopeを利用するとデフォルトで設定が出来る。

・但し、既定のスコープを上書きすることはできず、モデルの初期化に影響することから、使用を推奨しない意見がある。

・反対意見も存在する為、仕様をきちんと理解した上での使用あれば、有用だと考えられる

【目次】

【本題】

default_scopeについて

Railsにはデフォルトでスコープを設定できるdefault_scopeというものが存在します。

http://railsdoc.com/references/default_scope

めちゃくちゃ便利そうだったので、試しに使ってみると、RuboCopに怒られました。

理由を確認すると、以下のRails Best Practicesの内容に行き着きました。

Rails Best Practices - default_scope is evil

今回は、この内容をまとめます。

1:既定のスコープを上書きすることはできない

まず具体例を示す為に、Postモデルでdefault_scopeを定義した以下のコードを用意します。

class Post
  default_scope where(published: true).order("created_at desc")
end

こちらは、デフォルトでは投稿順はcreated_atです。

> Post.limit(10)
  Post Load (3.3ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`published` = 1 ORDER BY created_at desc LIMIT 10

投稿の順序をcreated_atではなくupdated_atで表示するには、次のようにします。

> Post.order("updated_at desc").limit(10)
  Post Load (17.3ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`published` = 1 ORDER BY created_at desc, updated_at desc LIMIT 10

しかし、これだとcreated_atとupdated_atの両方で順番になっており、default_scopeはオーバーライドされません。

デフォルトスコープを明示的に無効にするにはunscopedを使用する必要があります。

> Post.unscoped.order("updated_at desc").limit(10)
  Post Load (1.9ms)  SELECT `posts`.* FROM `posts` ORDER BY updated_at desc LIMIT 10

この様なことから、モデルにdefault_scopeがあることを覚えておく必要があり、default_scopeをオーバーライドしたい場合にはunscopedを追加する必要があります。

2:default_scopeはモデルの初期化に影響する

default_scopeはクエリにのみ影響すると思いがちですが、初期化にも影響を及ぼします。

> Post.new
=> #<Post id: nil, title: nil, created_at: nil, updated_at: nil, user_id: nil, published: true>

上記の通り、Post.newすると、デフォルトでpublished: trueになってしまいます。

その為、一旦定義されたdefault_scopeを使わないようにするのが非常に困難です。

以上2点の理由から、default_scopeは使用せず、スコープとして定義し、明示的にそのスコープを呼び出すことを推奨していました。

反対意見も存在する

とはいえ、反対意見も複数存在します。なので、リスクを理解した上で利用する分には有用なのかもしれません。

Railsのdefault_scopeは悪ではない。 - Qiita

[Rails] default_scopeを使ったせいで泣きを見たクレイジーな困難たちを紹介するぜ! - Rista Tech Blog

Railsのdefault_scopeをどうしても使いたい時 - Qiita

参考情報

Rails Best Practices - default_scope is evil

Railsのdefault_scopeは使うな、絶対(翻訳)|TechRacho by BPS株式会社

【AtCoder:9回目】AtCoder Beginner Contest 132の振り返り(Ruby)

【目次】

【本題】

今回は 6/29(土)に開催されたAtCoder Beginner Contest 132の振り返りを行います。

AtCoder Beginner Contest 132 - AtCoder

今回は3問まで解けました。

めちゃくちゃトントン拍子で進んだので、もしや初の4問目も回答出来るのでは!?っと思ったのですが、全く検討が付かず・・・

ここ辺りになってくると、プログラミングというより、完全に数学の世界ですね・・・

レーティングは微増です。

A - Fifty-Fifty

問題文 長さ 4 の英大文字からなる文字列 S が与えられます。 S がちょうど 2 種類の文字からなり、かつ現れる各文字はちょうど 2 回ずつ現れているかどうかを判定してください。

制約 S の長さは 4 である S は英大文字からなる

これ問題読み違えてました・・・

「かつ現れる各文字はちょうど 2回ずつ現れているか」というところを、「2文字づつ」と勘違いしていました・・・

なので通りはしたものの、めっちゃ無駄なコードになってます・・・実装したコードがこちら。

s = gets.chomp.split("")

s_uniq = s.uniq

if s_uniq.size == 2 && s.count(s_uniq[0]) == 2 && s.count(s_uniq[1]) == 2
  puts 'Yes'
else
  puts 'No'
end

こっちで良かったです・・・

s = gets.chomp.split("")
 
puts s.uniq.size == 2 ? 'Yes' : 'No'

B - Ordinary Number

問題文 { 1 ,

2 ,

. . . ,

n } の順列 p = { p 1 ,

p 2 ,

. . . ,

p n } があります。

以下の条件を満たすような p i ( 1 < i < n ) がいくつあるかを出力してください。

p i − 1 ,

p i ,

p i + 1 の 3 つの数の中で、 p i が 2 番目に小さい。 制約 入力は全て整数である。 3 ≤ n ≤ 20 p は { 1 ,

2 ,

. . . ,

n } の順列である。

pを配列に入れたら、nで作った繰り返し処理の中で、順番に比較を行う方式で行きました。

なお、配列の両端は比較対象が揃わないので除外します。

そして注意点は、piが2番目に来れば良いので、pi−1とpi+1はどちらが大きくても構わないという事です。つまり両方のパターンの条件式が必要です。

そんなこんなで作ったコードがこちら。

n=gets.to_i
p=gets.split.map &:to_i
 
answer = 0
n.times do |i|
  next if i == 0 || i == n - 1
  answer += 1 if (p[i+1] > p[i] && p[i-1] < p[i]) || (p[i+1] < p[i] && p[i-1] > p[i])
end
 
puts answer

ちょい力技感がありますね・・・

そんな中、面白い実装方法を見つけました。

N = gets.to_i
P = gets.split.map &:to_i
p P.each_cons(3).count{|a|
  a[1] == a.sort[1]
}
puts answer

each_consというのは、引数で指定した数の要素を繰り返し配列から取り出して、ブロックを実行するメソッドです。

引数に3を指定して、sortメソッドで順番を変えたとしても、2番目の位置になるかをチェックする方法です。

こちらの方がスマートでかっこいいですね!

C - Divide the Problems

問題文 高橋君は、 N 個の競技プログラミング用の問題をつくりました。 それぞれの問題には 1 から N の番号がついており、問題 i の難易度は整数 d i で表されます(大きいほど難しいです)。

高橋君はある整数 K を決めることで、

難易度が K 以上ならば「 A R C 用の問題」 難易度が K 未満ならば「 A B C 用の問題」 という風に、これらの問題を二種類に分類しようとしています。

「 A R C 用の問題」と「 A B C 用の問題」が同じ数になるような整数 K の選び方は何通りあるでしょうか。

制約 2 ≦ N ≦ 10 5 N は偶数である。 1 ≦ d i ≦ 10 5 入力は全て整数である。

これは中央値(偶数なので二つ)を取得して、それらの差で区切る位置のパターンを求めました。

それがこちらです。

n=gets.to_i
d=gets.split.map &:to_i
 
d_sort = d.sort
p d_sort[d_sort.size/2] - d_sort[d_sort.size/2-1]

d_sort.sizenも同じ値なので、nを使った方が良かったですね。

またd.sort!にすれば、レシーバ自身を上書きしてくれるので、わざわざ変数に入れる必要もありませんでした。

n = gets.to_i
d=gets.split.map &:to_i
 
d.sort!
puts d[n / 2] - d[n / 2 - 1]

UnicodeのBOM(バイトオーダーマーク)について

【結論】

・BOM(バイトオーダーマーク)とは、Unicodeで符号化したテキストの先頭に付与される数バイトのデータのこと

・テキストファイルを開く際に、プログラム側にUnicodeである事を認識させて、最適な設定で開かせることが出来る(ExcelなどでUTF-8のテキストを開こうとした際に、BOMが無いと文字化けする)

・Ruby2.5系では、CSV.generateでBOMが付与出来ないバグが存在する

【目次】

【本題】

BOM(バイトオーダーマーク)について

BOM(バイトオーダーマーク)とは、Unicodeで符号化したテキストの先頭に付与される数バイトのデータのことです。

これにより、テキストファイルを開く際に、プログラム側にUnicodeである事を認識させて、最適な設定で開かせることが出来ます。

例えば、Excelでテキストファイルを開くと、デフォルトではファイルをShift_JISと認識する為、UTF-8のテキストを開こうとした際に、文字化けしてしまいます。

そこでテキストファイルにBOMを付与しておけば、ファイルを開いた際に、UTF-8だと認識して最適な設定に自動で変更してくれるので、文字化けが発生しなくなります。

BOMの有無の確認方法

BOMの有無は、ターミナルで下記のコマンドを実行することで確認できます。

$ file hoge.txt

# BOMなしUTF8
hoge.txt: ASCII text 

 # BOMありUTF8
hoge.txt: UTF-8 Unicode (with BOM) text

なお、BOMの追加・削除は、下記のnkfコマンドで実行できます。

# BOMの追加
$ nkf --overwrite --oc=UTF-8-BOM hoge.txt 

# BOMの削除
$ nkf --overwrite --oc=UTF-8 hoge.txt 

nkfコマンドを利用するには、nkfを導入する必要があります。

$ brew install nkf

Ruby2.5系で発生するBOM関係のバグ

そもそも、今回BOMについて調べた理由なのですが、業務で実装したRubyで出力したCSVファイルをExcelで開くと、文字化けするという事象に遭遇したことがきっかけでした。

Rubyで出力したCSVファイルはデフォルトではUTF-8 なので、BOMを付けて対処しようとしましたが、様々なサイトを参考にしても、BOMが上手くつきませんでした・・・

そうして調べるうちに、以下の記事に辿り着き、Ruby2.5系のバグであることが判明しました。

secret-garden.hatenablog.com

Ruby2.5系では、CSV.generateで第1引数を上手く渡せないバグが存在する様で、下記の様にコードを実装しても、BOMが付かない様でした。

require "csv"

bom = "\uFEFF"
csv = CSV.generate(bom) do |row|
# 以下略

参考情報

https://wa3.i-3-i.info/word11423.html

Unicode - Wikipedia

nkf コマンド | コマンドの使い方(Linux) | hydroculのメモ

BOM(バイトオーダーマーク)とは - 意味をわかりやすく - IT用語辞典 e-Words

【メモ】コマンドでのBOMの追加・削除・確認 - Qiita

初心者でも分かる「文字コードはとりあえずUTF-8にする」という歴史的背景

英語版 Office for Mac で .csv ファイルを開くと文字化けする問題 - fascinated with tofu

Excelで開くと文字化けするCSVファイルへの簡単な対応方法 | プライマリーテキスト

UTF-8のBOM付き・BOM無しの違いと確認方法 | UX MILK

【AtCoder:8回目】AtCoder Beginner Contest 131の振り返り(Ruby)

【目次】

【本題】

振り返り

今回は 6/16(日)に開催されたAtCoder Beginner Contest 131の振り返りを行います。

AtCoder Beginner Contest 131 - AtCoder

今回も2問までです。

3問目が解けそうに思えたのですが、途中のテストで躓いてしまいました・・・

いっそ全部テスト落ちてくれていた方が解決策も探しやすいのですが、公開されていないテストの一部だけで躓いているとなると、何が原因なのか特定が非常に難しいです・・・

レーティングは微増です。

A - Security

問題文 すぬけ君の管理する研究室の扉にはロックがかかっており、解錠にはセキュリティコードを入力する必要があります。

セキュリティコードは 4 桁の数字列です。セキュリティコードが「入力しづらい」とは、同じ数字が連続する箇所が存在することを言います。

現在のセキュリティコード S が与えられます。 S が「入力しづらい」なら Bad を、そうでなければ Good を出力してください。

制約 S は半角数字のみからなる長さ 4 の文字列

文字列を一文字づつ分割して、前後の文字列が同一なのか比較する方法でコードを実装しました。結構力技です・・・

s = gets.chomp.split("")
 
sss = ''
ssss = ''
s.each do |ss|
  ssss = 'Bad' if ss == sss
  sss = ss
end
 
if ssss == 'Bad'
  puts 'Bad'
else
  puts 'Good'
end

案の定もっと良い方法がありました。

puts gets.chomp.squeeze.size == 4 ? "Good" : "Bad"

squeezeメソッドは、文字列中で同じ文字が連続している部分を1つの文字にまとめ、新しい文字列を返します。

新しい文字列が4文字であれば、"Good"で、それ以外は"Bad"を出力するという方法です。

B - Bite Eating

問題文 N 個のリンゴがあります。これらはそれぞれリンゴ 1 、リンゴ 2 、リンゴ 3 、...、リンゴ N と呼ばれており、リンゴ i の「味」は L + i − 1 です。「味」は負になることもありえます。

また、 1 個以上のリンゴを材料として、アップルパイをつくることができます。その「味」は、材料となったリンゴの「味」の総和となります。

あなたはこれらのリンゴを全て材料として、アップルパイをつくる予定でしたが、おなかがすいたので 1 個だけ食べることにしました。勿論、食べてしまったリンゴはアップルパイの材料にはできません。

つくる予定だったアップルパイとできるだけ同じものをつくりたいので、 N 個のリンゴ全てを材料としてできるアップルパイの「味」と、食べていない N − 1 個のリンゴを材料としてできるアップルパイの「味」の差の絶対値ができるだけ小さくなるように、食べるリンゴを選ぶことにしました。

このようにして選ばれたリンゴを食べた時、食べていない N − 1 個のリンゴを材料としてできるアップルパイの「味」を求めてください。

なお、この値は一意に定まることが証明できます。

制約 2 ≦ N ≦ 200 − 100 ≦ L ≦ 100 入力は全て整数である。

とりあえずリンゴ一つ一つの味を配列に格納して、それらの絶対値を個別に比較して、最小の配列の位置を特定し、それと合計を引くという手法で実装しました。

力技だと分かりながらも、他の方法を実践していると時間が経過してしまいから、そのまま力技で実装してしまわざる得ないのが辛いところ・・・

 n,l=gets.split.map &:to_i
 
eat = []
n.times do |i|
  eat << (l + i)
end
 
ee = eat[0].abs
num = 0
eat.each_with_index do |e,i|
  if e.abs < ee
    ee = e.abs
    num = i
  end
end
 
sum = 0
eat.each { |i| sum += i }
 
puts sum - eat[num]

こちらも案の定、もっと良い実装方法がありました。

N, L = gets.split.map &:to_i
a = [*L...L+N]
p a.inject(:+) - a.min_by(&:abs)

味は連番になるので、範囲オブジェクトで定義して、inject(:+)で合計値を出しています。

min_byメソッドは、ブロックの戻り値が最小値となる要素を返すので、(&:abs)を指定する事で、絶対値の最小を抽出できます。

C - Anti-Division

問題文 整数 A , B , C , D が与えられます。 A 以上 B 以下の整数のうち、 C でも D でも割り切れないものの個数を求めてください。

制約 1 ≤ A ≤ B ≤ 10 18 1 ≤ C , D ≤ 10 9 入力はすべて整数である

はじめに下記の全通りの実装を試しましたが、パターンが膨大になるので、これだと処理時間オーバーになってしまいました。

a,b,c,d=gets.split.map &:to_i

range = Range.new(a,b)

num = 0
range.each do |i|
  num +=1 if i % c != 0 && i % d != 0
end

puts num

次に試したのは、下記の実装です。

まず、あるxとyという整数があった場合、xをyで割った時の商が、0~xの数字の中で、yで割り切れる数の個数になるという考え方をベースとしました。

なので、aとbを、それぞれcとdで割って、割り切れる個数を求めます。

なお、cとdで共通して割り切れる数も存在する事が想定される為、cとdの最小公約数でも割って、その個数も求めます。

最終的に、cとdで割った数の合計から、最小公約数で割った数を引けば、cとdのどちらかで割り切れる数の個数が導き出せます。

aとbの両方でこの計算をしたのは、0~bの結果と0~aの結果を引く事で、a~bの結果を抽出できるからです。

なお、今回はa以上という条件なので、a自体も割り切れる数の対象に含まれています。なので、aがc・dのいずれかで丁度割り切れる場合は、a自身の個数が商に含まれないので、+1する必要があります。

その結果、出来上がったのが下記のコードです。

a,b,c,d=gets.split.map &:to_i
a_c = a/c
a_d = a/d
a_cd = a/(c.lcm(d))
b_c = b/c
b_d = b/d
b_cd = b/(c.lcm(d))

sum = (b_c + b_d - b_cd) - (a_c + a_d - a_cd)
sum += 1 if a % c == 0
sum += 1 if a % d == 0
sums = b-a+1
puts sums-sum

しかし、こちらのコードだと一部のテストが通らず、原因不明のまま、時間が経過してしまいました・・・

ただ、よくよく見返すと、sum += 1は一回で良いのに、二度処理を記述していたのが原因だと分かりました。

なので下記の様にコードを修正すると、正常に通りました。勿体無い事をした・・・

a,b,c,d=gets.split.map &:to_i
a_c = a/c
a_d = a/d
a_cd = a/(c.lcm(d))
b_c = b/c
b_d = b/d
b_cd = b/(c.lcm(d))

sum = (b_c + b_d - b_cd) - (a_c + a_d - a_cd)
sum += 1 if a % c == 0 || a % d == 0
sums = b-a+1
puts sums-sum

インフラ触るときに欠かせない「tmux」について

【結論】

・「tmux」とは、端末多重化ソフトウェアと呼ばれるもので、1つのターミナル上で複数のターミナルを立ち上げて同時並行で実行できる

・リモートサーバーでtmuxを実行しておくと、セッションが切れたりインターネット接続が途切れても、すぐに作業復帰が出来る

・そのほかにも、ショートカットキーや表示といった部分をカスタマイズできる

【目次】

【本題】

「tmux」について

「tmux」とは、1つのターミナル上で複数のターミナルを立ち上げて同時並行で実行できる端末多重化ソフトウェアです。

それだけ聞くと、ターミナルのタブ機能で代替え可能な様に感じるかもしれませんが、最大のメリットはインフラを触る時にあります。

リモートサーバーの作業でtmuxを利用するメリット

AWSなどのリモートサーバーにSSHで接続すると、途中でセッションが切れる事があります。

また、何か処理の重いコマンドやスクリプトを実行している最中に、PCがスリープしたり、インターネット接続が切れてしまうと、データの不整合が発生してバグに繋がってしまう危険性もあります。

そういった問題に対処する方法として、tmuxがあります。

リモートサーバーに接続してtmuxを起動すれば、もしローカルとリモートサーバーの接続が途中で切れても、リモートサーバー上にtmuxプロセスが残るので、作業を復帰させる事が出来ます。

これにより、作業が中断される心配は無くなります。

その他にも、フロントエンドとバックエンドのビルドが分かれていたり、マイクロサービスアーキテクチャの様な複数サービスを連携させる場合に、ターミナルから複数プログラムを実行させる使い方も出来ます。

導入方法

では導入方法ですが、まずはHomebrewでtmuxをインストールします。

$ brew install tmux

そして、下記コマンドで実行できます。

$ tmux

デタッチとアタッチ

先ほど、最大のメリットとして取り上げたリモートサーバーとの再接続についてですが、簡単に操作手順をご説明します。

まず、再接続できたという事が分かりやすいように、適当にコマンドを入力しておきます。

続いて「Ctrl + b」→「d」という順にキーを入力して、tmuxとの接続を一旦終了させます。

tmuxは基本的に「Ctrl + b(prefix key)」→「任意のキー」というショートカットキーで操作を行います。

そして再接続するのは、下記のコマンドでアタッチさせます。

$ tmux attach-session

そうすると、先ほど切れたtmuxとの接続を復帰させる事が出来ます。

他にもウィンドウを複数作成する機能など便利な使い方が多いので、ぜひ活用して行きたいものです。

参考情報

tmuxを必要最低限で入門して使う - Qiita

tmux基本まとめ - Qiita

tmuxを使い始めたので基本的な機能の使い方とかを整理してみた - Moved Permanently

インフラエンジニアならtmuxを使いこなしているか!? - Goalist Developers Blog

RubyでMeCabを使った形態素解析をやってみた

【結論】

形態素解析とは、自然言語処理の一種で、文章を意味を持つ最小単位の単語に区切る技術

MeCabとは、オープンソース形態素解析エンジン

・「natto」というgemを利用する事で、RubyでもMeCabを扱う事ができる

【目次】

【本題】

自動タグ付け機能実装の第一歩

業務で開発中のサービスに、自動タグ付け機能を実装してみたいと考えて、その一環で形態素解析を試してみる事にした。

形態素解析について

形態素解析とは、対象の文章を辞書(単語の品詞などの情報)に基づいて、最小単位に分割する技術。

オープンソース形態素解析エンジンは、複数存在しますが(Sudachi、Kuromojiなど)、今回は「MeCab」を利用します。

MeCabについて

MeCabとは、オープンソース形態素解析エンジンの一種で、日本語の形態素解析エンジンの中では比較的よく使用されているそうです。

Rubyでは、nattoというgemを利用する事で、手軽にMeCabを扱う事ができます。

GitHub - buruzaemon/natto: A Tasty Ruby Binding with MeCab

作業の流れ

1:MeCabとnattoを導入

2:'nokogiri'などを利用して、WEBサイトから適当な文章を抽して来る (とりあえず自分の勤め先のコーポレートサイトからスクレイピングしてくる)

3:Nattoを利用して、1の文章を形態素解析する

事前準備

まずはMeCabをHomebrew経由で導入します。

$ brew install mecab
$ brew install mecab-ipadic

次にnattoも導入します。

$ gem install natto

実装したコード

そして色々調べて(後述する参考サイトを参照)実装したコードがこちらです。

require 'natto'
require 'open-uri'
require 'nokogiri'
require 'sanitize'

urls = ['https://relic.co.jp/', 'https://relic.co.jp/service/', 'https://relic.co.jp/company/','https://relic.co.jp/recruit/special/','https://relic.co.jp/services/throttle/','https://relic.co.jp/services/enjine/','https://relic.co.jp/services/booster/','https://relic.co.jp/services/innovator-dna/']

texts = []
urls.each do |url|
  charset = nil
  html = open(url) do |f|
    charset = f.charset
    f.read
  end

  doc = Nokogiri::HTML.parse(html, nil, charset)
  doc1 = Sanitize.clean(doc)
  texts << doc1.delete("\n").delete(" ")
end

text = texts.join(", ")

natto = Natto::MeCab.new
natto.parse(text) do |n|
  puts "#{n.surface}: #{n.feature}"
end

スクレイピングの部分とか、かなりグチャグチャですが、とりあえずお試しで動かすだけなので、気にしない事にしました!

実行結果

全部書ききれないので、一部抜粋

Relic: 名詞,固有名詞,組織,*,*,*,*
採用: 名詞,サ変接続,*,*,*,*,採用,サイヨウ,サイヨー
サイト: 名詞,一般,*,*,*,*,サイト,サイト,サイト
へ: 助詞,格助詞,一般,*,*,*,へ,ヘ,エ
お: 接頭詞,名詞接続,*,*,*,*,お,オ,オ
問い合わせ: 名詞,サ変接続,*,*,*,*,問い合わせ,トイアワセ,トイアワセ
CONTACT: 名詞,一般,*,*,*,*,*
新規: 名詞,一般,*,*,*,*,新規,シンキ,シンキ
事業: 名詞,一般,*,*,*,*,事業,ジギョウ,ジギョー
の: 助詞,連体化,*,*,*,*,の,ノ,ノ
立ち: 名詞,一般,*,*,*,*,立ち,タチ,タチ
上げ: 名詞,一般,*,*,*,*,上げ,アゲ,アゲ
や: 助詞,並立助詞,*,*,*,*,や,ヤ,ヤ
プロダクト: 名詞,一般,*,*,*,*,プロダクト,プロダクト,プロダクト
開発: 名詞,サ変接続,*,*,*,*,開発,カイハツ,カイハツ
に関する: 助詞,格助詞,連語,*,*,*,に関する,ニカンスル,ニカンスル
ご: 接頭詞,名詞接続,*,*,*,*,ご,ゴ,ゴ
相談: 名詞,サ変接続,*,*,*,*,相談,ソウダン,ソーダン
など: 助詞,副助詞,*,*,*,*,など,ナド,ナド
効率: 名詞,一般,*,*,*,*,効率,コウリツ,コーリツ
的: 名詞,接尾,形容動詞語幹,*,*,*,的,テキ,テキ
な: 助動詞,*,*,*,特殊・ダ,体言接続,だ,ナ,ナ
事務: 名詞,一般,*,*,*,*,事務,ジム,ジム
局: 名詞,接尾,一般,*,*,*,局,キョク,キョク
運営: 名詞,サ変接続,*,*,*,*,運営,ウンエイ,ウンエイ
を通じて: 助詞,格助詞,連語,*,*,*,を通じて,ヲツウジテ,ヲツージテ
事業: 名詞,一般,*,*,*,*,事業,ジギョウ,ジギョー
化: 名詞,接尾,サ変接続,*,*,*,化,カ,カ
と: 助詞,並立助詞,*,*,*,*,と,ト,ト
成長: 名詞,サ変接続,*,*,*,*,成長,セイチョウ,セイチョー
まで: 助詞,副助詞,*,*,*,*,まで,マデ,マデ
を: 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
ワン: 名詞,一般,*,*,*,*,ワン,ワン,ワン
ストップ: 名詞,サ変接続,*,*,*,*,ストップ,ストップ,ストップ
クレイ: 名詞,固有名詞,人名,姓,*,*,クレイ,クレイ,クレイ
トン: 名詞,固有名詞,人名,名,*,*,トン,トン,トン
M: 名詞,固有名詞,組織,*,*,*,*
.: 名詞,サ変接続,*,*,*,*,*
クリステンセン: 名詞,一般,*,*,*,*,*
1952: 名詞,数,*,*,*,*,*
年: 名詞,接尾,助数詞,*,*,*,年,ネン,ネン
、: 記号,読点,*,*,*,*,、,、,、
ユタ: 名詞,固有名詞,地域,一般,*,*,ユタ,ユタ,ユタ
州: 名詞,接尾,地域,*,*,*,州,シュウ,シュー
ソルトレイクシティ: 名詞,固有名詞,一般,*,*,*,*
生まれ: 名詞,一般,*,*,*,*,生まれ,ウマレ,ウマレ
。: 記号,句点,*,*,*,*,。,。,。
ブリガムヤング: 名詞,一般,*,*,*,*,*
大学: 名詞,一般,*,*,*,*,大学,ダイガク,ダイガク
経済学部: 名詞,一般,*,*,*,*,経済学部,ケイザイガクブ,ケイザイガクブ
、: 記号,読点,*,*,*,*,、,、,、
オックスフォード大学: 名詞,固有名詞,組織,*,*,*,オックスフォード大学,オックスフォードダイガク,オックスフォードダイガク
経済学部: 名詞,一般,*,*,*,*,経済学部,ケイザイガクブ,ケイザイガクブ
を: 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
卒業: 名詞,サ変接続,*,*,*,*,卒業,ソツギョウ,ソツギョー

なんの手も加えていないコードにしては、しっかり分割されたのでは無いでしょうか!

うちの会社名(Relic)を、ちゃんと固有名詞で組織名だと識別していたのは驚きました。

こんなコピペコードでも、これくらいならサクッと作れてしまうのが恐ろしい・・・

次回以降の展望

とはいえ、やっぱり一部のワードは、きちんと単語が区切れていなかったりと、このままだと何の役にも立たないので、下記のような事も実践して見ようと考えています。

・辞書を登録して単語識別の精度を高める

・TF-IDFを利用して特徴語を抽出する

参考情報

GitHub - buruzaemon/natto: A Tasty Ruby Binding with MeCab

Mac OSにmecabをインストールしてnattoを利用してrubyからmecabを叩いてみる - woshidan's blog

RubyでMeCabを使う - Qiita

MacでRubyを使ってMeCabを利用する準備 - 別館 子子子子子子(ねこのここねこ)はてブロ部

mecabをRubyから使おうとしたらエラーが・・・・ - /var/www/yatta47.log

「リーダブルコード」の読書メモ2(第二部:ループとロジックの単純化、第三部:コードの再構成)

【結論】

・条件やループなどの制御フローは出来るだけ「自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く

・巨大な式を分割する為に「説明変数」を利用する

・標準ライブラリの関数などを定期的に読んで、ライブラリで可能な事を理解しておく

【目次】

【本題】

第7章:制御フローを読みやすくする

条件式の引数の並び順

・左側に調査対象(変化する)の式、右側に比較対象(変化しない)の式を配置する

#悪い例
if length == 10

#良い例
if 10 == length

if/elseブロックの並び順

・条件は否定形より肯定形を使う

・単純な条件や、関心を引く条件(目立つ条件)を先に書く

三項演算子は二者択一の条件でのみ用いる

ガード節を使って関数から早く返す

処理の対象外とする条件を、関数やループの先頭に集めて return や continue/break で抜ける方法。 ネストを減らし正常系の処理がわかりやすくなるメリットがある

ネストを浅くする

ネストが深くなると、読み手は条件を覚えておく必要があるので、負担が増える コードを変更する時にはコードを新鮮な目で見る。一歩下がって全体を見る。

第8章:巨大な式を分割する

式を表す変数「説明変数」を導入する事で、コードを文書化することが可能。コードの主要な概念も読み手が認識しやすくなる

第9章:変数の読みやすさ

役に立たない一時変数や中間結果は削除する

変数のスコープを縮める(グローバル変数は多用しない)

定数を使う(変数の変更箇所を出来るだけ少なくする)

第10章:無関係の下位問題を抽出する

・プロジェクト固有のコードから汎用コードをを分離する

エンジニアリングとは、大きな問題を小さな問題に分割して、それぞれの解決策を組み立てることに他ならない。

関数やコードブロックをみて「このコードの高レベルの目標は何か?」と自問する

コード各行に対して「高レベルの目標に直接的に効果があるのか?あるいは、無関係の下位問題を解決しているのか?」自問する

無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関するにする

第11章:一度に1つのことを

コードは1つずづタスクを行うようにしなければいけない

読みにくいコードがあれば、タスクを全て列挙して、分割できるものがないか探る。

第12章:コードに思いを込める

コードの動作を簡単な言葉で同僚にも分かるように説明する

その説明の中で使っているキーワードやフレーズに注目する

その説明に合わせてコードを書く

第13章:短いコードを書く

・不要/過剰な機能はプロダクトから削除する

・最も簡単に問題を解決できるような方法を考える

・標準ライブラリの関数などを定期的に読んで、ライブラリで可能な事を理解しておく

但しやり過ぎると、逆に可読性が悪くなる原因になり得るので注意が必要。

「リーダブルコード」の読書メモ1(第一部:表面上の改善)

【結論】

・名前を見ただけで情報が読み取れるような命名を行う

・一貫性と意味のあるスタイルでコードを整形する

・コードから直ぐ読み取れる事はコメントしない。コードを理解するのに役立つものにコメントする

【目次】

【本題】

リーダブルコードとは

副題の通り、より良いコードを書くためのシンプルで実践的なテクニックが詰まった本

第1章:理解しやすいコード

「良いコードとは、他の人が最短時間で理解できるように書かれたものである」

第2章:名前に情報を詰め込む

変数・関数・メソッド・クラスなどに名前をつける際は、それらを表す情報を詰め込む (そのメソッドが何を返すのか?その変数がどういった値を持っているのか?)

その為のノウハウは下記の通り。

・明確な単語を選ぶ

解釈の範囲が広い単語より、限定的な意味合いの単語の方が、メソッドの動きなどが伝わりやすい

例) ・get → download、fetch

・size → height、NumNodes、MemoryBytes

・stop → kill、resume、pause

・find → search、extract、locate、recover

・start → launch、create、begin、open

・make → setup、build、generate、compose、add、new

・汎用的な名前は避ける

なんの意味も含まない名前ではなく、データの値や目的を表した名前を付ける(明確な意図がある場合は別)

例)

hoge、tmp、retail、foo、hoge、it

・抽象的な名前よりも具体的な名前を使う

変数やメソッドの動作をそのまま表すような名前を付ける。

例)

・ServerCanStart→CanListenOnPort

・DisallowEvilConstructors→DisallowCopyAndAssign

・—run_locally→—extra_logging

・接尾辞や接頭辞を使って情報を追加する

時間やバイト数などの計測できるものは変数名に単位を入れると良い

例)

delay→delay_secs

size→size_mb

limit→max_kbps

angle→degrees_cw

変数の意味を勘違いすると深刻な被害が出る箇所は、危険や注意を喚起する情報も追加した方が良い

例)

password(処理前に暗号化する必要があるプレインテキスト)→plaintext_password

comment(表示前にエスケープする必要があるコメント)→unescaped_comment

html(文字コードUTF-8に変えている)→html_utf8

data(URLエンコードしているデータである)→data_urlenc

・名前の長さを決める

スコープが小さければ、近くにその変数・メソッドの情報が集約されているので、名前は短くても良い (ブロック引数のiなどが良い例)

頭文字を使った省略は、新規でアサインされた人でも分かるレベルの内容に留める (stringをstrと略すような一般的なレベルに抑える)

単語の不要な部分は切り捨てる

例)

ConvertToString→ToString

DoServeLoop→ServeLoop

・名前のフォーマットで情報を伝える

Rubyなら、クラス・モジュール名はキャメルケース、定数は全て大文字の_区切りなど

第3章:誤解されない名前

・filter(選択するのか?除外するのか?分からない)→select、exclude

・clip(文字を削除するのか?切り詰めるのか?)→remove truncate

・限界値を明確にするには、名前の前にmaxやminを付ける

・範囲を指定する時は、firstとlast、minとmax、beginとendの様に、包括関係が分かる単語を用いる

・ブール値(tureかfalse)の場合、頭にis・has・can・shouldなどを付けるとわかりやすい。

・単語に対する期待にも注意する(get、sizeなどは軽量な処理だと期待される)

第4章:美しさ

一貫性のあるレイアウトを使う(チーム内のコード規約に従う)

似ているコードは似ている様に見せる、関連するコードをまとめてブロックにする

・一貫性のある簡潔な改行を行う(同じルールでコードを折り返す)

・メソッドを使った整列(メソッドにまとめて、コードを折り返さずに済む様にする)

・縦の線は真っ直ぐにする(同じメソッドが並ぶ場合、引数の位置が揃う様に空白を入れる)

・一貫性と意味のある並び順(フロントに表示される並び、重要度、アルファベット順)

・宣言をブロックにまとめる、コードを段落に分割する(グループごとにコメントで区切る

第5章:コメントすべき事を知る

・コードからすぐにわかることはコメントに書かない(ひどい名前はコメントをつけずに名前を変える)

・監督コメンタリーを入れる(他の実装方法との比較や、コードが汚い理由など)

・コードの欠陥にコメントを付ける(TODO:)

・定数にコメントを付ける(定数である理由や背景)

・質問されそうな事をあらかじめ記述する

・ハマりそうな罠を告知する

・全体像のコメント(データの流れ、クラスの連携など)

・細部に捕らわれない様に要約コメント(塊を関数に分割できるのであれば、そちらがベター)

・コードを理解するのに役立つものなら何でもいいから書こう(WHYのみ書くだと、人によって受け取り方が異なる場合がある)

・よく分からない引数には、名前付き引数を使う(非対応の言語ではインラインコメント)

第6章:コメントは簡潔で正確に

・コメントは簡潔に保つ(情報密度の高い言葉を使う)

・あいまいな代名詞は避ける(「それ」「これ」など)

・関数の動作を正確に記述する(例:行数を数える→改行文字'\n'を数える)

・概念的な説明が難しければ、入出力の実例を示す

・コードの意図を書く(listを逆順にイテレートする→値段の高い順に表示する)

論理削除について(論理削除が実装できるgem「paranoia」の使用方法)

【結論】

・「論理削除」とは、データベースから対象データを削除せずに、フラグで削除された事を表現する削除方法。データベースから対象データを削除する削除方法は、「物理削除」と呼ばれる。

・データが保持されるので、簡単に復元可能なことがメリット。但しデータ量が増えて、検索速度が落ちる場合もある。

・「paranoia」とは、Railsのgemの一種で、論理削除が簡単に実装できる

【目次】

【本題】

論理削除について

論理削除とは、データベースから対象データを削除せずに、フラグを使って削除された事を表現する削除方法です。

論理削除を行うと、対象データは検索してもヒットしませんが、データベースにデータは保持されたままになります。

なお、データベースから対象データを削除する削除方法は、「物理削除」と呼ばれます。

論理削除のメリット・デメリット

論理削除は実際のデータを保持したまま、見た目上は削除された様に装うことができる事から、下記の様なメリットがあります。

・誤って削除しても、直ぐにデータを復元することができる

・データが消える事によって、データの整合性が取れなくなる事象を防ぐ

しかし、論理削除にもデメリットはあります。

・フラグの付け忘れなどによって、バグの温床となり得る

・データ量が増える為、検索速度が落ちる

それぞれのメリット・デメリットを理解した上で、必要な場面で利用することが重要です。

gem「paranoia」について

Railsの場合、「paranoia」というgemを用いることで、簡単に論理削除を実装することが可能です。

実装の流れですが、まずはgemをインストールします。

gem ‘paranoia'

次に、論理削除したいモデルに deleted_atカラムとindexを追加します。

rails g migration AddDeletedAtToUsers deleted_at:datetime:index
class AddDeletedAtToUsers < ActiveRecord::Migration[5.1]
  change_table :users, bulk: true do |t|
    t.column :deleted_at, :datetime
    t.index :deleted_at
  end
end

そして、対象モデルに acts_as_paranoid を追記します。

class User < ApplicationRecord
    acts_as_paranoid
end

これで実装は完了です。

あと、 destroy を実行すると、deleted_atにタイムスタンプが入り、物理削除ではなく論理削除になります。

user.destroy

また、元に戻す場合は、restoreを実行します。

user.restore

なお、アソシエーションを組んでいる子モデルにdependent: :destroyを設定していて、子モデルも論理削除を行いたい場合は、子モデルにもカラム追加とモデルにacts_as_paranoidの記述を行う必要があります。

参考情報

GitHub - rubysherpas/paranoia: acts_as_paranoid for Rails 5, 6 and 7

論理削除と物理削除とは - Qiita

すぐに使える!DBデータの論理削除(1/4) そもそも「論理削除」って何? - DBひとりでできるもん

https://remonote.jp/rails-gem-paranoia

Railsで論理削除を実装したい時は「paranoia」Gemを使おう! - とんてき

【Ruby on Rails】deleted_at を使って論理削除をしよう - きゃまなかのブログ

【AtCoder:7回目】AtCoder Beginner Contest 130の振り返り(Ruby)

【目次】

【本題】

振り返り

今回は 6/16(日)に開催されたAtCoder Beginner Contest 130の振り返りを行います。

今回は1問しか回答できませんでした・・・

やればやるほど成績落ちているのは気がする・・・

業務のキャッチアップ優先なので、アルゴリズム系の勉強する時間を確保できないのが口惜しい・・・

A - Rounding

問題文 X , A は 0 以上 9 以下の整数です。

X が A 未満の時 0 、 A 以上の時 10 を出力してください。

制約 0 ≤ X , A ≤ 9 入力は全て整数である

「X がA未満の時 0、A以上の時 10を出力」という特に捻りは無い内容です。

提出したコードはこちらです。

x,a=gets.split.map &:to_i
puts x>=a ? 10 : 0

B - Bounding

問題文 数直線上を N + 1 回跳ねるボールがあり、 1 回目は 座標 D 1

0 , i 回目は 座標 D i

D i − 1 + L i − 1 ( 2 ≤ i ≤ N + 1 ) で跳ねます。

数直線の座標が X 以下の領域でボールが跳ねる回数は何回でしょうか。

制約 1 ≤ N ≤ 100 1 ≤ L i ≤ 100 1 ≤ X ≤ 10000 入力は全て整数である

提出したコードはこちらです。

N, X = gets.split.map(&:to_i)
L = gets.split.map(&:to_i)

now = 0
bound = 1
L.each do |l|
  now += l
  break if now > X
  bound += 1
end
 
p bound

timesではなくeachで回すと上手く行きました。

N, X = gets.split.map(&:to_i)
L = gets.split.map(&:to_i)

now = 0
bound = 1
L.each do |l|
  now += l
  break if now > X
  bound += 1
end
 
p bound