定食屋おろポン

おろしポン酢と青ネギはかけ放題です

Rubyのnet/httpで重複するキーをもつパラメータをPostする

net/http を使うと、単純にPostするのは至って簡単だ。

Net::HTTP.post_formメソッドを叩くだけで事足りる。

library net/http

require 'net/http'
require 'uri'

#例1: POSTするだけ
res = Net::HTTP.post_form(URI.parse('http://www.example.com/search'),
                          {'q'=>'ruby', 'max'=>'50'})
puts res.body

#例2: 認証付きで POST する
res = Net::HTTP.post_form(URI.parse('http://jack:pass@www.example.com/todo.cgi'),
                          {'from'=>'2005-01-01', 'to'=>'2005-03-31'})
puts res.body

#例3: より細かく制御する
url = URI.parse('http://www.example.com/todo.cgi')
req = Net::HTTP::Post.new(url.path)
req.basic_auth 'jack', 'pass'
req.set_form_data({'from'=>'2005-01-01', 'to'=>'2005-03-31'}, ';')
res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
case res
when Net::HTTPSuccess, Net::HTTPRedirection
  # OK
else
  res.value
end

キーが重複するパラメータ問題

フォームで配列を送信するとしよう。

例えば、

somekey[] => "hoge",
somekey[] => "fuga",
somekey[] => "piyo",

といったパラメータだ。

post_formメソッドは、uriparamsの引数を取る。第一引数のuriURIで、第二引数のparamsHashだ。 つまり、こういう呼び出しになるだろう。

Net::HTTP.post_form(URI.parse('http://example.com'), {
  "somekey[]" => "hoge",
  "somekey[]" => "fuga",
  "somekey[]" => "piyo",
})

ここで問題が浮上する。Hashは重複するキーを持つことができない。上記の呼び出しは、このように変換されてしまう。

Net::HTTP.post_form(URI.parse('http://example.com'), {
  "somekey[]" => "piyo",
})

解決法

HTTPリクエストbodyにPostしたいデータを直接セットしてやることで回避した。 その際は、自分でPostするパラメータを文字列化し、URLエンコードする必要がある。具体的には、URI.encode_www_formを使う。

3行目を参照。

url = URI.parse('http://www.example.com/todo.cgi')
req = Net::HTTP::Post.new(url.path)
req.body = URI.encode_www_form [
  ["somekey[]", "hoge"], 
  ["somekey[]", "fuga"], 
  ["somekey[]", "piyo"]
]
res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
case res
when Net::HTTPSuccess, Net::HTTPRedirection
  # OK
else
  res.value
end

キモは、URI.encode_www_formでURLエンコードする際に、配列の配列を渡すことだ。Hashじゃないからキーの重複も問題ない。

http://docs.ruby-lang.org/ja/2.1.0/class/URI.html#S_ENCODE_WWW_FORM

URI.encode_www_formメソッドの返り値はこうなる。

"somekey%5B%5D=hoge&somekey%5B%5D=fuga&somekey%5B%5D=piyo"

tips

Net::HTTP.post_formはHashを受け取ると書いたが、それはあくまでドキュメント上のはなしだ。 ソースを追えばわかるが、内部ではURI.encode_www_formを呼んでいるだけである。 (post_formの中でNet::HTTPHeader#set_form_dataが呼ばれ、その中でURI.encode_www_formしている。)

だから、post_formメソッドに(Hashではなく)配列の配列を渡しても動く。

Net::HTTP.post_form(URI.parse('http://example.com'), [
  ["somekey[]", "hoge"], 
  ["somekey[]", "fuga"], 
  ["somekey[]", "piyo"]
])

不安なら、set_debug_output($stderr)を使ってリクエストを覗いてみればいい。

ただundocumentedなので、こういう風に使っていいのかアヤシイ。

バーコードジェネレータをゆるーくRubyで

「IOモナド難しい..Rubyで書いたらゆるふわ楽勝なのに..」

とか前に書いてたので、ほぼ同様のコードをゆるーく書いてみました。

gist8574615

# =>
#MJDRYCAD
#CKLKCQSW
#CHJAQKSZ
#YWASAYMI
#UHVRVEWW
#以下略

重複する可能性を考慮して、必要な数の2倍のコードを生成し、シャッフルしてから必要な数だけ取り出す謎の処理をしています。

チェックサムする前に重複を削除して、重複した文をまた生成する」を繰り返すほうが速いけど10万件くらいならまあこれでよいでしょう。

ドットインストール Ruby on Railsの基礎 #36 でAjaxによるPost削除処理が動かない

メモ

$('a[data-method="delete"]').live('ajax:success', function(e, data, status, xhr) {
  $('#post_'+data.post.id).fadeOut("slow");
});

data-method="delete"って属性を持つaタグに、イベントハンドラを設定するコード。

JSデバッガコンソールに'undefined is not a function'とのエラーが表示され、正しく動作しなかった。
jQuery v1.9.0では、jQueryオブジェクトのliveメソッドが非推奨を経て削除されており、定義されていない(undefined)ため。
live関数の代わりに追加されたon関数を使う。

書き直すと以下のようになる。

$(document).on('ajax:success', 'a[data-method="delete"]', function(e, data, status, xhr) {
  $('#post_'+data.post.id).fadeOut("slow");
});

まあ3行ならコピペするけどね。

こんな呟きが流れてきたので、

なんて呟いてみたけど、出力するだけなら普通に書けるね。

NSArray* week;
week = [NSArray arrayWithObjects:@"月", @"火", @"水", @"木", @"金", @"土", @"日", nil];
for (int i = 5; i <= 7; i++) {
    NSLog(@"6月%d日(%@) 14:00以降", i, [week objectAtIndex:(i+3)%7]);
}

ついでにRuby

(5..7).each {|i| puts "6月%d日 (%s) 14:00以降" % [i, %w!月 火 水 木 金 土 日![(i+3)%7]]}

Objective-Cは配列周りの記述が冗長になるのは仕方ない。Objective-Cでも一行で書けるけど、Objective-Cは丁寧に、Rubyはなるたけ簡潔に書くほうが読みやすい気がします。

これは文字列を単純に出力するだけのコードだけど、実際にブルーになるのは日付計算です。
Cocoaの日付計算は覚えることも少なく直感的に書けて、かつ柔軟で優秀だと思うんだけど、どうしても行数はかさむ。
Rubyはどんな感じなのかな。今日は眠いので次にでも書き比べてみます。

The new Audi Q3 Decode Challenge用ツール

codes = [
  [0,0,1],
  [2,2,2]
]
ans = ''
codes.each do |code|
  byte = ?a.getbyte(0)
  code.each_with_index do |c, idx|
    byte += c * (3 ** (2-idx))
  end
  ans << (byte-1).chr
end
p ans.upcase #=> 'AZ'

ちなみに僕は解けませんでした。これでも使って皆様頑張ってください。

追記

解けました。。
6番(及びその右上)はx=2なんですね。。なんていうトラップ。。
皆様気をつけてください。

もっとハノってみた

ハノイの塔が途中経過状態でも解を出せるように改良してみた。

state = [[5, 3, 1], [4], [2]]
hanoi(5, state, "A", "B", "C")

で、何がしたいのかは分かるでしょ。

def create_counter
  count = 0
  return Proc.new do
    count += 1
  end
end

$counter = create_counter()

def hanoi(n, state, from, work, dest)
  return if n.zero?

  # Get index
  index = 0
  state.each_with_index do |item, idx|
    index = idx if item.include? n
  end

  case index
  when 0
    from_state, work_state, dest_state = state
    
    # Move obstacle
    if work_state.include? n-2
      hanoi(item, [work_state, from_state, dest_state], work, from, dest)
    end
    
    hanoi(n-1, [from_state, dest_state, work_state], from, dest, work)

    # Move obstacle
    if dest_state.include? n-2
      hanoi(item, [dest_state, from_state, work_state], dest, from, work)
    end

    puts "#{"%04d" % $counter.call}: move #{n}, from #{from} to #{dest}"

    dest_state.push(from_state.pop)
    hanoi(n-1, [work_state, from_state, dest_state], work, from, dest)
  when 1
    from_state, work_state, dest_state = state
    hanoi(n, [work_state, from_state, dest_state], work, from, dest)    
  when 2
    hanoi(n-1, state, from, work, dest)
  end
end

state = [[5, 3, 1], [4], [2]]
hanoi(5, state, "A", "B", "C")

# Output
0001: move 1, from A to C
0002: move 3, from A to B
0003: move 1, from C to A
0004: move 2, from C to B
0005: move 1, from A to B
0006: move 5, from A to C
0007: move 1, from B to A
0008: move 2, from B to C
0009: move 1, from A to C
0010: move 3, from B to A
0011: move 1, from C to B
0012: move 2, from C to A
0013: move 1, from B to A
0014: move 4, from B to C
0015: move 1, from A to C
0016: move 2, from A to B
0017: move 1, from C to B
0018: move 3, from A to C
0019: move 1, from B to A
0020: move 2, from B to C
0021: move 1, from A to C

まあ解けてるみたいなんですが、問題は

  • 「これが最短解である証明」
  • 「どの場合でもこのプログラムで解けるかのテストを通過していない」

の二点です。ていうかホントにあってんのかコレ。

あと、相変わらずRubyっぽい書き方になっていないような気がする。
コードをスマートに書き、可読性を高めるための道具が色々と揃っているってのが、初心者から見たRubyの大きな魅力。
メタプロ等を可能にする柔軟性や言語設計のセンスの高さなどを実感するにはまだまだ修行が足りない。
まずは「Rubyらしい書き方」を身に着けたい。自分のコードは「Cを部分的にRubyに置き換えただけ」な感が拭えない。

ハノってみた

書いてみた

ハノイの塔
Ruby

def create_counter
  count = 0;
  return Proc.new do
    count += 1;
  end
end

$counter = create_counter()

def hanoi(n, from, work, dest)
  return if n.zero?
  hanoi(n-1, from, dest, work)
  puts "#{"%04d" % $counter.call}: move #{n}, from #{from} to #{dest}"
  hanoi(n-1, work, from, dest)
end

hanoi(4, "A", "B", "C")
# output
0001: move 1, from A to B
0002: move 2, from A to C
0003: move 1, from B to C
0004: move 3, from A to B
0005: move 1, from C to A
0006: move 2, from C to B
0007: move 1, from A to B
0008: move 4, from A to C
0009: move 1, from B to C
0010: move 2, from B to A
0011: move 1, from C to A
0012: move 3, from B to C
0013: move 1, from A to B
0014: move 2, from A to C
0015: move 1, from B to C

解法自体はいくつか解説サイトを見た。理解はできたが、似たような問題をまた見た時にサクッと解けるかは別問題。もう少し慣れが必要だろう。

出力に行番号が欲しかったので少し手を加えた。もっとスマートに出来ないものかなあ。
グローバル変数を使わなくて済むように、こんなのも書いてみた。

class Hanoi
  def initialize
    def create_counter
      count = 0
      return Proc.new do
        count += 1
      end
    end
    @counter = create_counter
  end

  def showAnswer(n, from, work, dest)
    return if n.zero?
    self.showAnswer(n-1, from, dest, work)
    puts "#{"%04d" % @counter.call}: move #{n}, from #{from} to #{dest}"
    self.showAnswer(n-1, work, from, dest)
  end
end

hanoi = Hanoi.new
hanoi.showAnswer(4, "A", "B", "C")

うーん。全くRubyを使いこなせてない気がする。

次の最善手

上記のプログラムが、ハノイの塔の最短解であることは理解している。しかし、任意の状態からの最短解は解けない。
たとえば、ハノイの塔をユーザが解くゲームがあるとする。ユーザがウンウン唸りながら色々と動かしてみた。しかし解けない!もうギブアップだ!そんな時に、「次の最善手」を教えてくれるプログラムはどんなものになるだろうか。
これはちょっと面白いような気がする。とても簡単なプログラムになるのか、あるいはひたすら探索するプログラムになるか、条件分岐を駆使した再帰呼出し関数になるか。
うーむ。うーむ。考えてみよう。。