Home > Ruby Archive

Ruby Archive

JavaとRubyで文字列の終端の扱いの違い

RubyのコードをJavaに書き直す際に注意する相違点が幾つかあったので、そのうちの一つを挙げてみます。特に文字列関係は色々やりにくいです。

a = "4321"
p a[4] #=> nil

Rubyでは文字を[]で指すとき終端文字の次の添え字を指すとnilを返します。これをCで言う文字列の終端文字'\0'のように考え、if文などの判定に用いることが出来ます。

対して、Javaで同じような書式で書こうものなら、

public class Test {
  public static void main(String[] args) {
    String str = "abcdef";
    char[] arr = str.toCharArray(); // String型をchar型の配列に変換

    System.out.println(arr);
    System.out.println(str.length()+ " == " + arr.length);
    try {
      System.out.println(arr[6]);
    } catch (java.lang.ArrayIndexOutOfBoundsException e) {
      System.out.println("キャッチ:" + e);
-    }
    try {
      System.out.println(str.charAt(6));
    } catch (java.lang.StringIndexOutOfBoundsException e) {
      System.out.println("キャッチ:" + e);
    }
    // 最後の文字を知るためには
    System.out.println(str.charAt(str.length() - 1));
  }
}

のように例外でキャッチしなければなりません。

実行結果

6 == 6
abcdef
キャッチ:java.lang.ArrayIndexOutOfBoundsException: 6
キャッチ:java.lang.StringIndexOutOfBoundsException: String index out of range: 6
f

まぁ、str.length()を用いて文字列長でcharAtで指す文字が文字列の末尾かどうかを判定することで制御すればよいまでの話かもしれません。

今回はとあるアルゴリズムを移植上、気づきが色々あったのでこうした機会に言語仕様に対する理解を深めていきたいです。

Webページから指定したタグの要素を抜き出すRuby関数

単一のWebページから抜き出した複数の要素を配列に格納して返します。
以下の例はaタグの要素(エレメント)を抽出した場合です。

Rubyコード

require 'net/http'
require 'kconv'

def parse_array(string, beg_tag, close_tag)
  array = Array.new
  string.scan(/#{beg_tag}(.*?)#{close_tag}/sm) { |matched|
    #puts matched
    array = array | matched
  }
  return array
end

Net::HTTP.version_1_2
Net::HTTP.start('b.hatena.ne.jp', 80) {|http|
  response = http.get('/hotentry/')
  str = Kconv.tosjis(response.body)
  a_tag_array = parse_array(str, "<a href=", "<\/a>" )
  puts a_tag_array
}

学んだこと

  • Net::HTTP.startメソッドをブロックを用いて呼び出すことによって、ブロックの間だけセッションを開いて接続し、ブロック終了とともに自動的にセッションを閉じる。Rubyに慣れてもJavaやCでの手続きを意識しておくこと。
  • 配列の結合には和集合(演算子は「|」)を用いた。この場合、重複する要素は1つとして数えられる。別々に数えたい場合は「+」演算子を用いる。
  • String#scanメソッドはブロックで用いるとブロック変数にマッチした部分を格納する。その際、正規表現の中で「()」が用いられると、()内の部分を配列にして返す。今回は別に配列にする必要はなかったかも。

参考サイト

Web サーバからドキュメントを得る - Rubyist Magazine - 標準添付ライブラリ紹介 【第 7 回】 net/http

タグの中の要素を抜き出すRuby関数

ライブラリを使えば簡単ですが、正規表現の学習の為に。

ソースコード

def return_between(unporsed, start, termi)
  unporsed =~ /#{start}(.*?)#{termi}/
  return $1
end

str = "<title>Trump Code</title>"
start = "<title>"
termi = "</title>"

puts return_between(str, start, termi)
#=> Trump Code

ここで学んだことは、正規表現の規則中に変数を用いる際は#{var_str}と表記すること。

POSTメソッドを用いてExcite翻訳を行うRubyコード

しかし、未完です。

Webの巡回などにはWWW::Mechanizeという便利なライブラリがありますが、あえてnet/httpのPOSTメソッドを使う理由は、単にPOSTそのものと正規表現の学習をするためです。

今回は正規表現で試行錯誤。

Rubyソースコード

#!/usr/bin/ruby
require 'net/http'
require 'kconv'

before = "hello"

http = Net::HTTP.new('www.excite.co.jp')
response = http.post('/world/english', "before=#{before}&wb_lp=ENJA")

result = Kconv.tosjis(response.body)
result =~ /"after"[^>]*>(.*)/ism
puts $1

実行結果

こんにちは
<中略>

ここで、正規表現

/"after"[^>]*>(.*)/ism

の部分を

/"after"[^>]*>(.*)< \/textarea>/ism

に変更するとマッチしなくなってしまいました。
オプション指定のmで複数行にマッチするはずなんですが・・・うーん、何を見落としているのだろう。

Rubyで引数の設定値によって4パターンの部分文字列を取得するラッパー関数

引数に設定値を与え、それによって挙動を変えることで、似た機能をまとめてみます。

追記(2008.2.8):正規表現のマッチを保持する変数があったことを失念していました。「$`」マッチした部分より前の文字列、「$&」マッチした文字列、「$'」マッチした部分より後ろの文字列を使えばより簡潔に書けると思いました。

ソースコード

EXCL = true
INCL = false
BEFORE = true
AFTER = false
#
# string 分割する文字列
# delineator 分割する場所
# desired    BEFORE: delineator 文字列より前の文字列
#            AFTER:  delineator 文字列より後の文字列
# type       INCL:   分割文字列に delineator を加える
#            EXCL:   分割文字列に delineator を加えない
def split_str(string, delineator, desired, type)
  low_str = string.downcase # 小文字に揃える(変換)
  marker = delineator.downcase
  return if (low_str.index(marker) == nil) # delineator に一致しない
  if (desired == BEFORE)
    if (type == EXCL)
      split_pos = low_str.index(marker)
    else
      split_pos = low_str.index(marker) + marker.length
    end
    parsed_str = low_str[0, split_pos]
  else
    if (type == EXCL)
      split_pos = low_str.index(marker)+marker.length
    else
      split_pos = low_str.index(marker)
    end
    parsed_str = low_str[split_pos, string.length]
  end
  return parsed_str
end

string = "I'm designing a Web crawler and a Search engine."
p split_str(string, "crawler", BEFORE, INCL);
p split_str(string, "crawler", BEFORE, EXCL);
p split_str(string, "crawler", AFTER, INCL);
p split_str(string, "crawler", AFTER, EXCL);
p split_str(string, "crauler", AFTER, EXCL);

実行結果

"i'm designing a web crawler"
"i'm designing a web "
"crawler and a search engine."
" and a search engine."
nil

単に部分文字列を取得するには

str = "A Web crawler"
p str[6, str.size] #=> "crawler"
Page 1 of 212

Home > Ruby Archive

バックナンバー
最近のコメント
最近のトラックバック
メタ情報

Return to page top