上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

--/--|スポンサー広告||TOP↑
http://d.hatena.ne.jp/shimobayashi/20090718/1247894330
上記ページのコードを元に自分に必要な変更を加えてみました。
shimobayashiさんに感謝
  • Ruby 1.9系対応(1.9.2動作確認。1.8系は使ってないけど、動かないはず)
  • ソースはUTF-8前提
  • 出来るだけアクセスが少なくなるようにしてみた。(初期化を遅延させてます)
  • 漫画にも対応。
  • イラストと漫画での統一されたインターフェース。(イラストにeach入れただけだけど)
  • ファイル保存時に便利な名前を返すfilenameメソッド付き。
  • 大量データアクセス時にメモリ不足になるのを防ぐため、画像データはライブラリで保持しません。
  • キャメルケースなメソッドをアンダースコア区切りに変更しました。
  • Mechanizeの便利なメソッドを使い切れてない気がします。
  • ソースコードを綺麗に貼り付けたいです(pre, codeタグ付けてもなんか勝手に改行消えるし)
下記のコードはライブラリ部のみです。実用スクリプトは自分で組んでみてください。
require 'rubygems'
require 'mechanize'
require 'uri'
class Mechanize
  class Page
    def utf8
      b = body
      b.force_encoding("UTF-8") if b
      b
    end
  end
end
class Object
  def self.lazy_attr_reader(bind, *names)
    names.each do |name|
      define_method name do
        send bind
        instance_variable_get :"@#{name}"
      end
    end
  end
end
class Pixiv
  attr_reader :agent, :bookmark_new_illust
  def initialize(pixiv_id, pass)
    @agent = Mechanize.new
    @agent.max_history = 1
    login(pixiv_id, pass)
    @bookmark_new_illust = BookmarkNewIllust.new(self)
  end
  class LoginFailedError < StandardError; end
  def login(pixiv_id, pass)
    form = get('http://www.pixiv.net/index.php').forms.first
    form.pixiv_id = pixiv_id
    form.pass = pass
    raise LoginFailedError unless @agent.submit(form).utf8 =~ /ログアウト/
  end
  
  def get(url, options={})
    wait_time = options[:sleep] || 1
    puts "get: #{url}, options:#{options}"
    sleep wait_time if wait_time
    @agent.get(url, options.fetch(:query, []), options[:refer])
  end
  
  def member_illust_list(id)
    MemberIllustList.new self, id
  end
  def search(word, s_mode)
    Search.new(self, word, s_mode)
  end
  def search_by_tag(word)
    search(word, 's_tag')
  end
  def search_by_title_and_caption(word)
    search(word, 's_tc')
  end
  
  def member_illust(id)
    MemberIllust.new(self, id)
  end
  class MemberIllust
    attr_reader :id, :url, :pixiv
    lazy_attr_reader :init_page, :title, :artist, :artist_id, :type, :illust, :manga
    def initialize(pixiv, id)
      @pixiv = pixiv
      @id = id.to_i
      @url = "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=#{id}"
      @init_page = false
      @illust = nil
      @manga = nil
    end
    def medium
      @illust.medium if illust?
    end
    def big
      @illust.big if illust?
    end
    
    def each(&b)
      if illust?
        b.call(illust)
      else
        manga.each(&b)
      end
    end
    
    def manga?
      @manga ? true : false
    end
    
    def illust?
      @illust ? true : false
    end
    
  private
    
    lazy_attr_reader :init_page, :manga_urls, :manga_page_url
    
    def init_page
      unless @init_page
        page = @pixiv.get(@url)
        # Get Title and Artist
        page.title =~ /\A「(.+)」\/「(.+)」の(イラスト|漫画) \[pixiv\]\z/
        @title, @artist, @type = $1, $2, $3
        page.utf8 =~ %r[<a href="/member.php\?id=(\d+)" class="avatar_m" [^>]*>]
        @artist_id = $1
        if @type == "イラスト"
          # Get Medium Size URL
          @illust = Illust.new(self, page)
        else
          # Manga
          @manga = Manga.new(self, page)
        end
        @init_page = true
      end
    end
    
    class Picture
      def self.delegate(*names)
        names.each do |name|
          define_method name do |*args, &b|
            @member_illust.send(name, *args, &b)
          end
        end
      end
      
      def initialize(member_illust)
        @member_illust = member_illust
      end
      
      delegate :pixiv, :id, :title, :artist, :artist_id
    end
    
    class Illust < Picture
      def initialize(member_illust, page)
        super(member_illust)
        page.utf8 =~ /"(http:\/\/.+\.pixiv\.net\/img\/.+\/\d+_m(\..{3})(?:\?\d+)?)"/
        @medium_url = $1
        @ext = $2
        
        @big_page_url = "http://www.pixiv.net/member_illust.php?mode=big&illust_id=#{id}"
        @init_big = false
      end
      
      attr_reader :medium_url, :ext, :big_page_url
      lazy_attr_reader :init_big, :url, :big_url
      
      def data
        big
      end
      
      def medium
        pixiv.get(medium_url, refer: @member_illust.url).body
      end
      
      def big
        pixiv.get(big_url, refer: big_page_url, sleep: nil).body
      end
      
      def filename
        "#{id}_#{title}#{ext}"
      end
      
    private
      
      def init_big
        unless @init_big
          # Get Big Size URL
          bigpage = pixiv.get(big_page_url, refer: @member_illust.url)
          bigpage.utf8 =~ %r[<img src="(http://img\d+\.pixiv\.net/img/[^/]+/\d+\..{3}(?:\?\d+)?)" border="0">]
          @url = @big_url = $1
          @init_big = true
        end
      end
    end
    
    class Manga
      include Enumerable
      
      NUMBER_OF_PAGE_PER_SCREEN = 50
      def initialize(member_illust, page)
        @member_illust = member_illust
        @id = member_illust.id
        @url = "http://www.pixiv.net/member_illust.php?mode=manga&illust_id=#{id}&type=scroll"
        @pages = nil
      end
      
      def pixiv
        @member_illust.pixiv
      end
      
      attr_reader :id, :member_illust, :illust_url, :url
      
      def init_manga
        unless @pages
          @pages = []
          loop do
            i = 0
            if @pages.empty?
              manga_page = pixiv.get(scroll_url(@pages.size), refer: member_illust.url)
            else
              manga_page = pixiv.get(scroll_url(@pages.size), refer: scroll_url(@pages.size - 1))
            end
            manga_page.utf8.scan(%r[<a href=".*"><img src="(http://img\d+\.pixiv\.net/img/[^/]+/\d+_p(\d+)(\..{3})(?:\?\d+)?)"></a>]) do |m|
              @pages << Page.new(self, m[1].to_i, m[0], m[2])
              i += 1
            end
            break unless i == NUMBER_OF_PAGE_PER_SCREEN
          end
        end
      end
      
      def scroll_url(idx)
        scroll_page = (idx / NUMBER_OF_PAGE_PER_SCREEN) + 1
        "#{url}&p=#{scroll_page}"
      end
      
      def [](idx)
        init_manga
        @pages[idx]
      end
      
      def each(&b)
        init_manga
        @pages.each(&b)
      end
      
      class Page < Picture
        def initialize(manga, index, url, ext)
          super(manga.member_illust)
          @manga = manga
          @index = index
          @url = url
          @ext = ext
        end
        
        attr_reader :index, :url, :ext
        
        def data
          pixiv.get(url, refer: @manga.scroll_url(index), sleep: nil).body
        end
        
        def filename
          "#{id}_#{title}_p#{index}#{ext}"
        end
      end
    end
  end
  class MemberIllustListBase
    include Enumerable
    
    NUMBER_OF_ILLUST_PER_PAGE = 20
    def initialize(pixiv)
      @pixiv = pixiv
      @member_illusts = []
      @reach_last = false
    end
    
    attr_reader :pixiv
    def [](idx)
      if not @reach_last and idx >= @member_illusts.size
        start_p = @member_illusts.size / NUMBER_OF_ILLUST_PER_PAGE + 1
        goal_p = idx / NUMBER_OF_ILLUST_PER_PAGE + 1
        for pn in start_p..goal_p
          url = generate_url(pn)
          page = pixiv.get(url)
          i = 0
          page.links.each do |link|
            if link.href =~ /member_illust\.php\?mode=medium&illust_id=(\d+)/
              member_illust = MemberIllust.new(pixiv, $1)
              @member_illusts << member_illust
              i += 1
            end
          end
          unless i == NUMBER_OF_ILLUST_PER_PAGE
            @reach_last = true
            break
          end
        end
      end
      return @member_illusts[idx]
    end
    def each(&b)
      if b
        i = 0
        while c = self[i]
          b.(c)
          i += 1
        end
        self
      else
        enum_for :each
      end
    end
  end
  class BookmarkNewIllust < MemberIllustListBase
    def generate_url(pn)
      "http://www.pixiv.net/bookmark_new_illust.php?mode=new&p=#{pn}"
    end
  end
  
  class MemberIllustList < MemberIllustListBase
    def initialize(pixiv, member_id)
      super(pixiv)
      @member_id = member_id
    end
    
    attr_reader :member_id
    def generate_url(pn)
      "http://www.pixiv.net/member_illust.php?id=#{member_id}&p=#{pn}"
    end
  end
  
  class Search < MemberIllustListBase
    def initialize(pixiv, word, s_mode)
      super(pixiv)
      @word = word
      @s_mode = s_mode
    end
    def generate_url(pn)
      "http://www.pixiv.net/search.php?word=#{URI.encode_www_form_component(@word)}&s_mode=#{@s_mode}&p=#{pn}"
    end
  end
end

スポンサーサイト
01/30|プログラムコメント(0)トラックバック(0)TOP↑

会社のマシンにruby(ver.1.9.1 mswin32)を突っ込んで、アプリの設定ファイルからの情報収集とかに使っている。
便利さについて語った文書は多いので、ここでは他言語を使ってきた私が感じた、違和感について語る。

文法への違和感
メソッド呼び出しの括弧の省略とか文末セミコロンの省略自体は特に違和感はなかったのだが、それに伴う記号の文法上の微妙な意味の違いに違和感を持つ。
簡単に言うと、メソッド呼び出しの括弧との間に空白を入れられなかったりとか(入れるような書き方は元からしてないが)、行末が次のトークンを必須にする記号かどうかで次の行に継続するか否かを判定したり、2項演算子と単項演算子の区別に空白の有無を利用する、といったような部分。
改行がただの空白ではなく、トークンとしては「改行」として存在するということ、そして括弧の意味の違いをとる為に「空白」もトークンとして存在する。
つまり、フリーフォーマットのように見えてフリーフォーマットじゃない文法に違和感が・・・。

ブロック付きメソッド呼び出し
メソッド呼び出しにブロックをつけられる。これ自体は特にイテレータなどの制御構造のように見える物の定義に便利だし、やはり対して違和感は持たなかった。
しかし、ちょっと引っかかったのが、ブロック付きで呼び出されたメソッドがそのブロックをすぐに呼び出さず、インスタンス変数に保存しておいて、別メソッドの呼び出しで使うような場合だ。

つまり、やや作為的な例だが、


class A
  def meth(&b)
    @b = b
  end
  def call
    @b.call
  end
end

a = A.new
a.meth do
  puts "さようなら"
end
puts "こんにちは"
a.call

というようなコードを実行すると、
こんにちは
さようなら
と出力される。

これは実際に書いたコードに対して、メソッド呼び出しにつけたブロックだけ前方から戻ってきて実行されていることがわかる。こういう後方へ戻るようなコードの流れは一般的にはわかりづらいとされるようだ。

もっとも、ブロック付きメソッドの呼び出しが無名関数を引数に渡す省略形のような物と考えれば納得できるが、そうすると今度はブロック付きメソッド呼び出しが、本当に制御構造のような物の時と無名関数を渡している時とで同じ見た目になると言うのがどうにも不自然に思える。
また、Rubyとしてはどちらの場合でもブロック付きメソッド呼び出しにするのが普通なので余計ややこしいところだ。メソッド自体はどういう挙動なのかわかって使っているので、書くときはいいが、見るときにもメソッドの挙動を把握する必要がある。

感じた違和感は上の二つくらいか。いずれの場合も慣れてしまう程度の物で、実際にもう慣れたが。


08/25|プログラムコメント(0)トラックバック(0)TOP↑
Cにおいて

0
'\0'
は結局(int)0になる。(ならなくてもよかった気がするけど、NULを0以外に割り当てる変態、変則コードだけでしょ。)

NULLはたいていただの0を定義するマクロだ。
#define NULL 0
(ただし、処理系によっては文法チェック上は(void*)にすることがある。
というか、VCだと上記の定義なのに警告でるんだが。予約語扱いか)

L'\0'
これは型はwchar_tになるが、結局0だ。

さらに、興味深いことに変数の初期化では
int a = {0};
としても0で初期化される。

また、ポインタの初期化、および代入で0を使用すると、勝手にヌルポインタを割り当てる。
ヌルポインタは内部表現で整数の0と異なってもよいが、コンパイラが調整してくれる。
(また、0と比較して等しくなる)

配列を[0}で初期化すると、すべての要素を0で初期化したときと同様に初期化される。
構造体で{0}を使うと、すべてのメンバ変数は0を代入したときと同様に初期化する。

また、両者ともにそれぞれのルールを再帰的に適用する。


以上から、{0}はすべての初期化式において0初期化できることがわかる。
0
NULL
'\0'
L'\0'
"\0" #これはただしくないがよく見る
""
L""
memset(p, 0, size) #これで構造体を初期化しちゃだめ。(特定処理系向けで、移植性がないのを理解しているか、絶対に他の処理系で仕事しないならいいけど)

これらを使い分けられない人は
{0}
で初期化してしまおう。


char *str = '\0';
char buf[512] = {"",};

とか、分けのわからないコードを見てそう思った。(上記の説明の通り、これで初期化できるのはわかるんだけど、なぜそう書いたのか理解できない。)
04/28|プログラムコメント(0)トラックバック(0)TOP↑
#2009/03/20 文章が全体的に説明不足、サンプルに本題と関係のない問題があったので、修正。


最近仕事で見かけたコードがちょっとひどかったので、その点をまとめてみる。
なお、下記のものはその遭遇した問題の概要を説明するためのサンプルであって、実際のものとは異なる。

以下に配列に格納された文字列をすべて結合する関数がある。

buf :バッファ
bufsiz :バッファ(buf)のサイズ
src :結合元の文字列の配列
num :結合する文字列の数
戻り値 :バッファを超えた場合1 そうでなければ0

int straraycat(char *buf, size_t bufsiz, const char * const *src, size_t num)
{
size_t buflen;
size_t i;

memset(buf, '\0', bufsiz);

for(i=0; i<num; i++)
{
srclen = strlen(src[i]);
buflen = strlen(buf);
if (buflen + srclen < bufsiz - 1)
{
strncat(buf, src[i], srclen);
}
else
{
return 1;
}
}
return 0;
}

ここでは問題を2つ挙げる。軽くソースを見てみよう。

まず、最初にsrclenを得ている。これは特に問題はない。
次にbufの文字列長を確認している。
ハイここでまず一つ目の問題点。
Cの場合文字列は特別な型ではなく、終端としてヌル文字を含むcharの配列に過ぎない。
よって、strlenはポインタの指す先頭からヌル文字になるまで一文字ずつチェックする。
これを繰り返すと大変効率が悪い。そして、求めるべき長さは、バッファはクリア済みであるために、実は自分が書き込んだバイト数そのものだ。
よって単にstrlen(src[i])の結果を合算しておけばよい。

次に、strncatしている。これが2つめの誤りだ。先に述べたとおり文字列終端を文字列の先頭から探すのは大変だ。
strncat(buf, src, num);

strncpy(buf + strlen(buf), src, num)
と、関数の呼び出しに関わるコストを除けば計算量が変わらない。
そして、問題のコードではすでに文字列の終端位置をstrlenで得ているのだから、
strncpy(buf + buflen, src, srclen)
でいいだろう。
さらに、上記の呼び出しでsrclenは対象文字列の長さそのものなので、
memcpy(buf + buflen, src, srclen);
とすべきだ。こうするとstrncpyでやっている終端文字のチェックが不要になるので高速化が期待できる。

そして、このように書き換えると出力先のヌル文字終端を一度も見ないようになるので、
冒頭のmemsetも不要になる。代わりに処理の最後にヌル文字を入れる。

これらを適用すると、


int straraycat(char *buf, size_t bufsiz, const char * const *src, size_t num)
{
size_t buflen = 0;
size_t i;

for(i=0; i<num; i++)
{
srclen = strlen(src[i]);
if (srclen + buflen < bufsiz - 1)
{
memcpy(buf + buflen, src[i], srclen);
buflen += srclen;
}
else
{
return 1;
}
}
buf[buflen] = '\0';
return 0;
}

となる。
関数の呼び出し回数と、その関数自体の期待されるコストが減少したのがわかるだろうか。
元のソースではこのような修正により、プログラム全体の処理時間が3割ほど減少した。

文字列処理用の標準関数を正しく使い分けよう。

なお、本来であれば戻り値はsize_t型で書き込みに成功したバイト数とするほうがより適切だろう。
そうすれば、この上位の結合関数を容易に書ける。しかし、今回はインターフェースが変わるものは認められないためやめた。(とはいっても、書き込み先の完全な0クリアはやめているのだが。)
また、Cとしては、ポインタへの直接的な加算でさらに高速化できるかもしれないが、この辺はスタイルが絡んでくるので、解説はしない。

これらは配列の長さを直接もたない、Cらしい問題といえる。そのほかの言語に見られる、大きさを持つ配列・文字列は、自身の長さを整数値として持っているので、その長さをいちいち計算することはない。
逆に性能上の問題からCを使わなければならないのであれば、このくらいのことは把握しておく必要がある。最初に書いたようなコードを書いてしまうようなら、C++ C# Java D そしてその他多くの文字列型を持つ言語を使う方がよっぽど処理が速くなるだろう。

Cが速いというのは、注意深いコーディングの末に得られるものであるという点を忘れてはならない。
03/13|プログラムコメント(3)トラックバック(0)TOP↑
使えるプログラム言語が数えてみたらすてきなことになっていた。
ただし、中には使う機会がないので習熟度が低いものも含まれる。
単に趣味で使ってみている言語を含む。
以下一覧表。

C
C++(低)
C#(趣味/勉強中)
D(趣味/勉強中)
Java(低)
Visual Basic 6.0(低)
Visual Basic for Aplication(低)
Visual Basic.NET
PHP
ECMAScript
 JavaScript(低)・ActionScript(低)
Ruby(趣味/勉強中)


静的型付けの言語で最強なのはDだとおもう。機能的にはC++のをすべて引き継いだ上でさらに追加して整理した形だ。この追加した機能というのが、動的型付けのスクリプト言語でよくある言語組み込みの強力な配列サポートだ。配列はよく使う機能なのだから言語のコア機能として高機能な配列がサポートされている方が都合がよい。もっとも高率と高機能性を追求した結果癖が強いように思うけど。

動的型付け言語では適度なわかりやすさで高機能なRubyだろう。
PHPも悪くはないが、標準ライブラリがオブジェクト指向じゃないので。
01/25|プログラムコメント(0)トラックバック(0)TOP↑
プロフィール

G.U.Nex

Author:G.U.Nex
職業:プログラマ
趣味:ゲーム(PC、コンシューマ)、ネットサーフィン、ニコニコ動画視聴、プログラミング、鉄道全般
PHP, C, C++, VB(系), Java, JavaScriptを使える。
最近はRubyにはまってる。

最近の記事
最近のコメント
最近のトラックバック
月別アーカイブ
カテゴリー
ブロとも申請フォーム
ブログ内検索
RSSフィード
リンク
フリーエリア
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。