Madogiwa Blog

主に技術系の学習メモに使っていきます。

RubyonRails:検索フォームの作り方

Railsで検索フォームを作ったときに少しハマったので、メモ

やりたかったこと

トップページにユーザー検索を行うフォームを設置し、 検索ボタン押下後、検索結果を引き継いだユーザー一覧を表示。

解決方法

<form action="呼び出したいアクションに紐づくurl">を設定すれば良い。 ※呼び出したい

実際のコード

routes.rb

  # /users にusers_controllerのindexアクションが紐付いている。
  resources :users, only: [:index]

application.html.erb

        <!-- actionにcontrollerのアクションに紐づくURL"/users"を設定 -->
        <form class="navbar-form navbar-left" action="/users">
          <div class="input-group">
            <div class="form-group">
              <!-- nameで指定した値"user_name"でcontroller側で取得出来る -->
              <input type="text" name="user_name" class="form-control" placeholder="友達を探す">
            </div>
            <span class="input-group-btn">
              <button type="submit" class="btn btn-default">検索</button>
            </span>
          </div>
        </form>

users_controller.rb

  def index
    # ユーザーの取得処理
    @users = user_search(params['user_name'])
  end

参考にさせて頂いたページ

rails.densan-labs.net qiita.com

RubyonRails:IN句を使った取得条件の指定(自分とフォロー済ユーザーの投稿取得)について

自分の投稿とフォローしているユーザの投稿をトップページで表示したかったけど、RailsでどうやってSQLのIN句のような形の条件指定でハマったので、メモ。

指定したかった条件

下記を含んだ投稿の取得

  • 自分自身の投稿
  • フォローしているユーザーの投稿

対応方法

Model.find(カラム名: 配列)を使えばOK

実際のコード

  def self.followed_users_and_my_topics(current_user)
    # フォロー済ユーザーのidの取得
    followed_ids = current_user.followed_users.map{ |user| user.id }
    # フォロー済ユーザーidと自分自身のユーザーidの含んだ配列の作成
    ids = [current_user.id,followed_ids].flatten
    # 自分自身とフォロー済ユーザーの投稿を取得
    Topic.where(user_id: ids)
  end

参考

qiita.com

Ruby:言語処理100本ノック(解説あり) 第二章:UNIXコマンドの基礎

前置き

勉強のためにrubyで言語処理100本ノックをなるべく答えを見ずにやってみる・・・!の二章目です。

言語処理100本ノック 2015

http://www.cl.ecei.tohoku.ac.jp/nlp100/

言語処理100本ノックは,実践的な課題に取り組みながら,プログラミング,データ分析,研究のスキルを楽しく習得することを目指した問題集です

Gist

rubyで言語処理100本ノック · GitHub ※作業用のGistです、アドバイス等頂けるとありがたいです(´;ω;`)

第2章: UNIXコマンドの基礎

hightemp.txtは,日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,hightemp.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

10. 行数のカウント

Q.行数をカウントせよ.確認にはwcコマンドを用いよ. A.countを使用してファイルの行数を出力した。

p File.open("highttemp.txt", "r").count

11. タブをスペースに置換

Q.タブ1文字につきスペース1文字に置換せよ.確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ. A.bufferにファイルの中身をgsubでタブを半角スペースに置換したものを格納し、それでファイルの上書きを行った。

file = File.open("highttemp.txt", "r", encoding: "Shift_JIS")
buffer = file.read.gsub("\t"," ")
file = File.open("highttemp.txt", "w", encoding: 'Shift_JIS')
file.write(buffer)
file.close

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

Q.各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.確認にはcutコマンドを用いよ. A.ファイルの内容をsplitで配列[高知県,江川崎,41,2013-08-12]に変換し、1列目だけ取得したものをcol1、2列目だけ取得したものをcol2に保存し、それぞれの内容でファイルを作成、上書きするようにした。

file = File.open("highttemp.txt", "r", encoding: "Shift_JIS")
buffer = []
file.each{ |row| buffer << row.encode(Encoding::UTF_8).split(" ") }
col1,col2 = buffer.map{ |r| r[0] }, buffer.map{ |r| r[1] }
File.open("./col1.txt", "w"){ |file| file.puts col1 }
File.open("./col2.txt", "w"){ |file| file.puts col2 }

13. col1.txtとcol2.txtをマージ

Q.12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.確認にはpasteコマンドを用いよ. A.col1.txt,col2.txtを読み込んだ結果を改行区切りで配列にし、buffer[ col1[i], col2[i] ]の形式で格納。その後、bufferの各要素をjoin("\t")でタブで結合し、ファイルに書き込みを行った。

col1 = File.open("./col1.txt", "r").read.split("\n")
col2 = File.open("./col2.txt", "r").read.split("\n")
buffer = []
col1.length.times{ |i| buffer << [ col1[i], col2[i] ] }
buffer = buffer.map{|b| p b.join("\t")}
File.open("./col1_col2.txt", "w"){ |file| file.puts buffer }

14. 先頭からN行を出力

Q.自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ. A.STDIN.gets.to_iで標準入力をIntgerに変換し取得。その後、入力値の数だけファイルの行をfile.gets.encode(Encoding::UTF_8)で出力した。

cnt = STDIN.gets.to_i
file = File.open("highttemp.txt", "r", encoding: "Shift_JIS")
cnt.times{ |i| p file.gets.encode(Encoding::UTF_8) }

15. 末尾のN行を出力

Q.自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ. A.STDIN.gets.to_iで標準入力をIntgerに変換し取得。その後、ファイルの内容をbufferに格納し、入力値の数だけ末尾から出力buffer.popした。

cnt = STDIN.gets.to_i
buffer = []
file = File.open("highttemp.txt", "r", encoding: "Shift_JIS")
file.each{ |row| buffer << row.encode(Encoding::UTF_8) }
cnt.times{ |i| p buffer.pop }

16. ファイルをN分割する

Q.自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ. A.STDIN.gets.to_iで標準入力をIntgerに変換し取得。ファイルの内容をbufferに格納し、Array.shiftを使いたかったので、現在の要素数file_lengthに保存。その後、入力値の回数分ファイルhighttemp_#{i}.txtを作成し、各ファイルにファイルの行数 / 入力値分だけbuffer.shiftで書き込むようにした。また最後のファイルには、余りの行を全て書き込む仕様としました。

cnt = STDIN.gets.to_i
buffer = []
file = File.open("highttemp.txt", "r", encoding: "Shift_JIS")
file.each{ |row| buffer << row.encode(Encoding::UTF_8) }
file_length = buffer.length
cnt.times do |i|
File.open("highttemp_#{i}.txt","w") do |file|
(file_length / cnt).times{ file.puts buffer.shift }
buffer.each{ |b| file.puts b } if i + 1 == cnt
end
end

17. 1列目の文字列の異なり

Q.1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはsort, uniqコマンドを用いよ. A.buffer.map{ |row| row.split(" ")[0] }でファイルの1列目だけの配列を作成し、uniqで要素を一意にした。

buffer = []
file = File.open("highttemp.txt", "r", encoding: "Shift_JIS")
file.each{ |row| buffer << row.encode(Encoding::UTF_8) }
p buffer.map{ |row| row.split(" ")[0] }.uniq

18. 各行を3コラム目の数値の降順にソート

Q.各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい). A.buffer.map{ |row| row.split(" ") }でファイルの内容を配列に格納し、sort{ |a, b| a[2] <=> b[2] }.reverseで3列目(気温)の降順で並べ替えた。

buffer = []
file = File.open("highttemp.txt", "r", encoding: "Shift_JIS")
file.each{ |row| buffer << row.encode(Encoding::UTF_8) }
p buffer.map{ |row| row.split(" ") }.sort{ |a, b| a[2] <=> b[2] }.reverse

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

Q.各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.確認にはcut, uniq, sortコマンドを用いよ. A.buffer.map{ |row| row.split(" ")[0] }で1列名(県名)のみの配列を作成し、 buffer.map{|row| [row,buffer.count(row)] }で県名と出現数を持つを作成、その後uniqで一意とし、sort{ |a, b| a[1] <=> b[1] }.reverseで出現数の降順に並び替えた。

buffer = []
file = File.open("highttemp.txt", "r", encoding: "Shift_JIS")
file.each{ |row| buffer << row.encode(Encoding::UTF_8) }
buffer = buffer.map{ |row| row.split(" ")[0] }
p buffer.map{|row| [row,buffer.count(row)] }.uniq.sort{ |a, b| a[1] <=> b[1] }.reverse

Ruby:言語処理100本ノック(解説あり) 第一章:準備運動

前置き

勉強のためにrubyで言語処理100本ノックをなるべく答えを見ずにやってみる・・・!

言語処理100本ノック 2015

http://www.cl.ecei.tohoku.ac.jp/nlp100/

言語処理100本ノックは,実践的な課題に取り組みながら,プログラミング,データ分析,研究のスキルを楽しく習得することを目指した問題集です

Gist

rubyで言語処理100本ノック · GitHub
※作業用のGistです、アドバイス等頂けるとありがたいです(´;ω;`)

第1章: 準備運動

00. 文字列の逆順

Q.文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.
A.これは単純にstring.reverseを使用しました。

p "stressed".reverse

01. 「パタトクカシーー」

Q.パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ. A.これは、文字列の奇数番目を取り出すようにしました。

str = "パタトクカシーー"
str.length.times{ |n| p str[n] if n.even? }

02. 「パトカー」+「タクシー」=「パタトクカシーー」

Q.「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.
A.配列にそれぞれを入れて、各要素のn文字目の値を結合した値を設定する処理を文字数分行いました。

ans = ""
words = %W(パトカー タクシー)
4.times{ |n| ans << (words[0][n] + words[1][n]) }
p ans

03. 円周率

Q.“Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.
A.gsubで”,“、”.“を空文字に置換して、splitを使って” “で配列に分割したあと、mapでそれぞれの要素の文字数を持つ配列を返すようにしました。

str = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
p str.gsub(/,|\./,'').split(" ").map{ |s| s.length }

04. 元素記号

Q.“Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,それ以外の単語は先頭に2文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.
A.単語に分割後、指定したインデックスの要素の1文字目だけを格納した配列とそれ以外のインデックスの要素の1~2文字目を格納した配列を用意して、それらを結合後に昇順に並び替えて、ハッシュに変換した。なんだかイマイチな気がする。。。

str = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
words = str.gsub(/,|\./,"").split( )
target = [0, 4, 5, 6, 7, 8, 14, 15, 18]
words1 = words.map{ |n| [words.index(n),n[0]]    if     target.include?(words.index(n)) }.compact
words2 = words.map{ |n| [words.index(n),n[0..1]] unless target.include?(words.index(n)) }.compact
p words1.concat(words2).sort.to_h

05. n-gram

Q.与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.
A. 与えられた引数seqが配列だった場合は、joinし文字列に変更する。charsで一文字ずつに分割後["a","b","c"]、それに対してeach_con(2)で、重複ありで2文字に区切る。[["a","b"],["b","c"]2文字に区切った要素をjoinで結合し、返却した。["ab","bc"]

def to_ngram(seq)
    seq = seq.join if seq.instance_of?(Array)
    seq.chars.each_cons(2).map{|chars| chars.join }
end

p to_ngram("I am an NLPer")
p to_ngram(%W(I am an NLPer))

06. 集合

Q.“paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.
A. bi-gramを出力するロジックは前回のものを流用。それぞれの結果をX・Yに格納し、&で積集合、|で和集合、include?'se'が含まれるか判定した。

def to_ngram(seq)
    seq = seq.join if seq.instance_of?(Array)
    seq.chars.each_cons(2).map{|chars| chars.join }
end

X = to_ngram "paragraph"
Y = to_ngram "paraparaparadise"

# 積集合
p X & Y
# 和集合
p X | Y
# seが含まれるbi-gramの存在チェック
p "X:#{X.include?('se')}、Y:#{Y.include?('se')}"

07. テンプレートによる文生成

Q.引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y=“気温”, z=22.4として,実行結果を確認せよ.
A.引数x,y,zを式展開#{}を使用して文字列に埋め込む関数templateを実装し、指定されたテンプレートに引数を埋め込んだ文字列を返すようにした。

def template(x,y,z)
    return "#{x}時の#{y}#{z}"
end
p template(12,"気温",22.4)

08. 暗号文

Q.与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.

  • 英小文字ならば(219 - 文字コード)の文字に置換
  • その他の文字はそのまま出力

この関数を用い,英語のメッセージを暗号化・復号化せよ.

A.暗号は、文字列をeach_charで1文字ずつ抽出し、英字(a-z)の場合だけ、codepoints文字コードに変換し219から引いた値を格納。復号の方は、ゴリ押し感がひどすぎて辛すぎる。勉強して書き直せるようになりたい。。。

def cipher(str,option)
    ans = []
    case option
    when "encode" 
        str.each_char{ |c| c = 219 - c.codepoints.join.to_i if /[a-z]/.match(c); ans << c }
    when "decode" 
        dec_targets = []
        i = 1
        
        str.each_char do |c|
            if /[\d]/.match(c)
                dec_targets << c
                c = i % 3 == 0 ? "@" : "" ; i += 1
            end
            ans << c if !c.empty?
        end
        
        dec_targets = dec_targets.join.scan(/.{1,3}/).map{ |n| (219 - n.to_i) }.pack("U*").split("")
        
        i,j = 0,0
        ans.each do |c|
            if c == "@"
                ans[i] = dec_targets[j]; j+= 1
            end
            i += 1
        end
    end
    ans.join
end

p cipher("ABCabcABCabc","encode")
p cipher("ABC122121120ABC122121120","decode")

ちょっとリファクタリングしました…φ(..)

def cipher(str,option)
    ans = []
    case option
    when "encode" 
        str.each_char{ |c| c = 219 - c.codepoints.join.to_i if /[a-z]/.match(c); ans << c }
    when "decode" 
        dec_targets = []
        str.each_char { |c| dec_targets << c if /[\d]/.match(c) }
        dec_targets = dec_targets.join.scan(/.{1,3}/).map{ |n| (219 - n.to_i) }.pack("U*").split("")
        ans = str.gsub(/\d{3}/,"@").split("")
        ans.length.times { |i| ans[i] = dec_targets.shift if ans[i] == "@" }
    end
    ans.join
end

p cipher("ABCabcABCabc","encode")
p cipher("ABC122121120ABC122121120","decode")

09.Typoglycemia

Q.スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば"I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind .“)を与え,その実行結果を確認せよ.
A.まず文字列をsplit(" ")でスペース区切りの配列に変換し、eachで各文字列を抽出。抽出した文字列の最初と最後の文字以外をc[1..(c.length - 2)].shuffle.joinでシャッフルし、最初の文字c[0]c[c.length - 1]joinで結合して格納し、最後にjoin(" ")でスペースを入れて結合して返却した。

str = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
ans = []
str.split(" ").map{ |c| c.split("") }.each do |c|
    c = c[0],c[1..(c.length - 2)].shuffle.join,c[c.length - 1] if c.length > 4
    ans << c.join
end
p ans.join(" ")

Ruby:3つのオプションがある外部コマンドを実装するアレ

前置き

Twwiterでちょっと話題になった3つのオプションを持つコマンドを実装するあれを、
自戒も込めてRubyで実装してみました・・・!

実装

解説ぽいもの

もっと良い書き方もあるかもしれませんが、 単純に文字列を付与していく形だとスペースが不正に入ってしまう気がしたので、 要素として配列に付与して最後にスペースで結合する形式にしてみました。

ズンドコやPPAPのようにシンプルだけど、意外と実用的で勉強になるテーマだと思いました!φ(..)

Ruby:今更ながらズンドコキヨシ

今更ながらズンドコキヨシをrubyで書いてみた。

ズンドコキヨシとは?

一昔前に流行ったやつです。

意外とこだわろうとすると、 色々な書き方が出来て勉強になる気がします。・ω・!

書いてみたソースコード

ソースコード(Gist)

ansの初期化のところがいまいちな気がする。。。

実行結果はこちら(paiza.io)

参考

以下のページに色々な言語で書かれたズンドコキヨシをまとめてくださってます! qiita.com

RubyonRails:githubからcloneして実行するまでの手順メモ

githubに上がっているRubyonRailsアプリを自分のローカル環境で実行するまでの手順メモ

前提

rubyがインストール済であること ・gitがインストール済であること ・PostgreSQL等のDBMSがインストール済であること

手順

リポジトリのクローン

下記コマンドを実行し、ローカル環境にgithubリポジトリをclone

$ git clone リポジトリのurl

結果の確認$ git remote -v

gemのインストール

下記コマンドを実行し、必要なgemをインストールする。

$ bundle install

テーブル定義の反映

下記コマンドを実行し、データベースを作成し、テーブル定義変更を反映する。

$ rake db:create
$ rake db:migrate

※下記のようなエラーが発生した場合は、コマンドを$ bundle exec rake db:migrateまたは、$ bin/rake db:migrateに変更して実行

rake db:migrate
rake aborted!
Gem::LoadError: You have already activated rake 10.4.2, but your Gemfile requires rake 10.5.0. Prepending `bundle exec` to your command may solve this.

起動

rails sを実行する。

だいぶ簡単ですね、RubyonRails恐るべし