「require」を含む日記 RSS

はてなキーワード: requireとは

2012-01-05

Types of digital cameras

Digital cameras are made in a wide range of sizes, prices and capabilities. The majority are camera phones, operated as a mobile application through the cellphone menu. Professional photographers and many amateurs use larger, more expensive digital single-lens reflex cameras (DSLR) for their greater versatility. Between these extremes lie digital compact cameras and bridge digital cameras that "bridge" the gap between amateur and professional cameras. Specialized cameras including multispectral imaging equipment and astrographs continue to serve the scientific, military, medical and other special purposes for which digital photography was invented.

[edit]Compact digital cameras


Subcompact with lens assembly retracted

Compact cameras are designed to be tiny and portable and are particularly suitable for casual and "snapshot" uses. Hence, they are also called point-and-shoot cameras. The smallest, generally less than 20 mm thick, are described as subcompacts or "ultra-compacts" and some are nearly credit card size.[2]

Most, apart from ruggedized or water-resistant models, incorporate a retractable lens assembly allowing a thin camera to have a moderately long focal length and thus fully exploit an image sensor larger than that on a camera phone, and a mechanized lens cap to cover the lens when retracted. The retracted and capped lens is protected from keys, coins and other hard objects, thus making it a thin, pocketable package. Subcompacts commonly have one lug and a short wrist strap which aids extraction from a pocket, while thicker compacts may have two lugs for attaching a neck strap.

Compact cameras are usually designed to be easy to use, sacrificing advanced features and picture quality for compactness and simplicity; images can usually only be stored using lossy compression (JPEG). Most have a built-in flash usually of low power, sufficient for nearby subjects. Live preview is almost always used to frame the photo. Most have limited motion picture capability. Compacts often have macro capability and zoom lenses but the zoom range is usually less than for bridge and DSLR cameras. Generally a contrast-detect autofocus system, using the image data from the live preview feed of the main imager, focuses the lens.

Typically, these cameras incorporate a nearly silent leaf shutter into their lenses.

For lower cost and smaller size, these cameras typically use image sensors with a diagonal of approximately 6 mm, corresponding to a crop factor around 6. This gives them weaker low-light performance, greater depth of field, generally closer focusing ability, and smaller components than cameras using larger sensors.

Starting in 2011, some compact digital cameras can take 3D still photos. These 3D compact stereo cameras can capture 3D panoramic photos for play back on a 3D TV.[3] Some of these are rugged and waterproof, and some have GPS, compass, barometer and altimeter. [4]

[edit]Bridge cameras


Sony DSC-H2

Main article: Bridge camera

Bridge are higher-end digital cameras that physically and ergonomically resemble DSLRs and share with them some advanced features, but share with compacts the use of a fixed lens and a small sensor. Like compacts, most use live preview to frame the image. Their autofocus uses the same contrast-detect mechanism, but many bridge cameras have a manual focus mode, in some cases using a separate focus ring, for greater control. They originally "bridged" the gap between affordable point-and-shoot cameras and the then unaffordable earlier digital SLRs.

Due to the combination of big physical size but a small sensor, many of these cameras have very highly specified lenses with large zoom range and fast aperture, partially compensating for the inability to change lenses. On some, the lens qualifies as superzoom. To compensate for the lesser sensitivity of their small sensors, these cameras almost always include an image stabilization system to enable longer handheld exposures.

These cameras are sometimes marketed as and confused with digital SLR cameras since the appearance is similar. Bridge cameras lack the reflex viewing system of DSLRs, are usually fitted with fixed (non-interchangeable) lenses (although some have a lens thread to attach accessory wide-angle or telephoto converters), and can usually take movies with sound. The scene is composed by viewing either the liquid crystal display or the electronic viewfinder (EVF). Most have a longer shutter lag than a true dSLR, but they are capable of good image quality (with sufficient light) while being more compact and lighter than DSLRs. High-end models of this type have comparable resolutions to low and mid-range DSLRs. Many of these cameras can store images in a Raw image format, or processed and JPEG compressed, or both. The majority have a built-in flash similar to those found in DSLRs.

In bright sun, the quality difference between a good compact camera and a digital SLR is minimal but bridgecams are more portable, cost less and have a similar zoom ability to dSLR. Thus a Bridge camera may better suit outdoor daytime activities, except when seeking professional-quality photos.[5]

In low light conditions and/or at ISO equivalents above 800, most bridge cameras (or megazooms) lack in image quality when compared to even entry level DSLRs. However, they do have one major advantage: their much larger depth of field due to the small sensor as compared to a DSLR, allowing larger apertures with shorter exposure times.

A 3D Photo Mode was introduced in 2011, whereby the camera automatically takes a second image from a slightly different perspective and provides a standard .MPO file for stereo display. [6]

[edit]Mirrorless interchangeable-lens camera

Main article: Mirrorless interchangeable-lens camera

In late 2008, a new type of camera emerged, combining the larger sensors and interchangeable lenses of DSLRs with the live-preview viewing system of compact cameras, either through an electronic viewfinder or on the rear LCD. These are simpler and more compact than DSLRs due to the removal of the mirror box, and typically emulate the handling and ergonomics of either DSLRs or compacts. The system is used by Micro Four Thirds, borrowing components from the Four Thirds DSLR system.

[edit]Digital single lens reflex cameras


Cutaway of an Olympus E-30 DSLR

Main article: Digital single-lens reflex camera

Digital single-lens reflex cameras (DSLRs) are digital cameras based on film single-lens reflex cameras (SLRs). They take their name from their unique viewing system, in which a mirror reflects light from the lens through a separate optical viewfinder. At the moment of exposure the mirror flips out of the way, making a distinctive "clack" sound and allowing light to fall on the imager.

Since no light reaches the imager during framing, autofocus is accomplished using specialized sensors in the mirror box itself. Most 21st century DSLRs also have a "live view" mode that emulates the live preview system of compact cameras, when selected.

These cameras have much larger sensors than the other types, typically 18 mm to 36 mm on the diagonal (crop factor 2, 1.6, or 1). This gives them superior low-light performance, less depth of field at a given aperture, and a larger size.

They make use of interchangeable lenses; each major DSLR manufacturer also sells a line of lenses specifically intended to be used on their cameras. This allows the user to select a lens designed for the application at hand: wide-angle, telephoto, low-light, etc. So each lens does not require its own shutter, DSLRs use a focal-plane shutter in front of the imager, behind the mirror.

[edit]Digital rangefinders

Main article: Rangefinder camera#Digital rangefinder

A rangefinder is a user-operated optical mechanism to measure subject distance once widely used on film cameras. Most digital cameras measure subject distance automatically using electro-optical techniques, but it is not customary to say that they have a rangefinder.

[edit]Line-scan camera systems

A line-scan camera is a camera device containing a line-scan image sensor chip, and a focusing mechanism. These cameras are almost solely used in industrial settings to capture an image of a constant stream of moving material. Unlike video cameras, line-scan cameras use a single row of pixel sensors, instead of a matrix of them. Data coming from the line-scan camera has a frequency, where the camera scans a line, waits, and repeats. The data coming from the line-scan camera is commonly processed by a computer, to collect the one-dimensional line data and to create a two-dimensional image. The collected two-dimensional image data is then processed by image-processing methods for industrial purposes.

Further information: Rotating line camera

[edit]Integration

Many devices include digital cameras built into or integrated into them. For example, mobile phones often include digital cameras; those that do are known as camera phones. Other small electronic devices (especially those used for communication) such as PDAs, laptops and BlackBerry devices often contain an integral digital camera, and most 21st century camcorders can also make still pictures.

Due to the limited storage capacity and general emphasis on convenience rather than image quality, almost all these integrated or converged devices store images in the lossy but compact JPEG file format.

Mobile phones incorporating digital cameras were introduced in Japan in 2001 by J-Phone. In 2003 camera phones outsold stand-alone digital cameras, and in 2006 they outsold all film-based cameras and digital cameras combined. These camera phones reached a billion devices sold in only five years, and by 2007 more than half of the installed base of all mobile phones were camera phones. Sales of separate cameras peaked in 2008. [7]

Integrated cameras tend to be at the very lowest end of the scale of digital cameras in technical specifications, such as resolution, optical quality, and ability to use accessories. With rapid development, however, the gap between mainstream compact digital cameras and camera phones is closing, and high-end camera phones are competitive with low-end stand-alone digital cameras of the same generation.

[edit]Waterproof


A Canon WP-1 waterproof 35 mm film camera

Waterproof digital cameras are digital cameras that can make pictures underwater. Waterproof housings have long been made but they cost almost as the cameras. Many waterproof digital cameras are shockproof and resistant to low temperatures; one of them is Canon PowerShot D10, one of the first underwater digital cameras.

These cameras become very popular during the holiday season, because many people want to save the best moments from their holidays at the seaside. Waterproof watches and mobile phones were produced earlier. Most makers of digital cameras also produce waterproof ones and every year they launch at least one new model, for example Sony, Olympus, Canon, Fuji.

Healthways Mako Shark, an early waterproof camera,[8] was launched in 1958 and cost around 25 dollars. It was a huge camera and pictures were black and white.



___________________________

http://www.gobatteryonline.com/canon-nb-9l-digital-camera-battery-gose.html

http://www.gobatteryonline.com/canon-lp-e5-digital-camera-battery-gose.html

http://www.gobatteryonline.com/canon-powershot-sd500-battery-charger-gose.html

2011-12-28

Summer scenery to take pictures of small skills

Undesirable cameras benefits us many 100 % satisfaction, leading to numerous spectacular images, as well as the hot months is operating relating to dependable summertime. This specific classmate through break free will be able to figure out any tons in addition to waterways, downtown along with far-away beautiful gardening, and yet may these pure attractiveness to become a memento playing hit decrease, apart from skin color boil due to the high-end digicam purpose, a great deal more simple could be to evaluation whatever taking pictures competency as well as having photos. The amount, smaller try to make your work latest many of the summer season harvesting powerful see to it, typically the classmate need to next image have a go through, you become this confidently unexpected wonder.

Digital cameras would bring us lots of enjoyment, leaving a myriad of unique images, and summertime is journeying in wonderful summer season. The classmate throughout the getaway can recognize that reams together with estuaries and rivers, metropolitan and also countryside attractive panoramas, yet take place all these charm being a memento opportunity straight down, together with steam as a result of a camcorder operation, a lot more crucial is usually to examine ones own taking functionality and also taking photographs. That period, the little generate your choice have quite a few summer months firing effective be mindful, the particular classmate may as well within the next snapshot have a shot at, you will get the particular total surprising stun.

Household landscapes

By out of doors firing indoors panoramas, basic will need throughout the Windows 7, out-of-doors mild typically currently have more substantial indoors light-weight, now a couple of challenges ought to be sorted out, is the particular coverage time frame, a couple of it truly is in order to avoid a display reflective, when pics of any camera through hands-on management operate, might take number of years coverage setting up, when photographic camera goes so that you can twit style, can easily will likely be ISO level of sensitivity Surroundings bump up, this ISO 2 hundred or perhaps ISO six hundred aiming.

Inside panorama 's best golf shot employing a tripod, in any other case, wants a spot to guide, one example is in opposition to any divider and entry, and even the true reason for executing it is because very long coverage is quite effortless construct any digital camera protein shake, producing photographs regarding fluffy envision. It's best to utilize the shutter launch, or perhaps utilize to get center-weighted avoiding relocate a video camera. Notice: except if absolutely necessary, tend not to available the particular pen, stay clear of a lot more reflective.

Out-of-doors landscapes

Bring that landscape designs from the probability with the photo may well schedule some individuals or even factors, and this also may help the room inside the operation of your view photos. Get a large surfaces area photos, as an example the veranda as well as the roof top, hillside, for example. Typically inside the mid-day is without a doubt the most suitable time frame filming this landscapes. Filming, having a polarizer to adjust a illumination with the stars, produce the particular heavens develop into dreary quite a few, well known from the glowing blue stars the white kind of fog up, as a way to boost the space or room repeatedly.

Several virtually no guidebook vulnerability management of any camera together with location form, as well as the landscape designs way is completely features on the list of manner, you are able to use landscape designs way to adopt shots. Together with handbook vulnerability restrain photographic camera might opt for aperture goal option that will movie, aperture to help acquired far better pick out F8 to help you photograph or maybe F11, this photo will be extra management concentrations senses.

houses

The particular modify on the urban center is without a doubt switching, specifically together with a lot of approach large establishing intended for symbolize, and also this are at recent many portrait digital photography buffs including firing template.

Yet typical photographic camera through simply how much suitable container distortion, hence as a way to make an effort to reduce the photograph of your deformation belonging to the accumulating " up ", thus you ought to pick around substantial thoughts and opinions filming. One example is inside the steps, or perhaps some other might help the beliefs with area. If you fail to find the correct destination for a reside, you can from the again, clear of the particular lead entity to cut back a distortion. Maybe you've employed the greatest wide-angle standard zoom lens. Vivid stars will be able to replace with a developing with the darkish. Having a polarizer to cut back or perhaps wipe out construction with reflective a glass. In addition to, certainly, consume an excellent know with the can make one of a kind imaginative results.

mineral water

Firing apply or maybe a aerosol in waters, incorporate the use of part mild and also backlight try to make waters intended for introducing see through pattern. Several novices all of coveted by a really "splash with photos, in truth, I am able to effortlessly applied for, so the hurrying h2o glance alot more downy, have got a form of passionate blur. Shutter possibly inside 1/10 so that you can 1/6 erinarians regulate might get move cosmetic perception for splash pics.

At night and also night time

Local plumber to be able to sunset taking scenario regarding is usually 15-30 moments, next the stars always possess some colouring failed to ends. As a way to raise the graphic with the amount with subject, can easily on the ending with the passage and also uncover several regarding an individual's special world. It is as well the location where the tripod on has to be, commonly programmed coverage is definitely o . k ., yet you should attempt to utilize hands-on visibility, and also the employment of the particular shutter put out restrain taking, respectively for 2, 5, 8, 12 and also of sixteen secs shutter tempo so that you can capture some photo contrast.

Needless to say now and again continue to are able to use ISO200 to help you photograph, yet so that you can be aware of an electronic camcorder with disturbance handle is normally adequate, when pixels will be overweight, or maybe together with ISO100 contrasting by using base style to help motion picture.

In truth firing night time scenarios, at times to be able to as well do not require that filming with step-by-step developing oh, this streets these kinds of, a small amount convert tips plus aiming design and style, might get the result of experiencing a specialized pattern.



_____________________________________________

http://www.gobatteryonline.com/canon-powershot-sd850-is-battery-charger-gose.html

http://www.chargerbatteryshop.co.uk/panasonic-lumix-dmc-tz7-battery-charger-cbbs.html

2010-07-24

google発のProtocol Buffersについて

オブジェクトシリアライズツールであるプロトコルバッファについて書きます。

プロトコルバッファって何って方はこちらへ

Protocol Buffers 本家

http://code.google.com/apis/protocolbuffers/

XMLはもう不要!? Googleシリアライズツール「Protocol Buffer」

http://journal.mycom.co.jp/articles/2008/07/18/protocolbuffer/index.html

Protocol Buffers (Protocol Buffers の内部解説記事。とても参考になります)

http://dodgson.org/omo/t/?date=20080712

内容

プロトコルバッファは異種言語間でオブジェクトのやりとりをするための規格です。

独自の言語によりオブジェクトインターフェースを規定することで、多言語対応を行っています。

例えばこんな感じ。

  • address.proto
package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;        // Unique ID number for this person.
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person person = 1;
}

以上のようなprotoファイルから各言語ソースコード、または何らかのデータ操作ライブラリを使いオブジェクトの処理を行います。

googleによってC++, Java, Python用のライブラリ作成されましたが、他の言語対応したサードパーティー製のライブラリがいくらでもあるので、実質的にほぼすべての言語で使えると言っても過言ではありません。

以下はこのライブラリを使ってみた感想などです。

整数型はVarintという可変長型でバイナリに保存される

数字が多きければ大きいほど、長いバイト長で保存されます。ただし、負数の場合符号ビットが立つ関係で、ほとんど常に変換後のバイト数が最長バイト数(10)になってしまいます。フィールドの型をsint32, sint64で宣言しると、各数値にzig-zags変換が行われるため、負数であってもその値の絶対値で使用バイト数が決まるようになります。

保存されるデータは各メッセージID/型/値のみ

バイナリに保存されるデータは各メッセージID/型/値のみです。なので、同じ定義の二つのメッセージ型は、プロトコルバッファ上では全く同じように扱うことが出来ます。例えば、片方からシリアライズしたデータを、もう片方の型でデシリアライズすることが可能です。

またオブジェクト連続シリアライズ/デシリアライズすることもできます。

継承されたクラスマッピング

すでに存在する継承関係のあるクラスを、Protocol Buffersでシリアライズ/デシリアライズしたい場合は次のようにします。

(Base, Derived はすでに存在するとします)

(ソースコード中になぜか日本語が書けないので、コメントはすべて英語になっています)

message PbBase {
        require int32 id = 1;
        require int32 value = 2;

        require Derived derived = 10; // - Point !!!
}

message PbDerived {
        require string string_value = 1;
}

継承元のメッセージ定義に、継承先のメッセージを持たせます。Base継承するクラスシリアライズ/デシリアライズしたい場合は、PbBaseメッセージを中心に処理を行うことで、比較的簡単に処理を実装することが出来ます。

例えばこんな感じ

Base *Base_DeserializeFrom(PbBase &pbobj)
{
    // Arrange the classes which inherits from Base.
    if (pbobj.has_derived()) {
        return new Derived(pbobj);
    }
    else
    ...
}

class Base {
    ...
    virtual void Base::SerializeTo(PbBase &pbobj) {
        // Set the fields of 'pbobj',
    }
    ...
};

class Derived {
    ...
    virtual void Base::SerializeTo(PbBase &pbobj)
    {
        PbDerived *derived = pbobj.mutable_derived();

        Base::SerializeTo(pbobj);
        // Set the fields of 'derived',
        ...
    }
    ...
};

protoファイルを以下のように書くと、メッセージの扱いが非常に難しくなります。

message PbBase {
        require int32 id = 1;
        require int32 value = 2;
}

message PbDerived {
        required PbBase base = 1; // - Here is the point !!!
        require string string_value = 2;
}

2010-06-24

Obama is controlled by Japan Goverment?

Possibly, my following story surprise you.

However, we, Japanese have a duty toward world people(included future generation)that tell the truth.


In 1998, when Obama was still just a lawyer, he came to Japan.

Then, Obama dealt with a lawsuit that was involved in Ford employer and Honda employer.

The content of this affair is following; in 1995, Ford employer stole the confidential report of Honda

(http:/nikkei.com/article=19951001_23), and this affair was disclosed by Japanese Police and Japanese Monopoly Commission.


This affair is responsible to Ford. And Honda was the victim.

But, in 1997 Obama was asked by Ford as their company lawyer. Then, Obama didn't know the true.

In this time, Obama was regarded as one of the best lawyer and Candidate. So he couldn't miss this lawsuit.

However, he had gradually understood his position and revealed his failure that undertook this lawsuit.


If this lawsuit occurred, Obama maybe didn't become Candidate and President of U.S.A.

But, the lawsuit didn't occur.In 1998, in Tokyo, Obama met some Japanese politicians, some Honda's boards.

Instead of giving up this lawsuit, they require Obama to accept a condition:

"If U.S.A government try to oust Japanese auto company, Obama and Ford should rescue Honda".

And Honda have been devoting a lot of money to Obama.


You must rethink 2009's situation.

Who is winner?

In 2009, when GM's problem occured, when Toyota's problem occured, who did get this benefit?



Some people maybe think this story is nonsense. Rethink.

2010-04-26

RubyJavaログ出力を統合する方法

JRuby上で動くRubyJavaログを同じファイルに保存したいときなど

JRuby界隈で何かいい方法ないかな~と探していたけど見つからないので

RubyのLoggerのインターフェースcommons-loggingを使用して実装してみた

使用バージョンは以下

require 'logger'

class CommonsLoggingLogger
  def initialize(name="ruby")
    @progname = nil
    @logger = org.apache.commons.logging.LogFactory.getLog(name)
  end
  def add(severity, message=nil, progname=@progname, &block)
    if message.nil? and block_given?
      message = yield
    end
    
    case severity
    when Logger::DEBUG
      debug(progname){message}
    when Logger::INFO
      info(progname){message}
    when Logger::WARN
      warn(progname){message}
    when Logger::ERROR
      error(progname){message}
    else
      fatal(progname){message}
    end
  end
  def debug(arg0=nil, &block)
    @logger.debug make_log(arg0, &block)
  end
  def info(arg0=nil, &block)
    @logger.info make_log(arg0, &block)
  end
  def warn(arg0=nil, &block)
    @logger.warn make_log(arg0, &block)
  end
  def error(arg0=nil, &block)
    @logger.error make_log(arg0, &block)
  end
  def fatal(arg0=nil, &block)
    @logger.fatal make_log(arg0, &block)
  end
  def debug?
    @logger.isDebugEnabled
  end
  def info?
    @logger.isInfoEnabled
  end
  def warn?
    @logger.isWarnEnabled
  end
  def error?
    @logger.isErrorEnabled
  end
  def fatal?
    @logger.isFatalEnabled
  end
  def level
    if debug?
      Logger::DEBUG
    elsif info?
      Logger::INFO
    elsif warn?
      Logger::WARN
    elsif error?
      Logger::ERROR
    else
      Logger::FATAL
    end
  end
  def level=(lv)
    #do nothing
  end
  def sev_threshold
    level
  end
  def sev_threshold=(lv)
    #do nothing
  end
  def datetime_format
    nil
  end
  def datetime_format=(fm)
    #do nothing
  end
  
  attr_accessor :progname
  
  private
  def make_log(message_or_progname, &block)
    if block_given?
      progname = message_or_progname || @progname
      message = yield
    else
      progname = @progname
      message = message_or_progname
    end
    
    progname_message(progname, message)
  end
  def progname_message(progname, message)
    progname.nil? ? message : "#{progname}: #{message}"
  end  
end

試しにRedmineに設定して動かしてみたところ大丈夫そうだった

2009-12-10

PHPのあの関数Perlでやるには?

元ネタ http://phpspot.org/blog/archives/2009/12/phpjavascriptph_1.html



面白そうだと思ったので僕もやってみた。モジュールPerl5.8系の標準モジュールのみ利用可という制限。

全部はキツイので関数処理関係の関数(http://php.benscom.com/manual/ja/ref.funchand.php)だけ実装してみた。

use strict;
use warnings;

=head2 call_user_func

 $ret = call_user_func($function,@param);
 $ret = call_user_func([$class,$method],@param);

example1

 sub plus { $_[0] + $_[1] }
 print call_user_func('plus',10,20); # 30

example2

 package Foo;
 sub plus { $_[1] + $_[2] }
 package main;
 print call_user_func(['Foo','plus'],10,20); # 30

=cut

sub call_user_func {
    my $proto = shift;
    if ( ref $proto eq 'ARRAY' ) {
        return $$proto[0]->${\$$proto[1]}(@_);
    }
    else {
        require Pod::Functions;
        if ( $Pod::Functions::Flavor{$proto} ) {
            return eval qq{$proto(\@_)};
        }
        else {
            no strict 'refs';
            return $proto->(@_);
        }
    }
}

=head2 call_user_func_array

 $ret = call_user_func_array($function,\@param);
 $ret = call_user_func_array([$class,$method],\@param);

example

 sub plus { $_[0] + $_[1] }
 print call_user_func_array('plus',[10,20]); # 30

=cut

sub call_user_func_array {
    return call_user_func(shift,@{+shift});
}

=head2 create_function

 $code = create_function($args_str,$code_str);

example

 $code = create_function('$c,$d=1','print $c+$d');
 $code->(10); # 11

=cut

sub create_function {
    my $args = shift;
    my $code = shift;
    my $default = 0;
    my @args = split /,/,$args;
    my $code_str = 'sub {';
    for my $arg (@args) {
        if ( $arg =~ /^\s*(\$[a-zA-Z][\w]*)\s*(?:=\s*(.+))?\s*$/ ) {
            my $val = $1;
            my $def = $2;
            if ( defined $def ) {
                $default = 1;
                $code_str .= qq{my $val = \@_ ? shift : $def;\n};
            }
            else {
                die 'parse error' if $default;
                $code_str .= qq{my $val = shift;\n};
            }
        }
    }
    $code_str .= $code . '}';
    my $sub = eval $code_str;
    die $@ if $@;
    return $sub;
}

=head2 forward_static_call

 $ret = forward_static_call($function,@param);
 $ret = forward_static_call([$class,$method],@param);

=cut

sub forward_static_call {
    call_user_func(@_);
}

=head2 forward_static_call_array

 $ret = forward_static_call_array($function,\@param);
 $ret = forward_static_call_array([$class,$method],\@param);

=cut

sub forward_static_call_array {
    call_user_func_array(@_);
}

=head2 func_get_arg

 $arg = func_get_arg($no)

example

 sub foo { print func_get_arg(1) }
 foo(100,200); # 200

=cut

sub func_get_arg {
    my $n = shift;
    package DB;
    @DB::args = ();
    () = caller(1);
    return defined $DB::args[$n] ? $DB::args[$n] : undef;
}

=head2 func_get_args

 @args = func_get_args()

example

 sub foo { print join ':', func_get_args() }
 foo(11,22,33); # 11:22:33

=cut

sub func_get_args {
    my $n = shift;
    package DB;
    @DB::args = ();
    () = caller(1);
    return @DB::args;
}

=head2 func_num_args

 $arg_count = func_num_args()

example

 sub foo { print func_num_args() }
 foo(11,22,33); # 3

=cut

sub func_num_args {
    my $n = shift;
    package DB;
    @DB::args = ();
    () = caller(1);
    return scalar @DB::args;
}

=head2 function_exists

 $bool = function_exists($func)

example

 sub foo {}
 print function_exists('foo');  # 1
 print function_exists('bar');  # 0
 print function_exists('rand'); # 1

=cut

sub function_exists {
    my $func = shift;
    return 1 if exists &$func;
    require Pod::Functions;
    return $Pod::Functions::Flavor{$func} ? 1 : 0;
}

=head2 get_defined_functions

 $funcs = get_defined_functions()

=cut

sub get_defined_functions {
    require Pod::Functions;
    return {
        internal => [ keys %Pod::Functions::Flavor ],
        user     => [ grep { exists &$_ } keys %:: ],
    };
}

=head2 register_shutdown_function

 register_shutdown_function($func,@param);
 register_shutdown_function([$class,$method],@param);

=cut

{
    my $REGISTER_SHUTDOWN_FUNCTION = [];
    sub register_shutdown_function {
        my $proto = shift;
        push @$REGISTER_SHUTDOWN_FUNCTION, [
            do {
                if ( ref $proto eq 'ARRAY' ) {
                    $$proto[0]->can($$proto[1]);
                }
                else {
                    require Pod::Functions;
                    if ( $Pod::Functions::Flavor{$proto} ) {
                        sub { eval qq{$proto(\@_)} };
                    }
                    else {
                        no strict 'refs';
                        \&$proto;
                    }
                }
            },
            [@_]
        ]
    }
    END {
        $_->[0]->(@{$_->[1]}) for @$REGISTER_SHUTDOWN_FUNCTION;
    }
}

思ったよりも難しかった。標準関数一覧を取る手段がなかったので標準モジュールを利用して標準関数の一覧を取得した。

あと文字列から標準関数を呼び出すスマートな手段が思いつかなかったのでeval便りに。

create_functionはかなりゴリ押し。myを勝手に付けたりデフォルト引数にも対応してたり細かい芸が光る(自分で言うな)

forward_static_callはぶっちゃけPerl的にcall_user_funcと殆ど処理が変わらないのでそのまま利用。

func_get_arg系は結構クリティカルだなー。@DB::argsをリアルに使ったの初めてだよ。

register_shutdown_functionはちょっとねー。ENDブロックを利用してるわけなんだけど当然mod_perlとかではうまく動かない。あとシグナルとか使った方が良いのかもしれない。

ヒマがあったら他の関数とかも実装してみたいかも。



プログラ増田のあなぐら

2009-10-12

[][]twitterのfollowingを整理するためのスクリプト

非常に頭が悪いやり方ですが、一応動くので置いておきます

必要なもの

動かし方

  1. 以下のソースのうち、meの部分を自分idに変えて保存。
  2. 実行
# -*- coding: utf-8 -*-
require 'uri'
require 'open-uri'
require 'rubygems'
require 'twitter'
require 'json'

LAST_YEAR = Time.now - 12*31*60*60*24
USERNAME = 'hogehoge'
PASSWORD = '********'

def following_me? other
  base = URI.parse('http://twitter.com/friendships/exists.json')
  base.query = "user_a=#{other}&user_b=#{USERNAME}"

  times = 0
  begin
    sleep 60
    open(base).read == 'true'
  rescue
    if times < 2
      times += 1
      retry
    else
      true
    end
  end
end

def posted? hash
  begin
    sleep 60
    last_post = @client.timeline_for(:user, :id=&gt;hash[:user], :count=&gt;1).first.created_at
  rescue Twitter::RESTError # protected ~A~K~A following ~A~L 0 ~A | ~P~H~A~Y~T~_~A~W~A~_~_
    p "#{$!}"
    p "passing  #{hash[:user]}"
    return true # ~D~P~F~_~P~F~J~\~A~Q~B~K
  rescue # twitter~A~L~P~A~A~B~K~Y~B対~V
    p "#{$!}"
    sleep 180
    retry
  end
  p "last_post was  #{last_post}"
  last_post &gt; hash[:from]
end

@client = Twitter::Client.new(:login =&gt; USERNAME, :password =&gt; PASSWORD)
(1..20).each do |pg|
  @client.my(:friends, :page=&gt;pg).each do |friend|
    p "checking #{friend.screen_name}"
    unless ((following_me? friend.screen_name) &amp;amp;&amp;amp; posted?({:from=&gt;LAST_YEAR, :user=&gt;friend.screen_name}))
      p "removing #{friend.screen_name}"
      sleep 60
      @client.friend(:remove, friend)
    end
  end
end

2009-03-05

http://anond.hatelabo.jp/20090303184207

http://anchor18.blog77.fc2.com/blog-entry-195.html

http://ameblo.jp/omisedayori/entry-10184130642.html

両方ブクマしてる人のコメント抜き出してみた。

あんまおもしろくないな。もっと面白いURL募集

teruyastar

["", "まいったね。 やはりWiiウェアなんかのDL方式か、ネトゲに移行せざるをえないか。"]

complex_cat

["何処をどう描いても完璧デッサン力。", "これについてはWiki記述は,確かに使う側を助けてしまっているかも。いや,ここを読むようなユーザーとの重複度は小さい

とは思うが。"]

silverscythe

["昔のはホントすごいよなあ", ""]

yachimon

["", ""]

shoku-in

["", ""]

mike_n

["", ""]

y_arim

["Dr.スランプの時点ですでに完成されていて、そこからさらに進化しているのはすごい。", "違法性の認識とか関係なく、便利なものがあるから使うってだけだろうな。たいていのひとは物事を深く考えないし自分の欲求には逆らわない。/そしてid:utd_sn3781の主張は増田に書かれた http://anond.hatelabo.jp/20081231201301"]

A410

["", ""]

tailtame

["やっぱり手書きの頃がいいなぁ…塗りが…塗りが…。&gt;&gt;66の赤いトカゲドラゴン?の艶とかたまらんなー。画集っていくつあるんだろ…", "蔓延しすぎているってのも あるんだろうな…本屋に行けば目に付く本は……。子供の頃なんてゲームは半年に一本でいいよ。クリエイターも割れしてたしな…"]

dododod

["", "おやおや"]

retlet

["", ""]

kashmir108

["", ""]

tokuhirom

["", ""]

ocha39

["", ""]

whirl

["", ""]

kalmalogy

["「ビッチズ・ライフ」買おう。", "親が買い与えるのは絶対だめ。『公認意識』が子供に芽生える。自腹で買って『本当はよくないけど次善の策』という意識でないと。/ マジコン使うならせめて『良いと思ったモノに投資する』意識教育すべきでは。"]

came8244

["", ""]

fujii_isana

["", ""]

lizy

["クロノトリガーの絵を見るだけで、頭にあのBGMが流れてくる", "モラルでの解決方法案としては、ゲーム作りの過程を理解してもらう、とかかな。どれだけ多くの人がどれだけ苦労してコストをかけて作っているのかを理解してもらうしか|それでもやる奴はもうダメですね"]

mochige

["", ""]

aohige9610

["中鶴の絵がまじってるので注意", ""]

gm333

["", ""]

posinega

["", ""]

pikayan

["", ""]

asitaki

["マジコンは叩くのに画像コピペブログは叩かないクソはてなーども↓。マジコンと同様にブログ規制すべき。", ""]

#!ruby -Ku

require "rubygems"
require "json"
require 'open-uri'

url = ["http://anchor18.blog77.fc2.com/blog-entry-195.html",
       "http://ameblo.jp/omisedayori/entry-10184130642.html"]

bookmarks = url.map{|u|
  open("http://b.hatena.ne.jp/entry/json/"+u){|f|
    JSON.parse(f.read.gsub(/\A\(|\)\Z/, ""))
  }
}

user_comment_map = bookmarks.map{|bm|
  h = {}
  bm["bookmarks"].each{|b|
    h[b["user"]] = b["comment"]
  }
  h
}

overlaped_users = user_comment_map.map{|bm|
  bm.keys
}.inject{|a,b|
  a&amp;amp;b
}

overlaped_users.each{|user|
  puts user
  p user_comment_map.map{|b| b[user]}
  puts
}

2009-02-25

http://anond.hatelabo.jp/20090225205005

25~11個

24 from know 

23 who because with 

22 economy

21 an new people plan so

20 by every health

19 us

18 America care they time

17 do

16 also that's these

15 all Americans It's must years your

14 about education energy here jobs make no

13 budget if

12 cost country one recovery than there tonight we've why

11 crisis how out reform those when

10個

at banks college Congress don't future money nation next responsibility some tax what where world

9個

children deficit even just me meet need put up work year

8個

afford back cannot down economic end first help lending my see take times was way

7個

again businesses confidence days each ensure get government high history invest issue million once only power right system

6個

already another been begin bring buy century challenges clean come credit debt fact families financial goal helping largest long-term made most over president recession save schools still support through too

5個

address any ask bad before business called chamber commitment cut cuts effort ever family finally global home its job keep last lead like loans many market necessary never off opportunity other programs provide spend united we'll were would

4個

able accountable across act action administration am asked can't could critical cyber cycle cynical difficult does dollars easy force four-year he homes investment Iraq let longer may men moment national nearly old pay percent price private problems promise receive renewable restore say security send sense she should single soon stand States street struggle technology tell think values war well well-being we're without words workers worse yet

3個

ability absolutely after auto bank begins beyond breaks build cause change child comes community compete comprehensive concern confront costs created decade decisions Democrats depend depends didn't dime done efficient enough entrepreneurs everyone federal forward foundation given good govern great half her higher hold hope housing I'm includes industry inherited insurance into I've lay layoffs legislation letter life long lost major makes Medicare move nor often part passed place problem progress protect pushed regulations Republicans resources restart return root said same savings says seen Sen. sent serve simply solar solve speak spirit step struggling students taxpayer thank then today together training understand use wait want watching ways whatever which women won't working young

2個

abuse accounts affordable Afghanistan agenda allow along amid appointed approach areas around arrived assistance assure away balance become begun being believe benefits best billions bless boldly both bought built burden car career Carolina cars children's citizens clear close committed common company complete continue continued corporations countries creating crushing debates decline demand did different diploma directly disease doctors doesn't dollar doors dreams eliminate enterprise era example expand expanded extremism face failure far fastest-growing father few fiscal forces forge found four fraud full fully generation generations getting give God Greensburg had hard haven't healthy held helped highest him his historic households ideas ignore important industries innovation inspiration instead invented kind lasting launched law laying lift line lines list living loan look lose lower massive millions months mortgage much nations neighbor nobody office oil open order ordinary ourselves overseas own pass paychecks payment payments places policies priorities probably process program prosperity provided public quality quick quickly quitters quitting rates real rebuild recover reflects renewed require responsible re-start result revive rewards rules sacrifice safe second secure seek serious service set ship sitting small Social something source South spending spur state stock stopped stores strength strengthen strong stronger submit suffering surely takes teacher teachers term terrorists third though thousands threats three told tomorrow took tough trust trying tuition turn under upon very Vice view wake walk Wall waste wasteful weakened went what's wind worthy yourself


変化とか無視した。1個なんて載せられるかー!

2009-01-31

最新版Ruby-1.9.1を思う存分試してみたい人のためのちょっとしたハック

Ruby-1.9.1リリースおめでとう!

http://www.ruby-lang.org/ja/news/2009/01/30/ruby-1-9-1-released/

まあ、ライブラリがぜんぜん1.9に対応してないから今すぐ1.8から移行するなんてことはできないんだけど、こうやって安定版を出せばライブラリの移行を促せるんだから、ちょうど鶏が先か卵が先かみたいなもんで、リリースすることに意味があるんだよね。

ところで、Ruby といえば、そのままコマンドラインruby を使っている人はあんまりいなくて、普通irb を使うよね。でも、単に ruby自分のホームディレクトリmake しただけだと、当然、irb は使えないんだ。でもこれだけためにインストールするのは嫌。そこでどうするか?

実は最近rubyMakefile には作ったばっかりの rubyライブラリパス込みで動作させるコードが入ってる。これを使って irb を動かしてみるというのが今回のハックのネタというわけ。

masuda@localhost:~/ruby-1.9.1-p0 $ makerubymake はすべて終了したとする)

masuda@localhost:~/ruby-1.9.1-p0 $ cat &gt; test.rb
require 'irb'
require 'irb/completion'
IRB.start
^D
(^D はCtrl-dで入力終了のこと。普通は表示されない)

masuda@localhost:~/ruby-1.9.1-p0 $ make runruby
./miniruby -I./lib -I.ext/common -I./- -r./ext/purelib.rb  ./runruby.rb --extout=.ext  -- ./test.rb
irb(main):001:0&gt; puts "Have a fun!"

これで適切なライブラリパスが指定されて、その場で最新の ruby が使い放題だ。Readlineを使ってるから編集も楽だし、マジおすすめ

というわけで、Ruby開発者さんたちに感謝しつつ、Enjoy Hacking! ピース

2008-11-01

http://anond.hatelabo.jp/20081101031143

pc@ubuntu-vm:~$ mobirc/mobirc

Could not load class (App::Mobirc::Plugin::HTMLFilter::DoCoMoCSS) because : Can't locate XML/LibXML.pm in @INC (@INC contains: /home/pc/mobirc/lib /etc/perl /usr/local/lib/perl/5.8.8 /usr/local/share/perl/5.8.8 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.8 /usr/share/perl/5.8 /usr/local/lib/site_perl .) at /home/pc/mobirc/lib/App/Mobirc/Plugin/HTMLFilter/DoCoMoCSS.pm line 5.

BEGIN failed--compilation aborted at /home/pc/mobirc/lib/App/Mobirc/Plugin/HTMLFilter/DoCoMoCSS.pm line 5.

Compilation failed in require at /usr/local/lib/perl/5.8.8/Class/MOP.pm line 151.

at /usr/local/lib/perl/5.8.8/Class/MOP.pm line 133

Class::MOP::load_first_existing_class('App::Mobirc::Plugin::HTMLFilter::DoCoMoCSS') called at /usr/local/lib/perl/5.8.8/Class/MOP.pm line 157

Class::MOP::load_class('App::Mobirc::Plugin::HTMLFilter::DoCoMoCSS') called at /usr/local/share/perl/5.8.8/MooseX/Plaggerize.pm line 20

App::Mobirc::load_plugin('App::Mobirc=HASH(0x8d7e490)', 'HASH(0x8d7536c)') called at /home/pc/mobirc/lib/App/Mobirc.pm line 44

App::Mobirc::_load_plugins('App::Mobirc=HASH(0x8d7e490)') called at /home/pc/mobirc/lib/App/Mobirc.pm line 35

Class::MOP::Class:::around('CODE(0x8ab5250)', 'App::Mobirc', '/home/pc/mobirc/config.yaml') called at /usr/local/lib/perl/5.8.8/Class/MOP/Method/Wrapped.pm line 129

Class::MOP::Method::Wrapped::__ANON__('App::Mobirc', '/home/pc/mobirc/config.yaml') called at /usr/local/lib/perl/5.8.8/Class/MOP/Method/Wrapped.pm line 89

App::Mobirc::new('App::Mobirc', '/home/pc/mobirc/config.yaml') called at mobirc/mobirc line 36

pc@ubuntu-vm:~$ sudo mobirc/mobirc

Could not load class (App::Mobirc::Plugin::HTMLFilter::DoCoMoCSS) because : Can't locate XML/LibXML.pm in @INC (@INC contains: /home/pc/mobirc/lib /etc/perl /usr/local/lib/perl/5.8.8 /usr/local/share/perl/5.8.8 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.8 /usr/share/perl/5.8 /usr/local/lib/site_perl .) at /home/pc/mobirc/lib/App/Mobirc/Plugin/HTMLFilter/DoCoMoCSS.pm line 5.

BEGIN failed--compilation aborted at /home/pc/mobirc/lib/App/Mobirc/Plugin/HTMLFilter/DoCoMoCSS.pm line 5.

Compilation failed in require at /usr/local/lib/perl/5.8.8/Class/MOP.pm line 151.

at /usr/local/lib/perl/5.8.8/Class/MOP.pm line 133

Class::MOP::load_first_existing_class('App::Mobirc::Plugin::HTMLFilter::DoCoMoCSS') called at /usr/local/lib/perl/5.8.8/Class/MOP.pm line 157

Class::MOP::load_class('App::Mobirc::Plugin::HTMLFilter::DoCoMoCSS') called at /usr/local/share/perl/5.8.8/MooseX/Plaggerize.pm line 20

App::Mobirc::load_plugin('App::Mobirc=HASH(0x8d799e0)', 'HASH(0x8d7089c)') called at /home/pc/mobirc/lib/App/Mobirc.pm line 44

App::Mobirc::_load_plugins('App::Mobirc=HASH(0x8d799e0)') called at /home/pc/mobirc/lib/App/Mobirc.pm line 35

Class::MOP::Class:::around('CODE(0x8ab4390)', 'App::Mobirc', '/home/pc/mobirc/config.yaml') called at /usr/local/lib/perl/5.8.8/Class/MOP/Method/Wrapped.pm line 129

Class::MOP::Method::Wrapped::__ANON__('App::Mobirc', '/home/pc/mobirc/config.yaml') called at /usr/local/lib/perl/5.8.8/Class/MOP/Method/Wrapped.pm line 89

App::Mobirc::new('App::Mobirc', '/home/pc/mobirc/config.yaml') called at mobirc/mobirc line 36

イミフ

2008-10-19

YourFileHostの動画をなんとかする

YourFileHostのCAPTCHA画像をなんとかするの続きの続き。

まぁ、なんというか、一応できたので張ってみる。微妙な出来栄えだけど。

decaptcha.rbと同じディレクトリに置いて適当に動かしてみてください。

どっかにいいRSSがないかなぁ。。。

しかし、CAPTCHA画像を相手にしてた時の方が楽しかったなぁ。。。

あと、添削とかいろいろ歓迎です。

追記

なぜか&が&amp;に置き換えられてるみたいなので、適当に&に読み替えてください。

download.rb

#!/usr/local/bin/ruby
$LOAD_PATH << File::dirname(File::expand_path($0))
require 'rubygems'
require 'rss'
require 'mechanize'
require 'decaptcha'
# require 'ruby-debug'
# require 'pp'

DESTDIR = 'files'
TMPDIR = '/tmp'

class Downloader
  COOKIE_URI = 'http://www.yourfilehost.com/media.php?cat=video&amp;file=%s'
  DOWNLOAD_URI =
    'http://www.yourfilehost.com/downloadlink.php?cat=video&amp;file=%s&amp;adult=1'
  VERBOSE = false
  DEBUG = false
  
  def self.fetch(file, destdir)
    puts "Fetching file: #{file}"

    destfile = destdir + '/' + file
    if test(?e, destfile) then
      puts '  - Already exists. skip'
      puts
      return nil
    end
  
    #
    # Initialize Mech
    #
    mech = WWW::Mechanize.new
    mech.user_agent_alias = 'Windows IE 6'
    mech.max_history = 3
    mech.open_timeout = 15
    mech.read_timeout = 3
  
    #
    # get cookie
    #
    uri = COOKIE_URI % file
    page = mech.get(uri)
  
    #
    # Challenge against CAPTCHA
    #
    uri = DOWNLOAD_URI % file
    page = mech.get(uri)
  
    for i in 0...3
      captcha_path = (page/'img[@height="38"]').attr(:src)
      captcha_uri = 'http://www.yourfilehost.com/%s' % captcha_path
      gif = mech.get_file(captcha_uri)
      open("#{TMPDIR}/captcha.gif", 'w') {|fd| fd.write(gif) }
      mech.back
  
      code = DeCAPTCHA.decode("#{TMPDIR}/captcha.gif")
      File.unlink("#{TMPDIR}/captcha.gif")
      if code.nil? then
        puts '  - CAPTCHA decode failed. retry' if VERBOSE
        next
      end
  
      form = mech.page.forms.first
      form.verify = code
      page = mech.submit(form)
  
      break unless page.links.empty?
    end
  
    if page.links.empty? then
      puts '  - Failed 3 times.  Try another one.'
      puts
      return nil
    end
  
    #
    # download
    #
    puts "  - Downloading: #{page.links.first.href}" if DEBUG
    retry_count = 0
    video = nil
    begin
      video = page.links.first.click
    rescue Timeout::Error => evar
      retry_count += 1
      if retry_count < 5 then
        puts "  * Timedout, retry" if VERBOSE
        retry
      end
      raise evar
    end

    unless video.instance_of?(WWW::Mechanize::File) then
      if DEBUG then
        puts "  - Something wrong while downloading. skip."
        puts
      end
      return nil
    end
    video.save(destfile)

    return destfile
  end
end



#
# main
#
[DESTDIR, TMPDIR].each do |dir|
  if !test(?d, dir) or !test(?w, dir) then
    puts "#{dir}: Directory not exists or cannot write."
    exit
  end
end

files = []


# collect URIs from RSS
RSS_URI = [
  ['http://www.yourfilehost.com/make-rss.php?range=day&amp;af=off',
    lambda {|rss| rss.items.map {|x| x.link }},
  ],
]
RSS_URI.each do |uri, preprocessor|
  rss = RSS::Parser.parse(uri)
  uris = preprocessor.call(rss) #=> Array of URI of YourFileHost

  uris.map {|x| /file=([^&amp;]*)/.match(x).to_a[1] }.each do |file|
    # next if file !~ /\.wmv/  # uncomment it if you need only wmv
    files << file
  end
end
files.uniq!


# download
files.each do |file|
  begin
    Downloader.fetch(file, DESTDIR)
  rescue SystemCallError, Timeout::Error => evar
    puts "  - error (#{evar.to_s}). skip."
    puts
  end
end
__END__

2008-10-18

http://anond.hatelabo.jp/20081018072817 の続きだよ

これでおしまいだよ

elisp

sangels.el
(require 'cl)				; for cl-seq

(defvar sangels-movies-dir nil)
(defvar sangels-player "c:/Program Files/GRETECH/GomPlayer/GOM.exe")
(defvar sangels-sort-by 'sangels-sort-by-rate)
(defvar sangels-rate-file "~/.emacs.d/.sangels/rate")
(defvar sangels-buffer "*sangels*")
(defvar sangels-thumbnail "00_thumbnail.jpg")
(defvar sangels-m3u "00_movies.m3u")
(defface sangels-name '((t (:family "fixed" :weight bold :height 3.0)))
  "")
(defface sangels-rate '((t (:family "fixed" :weight bold :height 1.5)))
  "")
(defvar sangels-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "n" 'next-line)
    (define-key map "p" 'previous-line)
    (define-key map (kbd "RET") 'sangels-select)
    (define-key map (kbd "SPC") 'sangels-select)
    (define-key map "q" 'sangels-quit)
    (define-key map "+" 'sangels-rate-plus)
    (define-key map "-" 'sangels-rate-minus)
    map))
(defvar sangels-mode-hook nil)
(defvar sangels-highlight-overlay nil)
(defvar sangels-rate-alist nil)

(defconst sangels-rate-max 6)

(defun sangels-insert-movies ()
  (save-excursion
    (let* ((inhibit-read-only t)
           (files (remove-if-not
                   (lambda (x)
                     (and (not (member (file-name-nondirectory x) '("." "..")))
                          (file-directory-p x)
                          (member sangels-thumbnail (directory-files x))))
                   (directory-files sangels-movies-dir t)))
           (ids (mapcar 'file-name-nondirectory files)))
      (erase-buffer)
      (setq ids (sangels-sort-ids ids))
      (dolist (id ids)
        (let ((file (expand-file-name id sangels-movies-dir))
              (pos (point)))
          (insert-image-file (expand-file-name sangels-thumbnail file))
          (end-of-line)
          (insert (propertize (format "%-15s " id)
                              'face 'sangels-name))
          (sangels-insert-rate id)
          (insert "\n")
          (put-text-property pos (point) 'sangels-id id))))))

(defun sangels-sort-by-name (a b)
  (string< a b))

(defun sangels-sort-by-rate (a b)
  (or (> (sangels-rate a) (sangels-rate b))
      (sangels-sort-by-name a b)))

(defun sangels-sort-ids (ids)
  (sort ids
        (or sangels-sort-by
            'sangels-sort-by-name)))
(defun sangels-insert-rate (id)
  (let ((rate (sangels-rate id)))
    (insert (propertize (concat
                         (make-string rate ?★)
                         (make-string (- sangels-rate-max rate) ?☆))
                        'sangels-rate t
                        'face 'sangels-rate))))

(defun sangels-current-id ()
  (get-text-property (point) 'sangels-id))

(defun sangels-play-movie (movie)
  (let ((explicit-shell-file-name "cmdproxy")
        (shell-file-name "cmdproxy"))
    (apply
     'call-process-shell-command
     "start" nil "*tmp*" nil
     (mapcar (lambda (x) (concat "\"" x "\""))
             (list sangels-player
                   (unix-to-dos-filename movie))))))

(defun sangels-select ()
  (interactive)
  (let ((id (sangels-current-id)))
    (when id
      (sangels-play-movie (expand-file-name
                           sangels-m3u
                           (expand-file-name id sangels-movies-dir))))))

(defun sangels-quit ()
  (interactive)
  (kill-buffer sangels-buffer))

(defun sangels-rate (id)
  (or (cdr (assoc id sangels-rate-alist)) (/ sangels-rate-max 2)))

(defun sangels-rate-save ()
  (interactive)
  (let ((dir (file-name-directory sangels-rate-file)))
    (unless (file-exists-p dir)
      (make-directory dir t)))
  (with-temp-file sangels-rate-file
    (insert (pp-to-string sangels-rate-alist))))

(defun sangels-rate-load ()
  (interactive)
  (when (file-exists-p sangels-rate-file)
    (with-temp-buffer
      (insert-file-contents sangels-rate-file)
      (goto-char (point-min))
      (setq sangels-rate-alist (read (current-buffer))))))

(defun sangels-rate-plus (&amp;optional n)
  (interactive "p")
  (setq n (or n 1))
  (let* ((id (sangels-current-id))
         (cell (assoc id sangels-rate-alist)))
    (unless cell
      (setq cell (cons id (sangels-rate id)))
      (setq sangels-rate-alist (cons cell sangels-rate-alist)))
    (setcdr cell (+ (cdr cell) n))
    (save-excursion
      (let ((inhibit-read-only t))
        (beginning-of-line)
        (goto-char (next-single-property-change (point) 'sangels-rate))
        (delete-region (point)
                       (next-single-property-change (point) 'sangels-rate))
        (sangels-insert-rate id)))
    (sangels-rate-save)))

(defun sangels-rate-minus (&amp;optional n)
  (interactive "p")
  (setq n (or n -1))
  (sangels-rate-plus (- n)))

(defun sangels-post-command-hook ()
  (save-excursion
    (move-overlay
     sangels-highlight-overlay
     (progn
       (move-beginning-of-line 1)
       (point))
     (progn
       (move-end-of-line 1)
       (forward-line)
       (point))
     (current-buffer))))

(defun sangels-mode ()
  (interactive)
  (kill-all-local-variables)
  (use-local-map sangels-mode-map)
  (setq sangels-highlight-overlay (make-overlay 0 0))
  (overlay-put sangels-highlight-overlay 'face 'highlight)
  (overlay-put sangels-highlight-overlay 'evaporate t)
  (make-local-variable 'post-command-hook)
  (add-hook 'post-command-hook 'sangels-post-command-hook nil t)
  (setq major-mode 'sangels-mode)
  (setq mode-name "Sangels")
  (run-mode-hooks 'sangels-mode-hook)
  (set-buffer-modified-p nil)
  (setq buffer-read-only t))

(defun sangels (&amp;optional arg)
  (interactive "P")
  (when (or arg (not sangels-movies-dir))
    (setq sangels-movies-dir (read-directory-name "movies dir: ")))
  (sangels-rate-load)
  (switch-to-buffer (get-buffer-create sangels-buffer))
  (sangels-insert-movies)
  (sangels-mode))

(provide 'sangels)

real street angels から mechanize を使って動画を取ってきてEmacsで見てみるよ

せっかく書いたから匿名でのせてみるよ

使い方は

  • 動画を取ってきたいよ
    • config.yamlユーザとかを設定するよ
    • ids.txt に取ってきたいIDを書くよ
    • sangels.bat を実行するよ
  • Emacs動画を見たいよ
    • sangels.el を load するよ
    • M-x sangels だよ

必要なものを gem で取ってくるにはこうすればいいよ

  • gem install -r log4r
  • gem install -r -v 0.6 hpricot
  • gem install -r mechanize

長すぎてelispが消えたから続きがあるよ

sangels.bat - 起動用バッチファイル

@echo off
setlocal
set WD=%~dp0
cd /d %WD%

ruby get_movies.rb
ruby get_images.rb
ruby create_m3u.rb

ruby

config.yaml - 設定ファイル
user: ユーザID
password: パスワード
ids_file: ids.txt
done_file: ids_done.txt
movies_dir: movies

log4r_config:
  pre_config:
    global: INFO
  loggers:
    - name: app
      type: Log4r::Logger
      level: INFO
      outputters:
        - STDOUT
        - FILE
  outputters:
    - name: STDOUT
      type: Log4r::StdoutOutputter
      formatter:
        type: Log4r::PatternFormatter
        pattern: "%d [%l] %C - %M"
        date_pattern: "%H:%M:%S"
    - name: FILE
      type: Log4r::FileOutputter 
      filename: "#{LOGDIR}/sangels.log"
      formatter:
        type: Log4r::PatternFormatter
        pattern: "%d [%l] %C - %M"
        date_pattern: "%Y-%m-%d %H:%M:%S"
get_movies.rb
require 'fileutils'
require 'logger'
require 'mechanize'

BASEDIR = File.dirname($0)
require "#{BASEDIR}/util"
require "#{BASEDIR}/sangels"

$config = load_config(BASEDIR)
prepare_logger(BASEDIR)
$log = new_logger("get_movies")
WWW::Mechanize.log = new_logger("mechanize")
WGet.log = $log

class IDFile
  def initialize(file)
    @file = file
    unless File.exist?(@file)
      Fileutils.touch(@file)
    end
  end

  def ids(contains_comment = nil)
    File.open(@file) {|io|
      io.to_a.map {|x|
        x.chomp
      }.select {|x|
        if x.empty?
          nil
        elsif contains_comment
          true
        else
          not /^\s*\#/ =~ x
        end
      }
    }
  end

  def add(id)
    ids = ids(true)
    unless ids.any? {|x| x == id}
      write(ids + [id])
    end
  end

  def delete(id)
    ids = ids(true)
    if ids.any? {|x| x == id}
      write(ids - [id])
    end
  end

  def write(ids)
    File.open(@file, "w") {|io|
      ids.each {|x| io.puts x}
    }
  end
end


$log.info("BEGIN #{$0} ================")
exit_code = 0
begin
  ids_file = IDFile.new($config.ids_file)
  done_file = IDFile.new($config.done_file)
  movies_dir = $config.movies_dir
  wget = WGet.new

  sangels = SAngels.new
  sangels.login($config.user, $config.password)
  ids_file.ids.each {|id|
    begin
      movies = sangels.movies(id)
    rescue SAngels::Movies::InvalidMoviesError
      $log.warn("invalid movie id: #{id}")
      next
    end
    dir = File.expand_path(id, movies_dir)
    movies.each {|link|
      wget.retrieve(link.href, dir)
    }
    expected = movies.movie_links.map{|x| File.basename(x.href)}
    actual = Dir.glob("#{dir}/*").map {|x| File.basename(x)}
    if (expected - actual).empty?
      done_file.add(id)
      ids_file.delete(id)
    end
  }
rescue => e
  $log.error(e)
  exit_code = 1
end

$log.info("END #{$0} (#{exit_code}) ================")
exit exit_code
get_images.rb
require 'fileutils'
require 'logger'
require 'mechanize'
require 'ostruct'

BASEDIR = File.dirname($0)
require "#{BASEDIR}/util"
require "#{BASEDIR}/sangels"

$config = load_config(BASEDIR)
prepare_logger(BASEDIR)
$log = new_logger("get_images")
WWW::Mechanize.log = new_logger("mechanize")
WGet.log = $log

$log.info("BEGIN #{$0} ================")
exit_code = 0
begin
  movies_dir = $config.movies_dir
  sangels = SAngels.new
  sangels.login($config.user, $config.password)
  thumbnails = sangels.thumbnails

  Dir.glob("#{movies_dir}/*").each {|dir|
    next unless File.directory? dir
    id = File.basename(dir)

    url = thumbnails.url(id)
    unless url
      $log.warn("#{id} is not found")
      next
    end
    path = File.expand_path("00_thumbnail#{File.extname(url)}", dir)
    next if File.exist? path

    $log.info("retrieving #{url}")
    thumbnail = thumbnails.get_file(id)
    File.open(path, "wb") {|io| io.write(thumbnail)}
  }
rescue => e
  $log.error(e)
  exit_code = 1
end

$log.info("END #{$0} (#{exit_code}) ================")
exit exit_code
create_m3u.rb
BASEDIR = File.dirname($0)
require "#{BASEDIR}/util"

$config = load_config(BASEDIR)
movies_dir = $config.movies_dir
Dir.glob("#{movies_dir}/*") {|dir|
  next unless File.directory? dir
  name = File.basename(dir)
  files = Dir.glob("#{dir}/*.wmv").sort

  File.open("#{movies_dir}/#{name}.m3u", "w") {|io|
    files.each {|file|
      io.puts "#{name}/#{File.basename(file)}"
    }
  }

  File.open("#{dir}/00_movies.m3u", "w") {|io|
    files.each {|file|
      io.puts "#{File.basename(file)}"
    }
  }
}
sangels.rb
require 'mechanize'
require 'hpricot'

BASEDIR = File.dirname($0)
require "#{BASEDIR}/util"

class SAngels
  HOST = "real2.s-angels.com"
  LOGIN_URL = "http://#{HOST}/member/"
  INFO_URL = "http://#{HOST}/teigaku/item.php"
  THUMBNAILS_URL = "http://#{HOST}/teigaku/"
  THUMBNAIL_URL = "http://#{HOST}/images/default/thumb/"

  def initialize()
    @agent = WWW::Mechanize.new
  end

  def login(user, password)
    login_form = @agent.get(LOGIN_URL).forms.find {|form|
      form.fields.any? {|field| field.name == "frmLoginid"}
    }
    login_form.frmLoginid = user
    login_form.frmPw = password
    @agent.submit(login_form)
  end

  def movies(id, no_validate = nil)
    Movies.new(@agent, id, !no_validate)
  end

  def thumbnails
    Thumbnails.new(@agent)
  end

  class Thumbnails
    def initialize(agent)
      @agent = agent
      doc = Hpricot(@agent.get_file(THUMBNAILS_URL))
      elems = doc.search("div[@class=realthum]/a")
      @links = Hash(
        elems.map {|elem|
          href = elem["href"]
          id = $1 if /ID=(.+)/ =~ href
          url = elem.search("img")[0]["src"]
          [id, url]
        })
    end

    def get_file(id)
      @agent.get_file(url(id))
    end

    def url(id)
      @links[id]
    end

    def exist?(id)
      url(id)
    end
  end

  class Movies
    class InvalidMoviesError < StandardError
    end

    def initialize(agent, id, no_validate)
      @agent = agent
      @id = id
      if !no_validate &amp;&amp; !valid?
        raise InvalidMoviesError
      end
    end

    def info_page_url
      "#{INFO_URL}?ID=#{@id}"
    end

    def info_page
      @agent.get(info_page_url)
    end

    def movies_page
      @agent.click(info_page.links.find {|link| /P=10/ =~ link.href})
    end

    def movie_links
      movies_page.links.select {|link|
        /wmv$/ =~ link.href
      }.sort {|a, b|
        File.basename(a.href) <=> File.basename(b.href)
      }
    end

    def valid?
      info_page.uri.to_s == info_page_url
    end

    def each(&amp;block)
      orig_links = movie_links
      orig_links.each {|orig_link|
        link = movie_links.find {|l| File.basename(l.href) == File.basename(orig_link.href)}
        block.call(link)
      }
    end
  end
end
util.rb
require 'log4r'
require 'log4r/yamlconfigurator'
require 'singleton'
require 'fileutils'
require 'ostruct'

def Hash(a)
  Hash[*a.flatten]
end

def load_config(basedir)
  OpenStruct.new(File.open("#{basedir}/config.yaml") {|io| YAML.load(io)})
end

def new_logger(name)
  Log4r::Logger.new("app::#{name}")
end

def prepare_logger(basedir, logdir = nil)
  logdir ||= basedir
  Log4r::YamlConfigurator["LOGDIR"] = logdir
  Log4r::YamlConfigurator.load_yaml_file("#{basedir}/config.yaml")
end

class NullObject
  include Singleton
  def method_missing(message, *arg)
    NullObject.singleton
  end
end

class WGet
  class << self
    attr_accessor :log
    def initialize
      super
      @log = NullObject.singleton
    end
  end
  
  def log
    self.class.log
  end

  def retrieve(url, dir)
    FileUtils.mkdir_p(dir)
    file = File.expand_path(File.basename(url), dir)
    if File.exist?(file)
      log.info("already retrieved #{url}")
      return true
    end

    tmp = "#{file}.part"
    log.info("retrieving #{url}")
    ret = system("wget", "-c", "-O", tmp, url)
    if ret
      log.info("retrieving succeeded #{url}")
      File.rename(tmp, file)
    else
      if $? == 0x020000 # Ctrl-C
        exit($?)
      else
        log.error("retrieving failure #{url} (#{$?})")
      end
    end
    return ret
  end
end

2008-10-17

http://anond.hatelabo.jp/20081006220009

YourFileHostのCAPTCHA画像をなんとかするの続き。

その後、適当にいじったら、手元環境で1枚あたり25秒くらい→だいたい2.5秒くらいで判別できるようになった。このくらいなら使えるかな。

速度向上に一番効いたのは、Token#importで画像を比較しているところの修正。他は細かい手直し。

使い方は前のやつと変わってません。

あと、テストに100枚くらいCAPTCHA画像食わせてみたけど、とりあえず全部正しく判定できた。

動作確認用のスクリプト (run.rb)

カレントディレクトリ以下にある*.gifCAPTCHA画像ファイル適当に判別するスクリプト。動作確認用にどうぞ。

後述のdecaptcha.rbと同じディレクトリ適当に置いてchmod +xしてね。

#!/usr/local/bin/ruby
$LOAD_PATH << File::dirname(File::expand_path($0))
require 'decaptcha'

STDOUT.sync = true

Dir.glob('*.gif').sort.each do |file|
  correct = File::basename(file, '.*')
  puts "Processing file: #{file}"
  start_time = Time.now
  ret = DeCAPTCHA.decode(file)
  elapsed = Time.now - start_time

  puts "  Result: #{ret} (=> #{(correct == ret) ? "Ok" : "Fail"})"
  puts "  Elapsed time: #{elapsed}"
  puts
end

コード (decaptcha.rb)

#!/usr/local/bin/ruby
require 'rubygems'
require 'gd2'
require 'pp'

#
#= CAPTCHA画像解析モジュール
# CAPTCHA画像ファイルを食わすとあら不思議Stringが出てくるよ。
# YourFileHostのやつに対応。
#
#== Usage
# decoded_str = DeCAPTCHA.decode("some_captcha_image.gif")  #=> String
# 失敗したらnilが返る。
#
module DeCAPTCHA
  DEBUG = false

  #=== CAPTCHA画像デコード
  # file::    画像ファイル名のパス
  # method::  未指定でよい。男は細かい事を気にするな。
  # returns:: CAPTCHA画像解析結果(String) or nil (デコード失敗時)
  def self.decode(file, method = DeCAPTCHA::Site::YourFileHost)
    return method.new(file).decode
  end



  #= CAPTCHA画像デコードクラス
  # このクラスサブクラスはimport, tokenize, stream_parseメソッドの
  # 実装を含む必要がある。
  class Site
    def initialize(file = nil)
      @pix = nil
      self.import(file) unless file.nil?
    end
    def decode
      return stream_parse(tokenize())
    end
  end

  #= YourFileHostのCAPTCHA画像を解析するクラス
  class Site::YourFileHost < Site
    def import(file)
      @pix = PixelMatrix.new.import(file)
      return self
    end

    # importしたイメージ(PixelMatrix)から、文字と思わしきパターンを
    # 抽出して上下マージンを切り取ってArrayにして返す。
    # returns:: Array of PixelMatrix
    def tokenize
      ret = []
      state = :initial
      for x in 0...@pix.width
        case state
        when :initial
          if !@pix.vline_blank?(x) then
            state = :tokenize
            pixel = PixelMatrix.new(0, 0, true)
            ret << pixel
            redo
          end
        when :tokenize
          if @pix.vline_blank?(x) then
            state = :initial
            next
          end
          x0 = pixel.width
          for y in 0...@pix.height
            pixel[x0, y] = @pix[x, y]
          end
        else
          raise 'NOTREACHED'
        end
      end

      ret.map! {|token| Token.new.import(token.cutoff_vmargin!) }
    end


    # PixelMatrixのArrayを受け取り、数字を判別。
    # tokens:: Array of PixelMatrix
    # returns:: String (判別結果)
    def stream_parse(tokens)
      rs = tokens.map {|x| x.guess.to_s }.join('')
      if rs.length != 4 then
        rs = nil
        if DEBUG then
          puts '- guess failed. dumping guess result of each token:'
          tokens.each_index do |i|
            print "##{i}:#{tokens[i].guess} "
            pp tokens[i].candidate
          end
          puts
        end
      end
      return rs
    end

    class Token
      @@digits = nil
      attr_accessor :candidate

      def initialize
        if @@digits.nil? then
          # 文字画像サンプルを作っておく
          @@digits = DIGITS_ASSOC.map {|digit|
            PixelMatrix.new(0, 0, true).import_array(digit) }
        end

        @candidate = Hash.new
      end

      # PixelMatrixを受け取り、文字画像サンプルと比較して
      # 一致率を計算しておく。
      # pixel:: PixelMatrix
      # returns:: self
      def import(pixel)
        @@digits.each_index do |i|
          digit = @@digits[i]

          if (digit.width - pixel.width).abs   > 4 or
             (digit.height - pixel.height).abs > 4 then
            @candidate[i] = -1  # サイズが違いすぎな場合、一致させない
            next
          end

          correct_bits = 0
          enlarged_width  = [digit.width,  pixel.width ].max
          enlarged_height = [digit.height, pixel.height].max
          for y in 0...enlarged_height
            dy = (y.to_f / digit.height * enlarged_height).to_i
            py = (y.to_f / pixel.height * enlarged_height).to_i
            for x in 0...enlarged_width
              dx = (x.to_f / digit.width * enlarged_width).to_i
              px = (x.to_f / pixel.width * enlarged_width).to_i
              correct_bits += 1 if digit[dx, dy] == pixel[px, py]
            end
          end

          @candidate[i] = correct_bits * 100 /
                          (enlarged_width * enlarged_height)
        end

        return self
      end

      # importのときの比較結果をもとに文字を推測
      # returns:: Fixnum or nil(失敗時)
      def guess
        digit, ratio = @candidate.sort {|a, b| a.last <=> b.last}.last
        digit = nil if ratio < 0 or ratio < 65
        return digit
      end
    end
  end


  #= 画素マトリックスクラス
  # 画像ファイルを食わせると、各ピクセル(画素)を2値(black(1) or white(0))に
  # 変換して、内部で保持する。
  # 以後、Matrixクラスのような感じで個々の画素アクセスできる。
  class PixelMatrix
    BLACK = 1
    WHITE = 0

    attr_accessor :width
    attr_accessor :height

    # width::  幅
    # height:: 高さ
    # is_flexible:: 自動的に伸張するか
    def initialize(width = 0, height = 0, is_flexible = false)
      @matrix = Hash.new {|hash, key| hash[key] = Hash.new(WHITE)}
      @width, @height, @flexible = width, height, is_flexible
    end

    # file:: 画像ファイル名のパス
    # brightness_threshold:: 画素を黒とみなす閾値 (0 - 255, default: 0x40)
    # returns:: self (DeCAPTCHA::PixelMatrix)
    def import(file, brightness_threshold = 0x40)
      gd = GD2::Image.import(file)
      @width, @height = gd.width, gd.height

      self.each_with_axis do |x, y|
        color = gd[x, y]
        greyscale = (color.red + color.green + color.blue) / 3
        self[x, y] = (greyscale > brightness_threshold) ?
          WHITE :
          BLACK
      end
      return self
    end

    def import_array(array)
      array.each_with_index do |str, y|
        str.split('').each_with_index do |c, x|
          self[x, y] = c.to_i
        end
      end
      return self
    end

    # PixelMatrixを画像ファイルとしてexport
    # file:: 新たに作る画像ファイル名のパス
    def export(file)
      gd = GD2::Image::IndexedColor.new(@width, @height)
      gd.palette << GD2::Color::WHITE
      gd.palette << GD2::Color::BLACK
      self.each_with_axis do |x, y|
        gd[x, y] = {
          WHITE => GD2::Color::WHITE,
          BLACK => GD2::Color::BLACK,
        }[self[x, y]]
      end
      gd.export(file)
      return self
    end

    # 指定された位置の画素を返す。
    # returns:: PixelMatrix::BLACK(1) or WHITE(0)
    def [](x, y)
      if !@flexible and !in_range?(x, y) then
        raise RangeError
      end
      return WHITE if !@matrix.has_key?(y)  # XXX: for optimize
      return @matrix[y][x]
    end

    # 画素に値を設定。
    # returns:: PixelMatrix::BLACK(1) or WHITE(0)
    def []=(x, y, val)
      unless in_range?(x, y) then
        raise RangeError unless @flexible
        @width  = (x >= @width)  ? x + 1 : @width
        @height = (y >= @height) ? y + 1 : @height
      end

      @matrix[y][x] = val
    end

    def in_range?(x, y)
      ((0...@width) === x and (0...@height) === y)
    end

    # 指定された軸をもとに画素を走査し、Arrayに変換。
    # 例えば、to_a(:vertical, 10) とすると、x == 10 な列を取り出して
    # Arrayにして返す。
    #
    # axis:: 軸を指定 (:vertical または :horizontal)
    # pos:: 位置を指定。_axis_で指定した軸と直交する軸における位置を指定。
    def to_a(axis, pos)
      {:vertical => lambda {
        (0...@height).map {|y| self[pos, y]}
       },
       :horizontal => lambda {
        (0...@width).map  {|x| self[x, pos]}
       },
      }[axis].call
    end

    # returns:: Array
    def hline(y)
      self.to_a(:horizontal, y)
    end

    # returns:: Array
    def vline(x)
      self.to_a(:vertical, x)
    end

    # X軸方向に画素を走査。
    # y:: どの位置で走査するか
    # returns:: 指定された軸の上にドットが存在: false, 無い: true
    def hline_blank?(y)
      return true if @matrix.has_key?(y) == false # XXX: for optimize
      for x in 0...@width
        return false if self[x, y] == BLACK
      end
      return true
    end

    # Y軸方向に画素を走査。
    # x:: どの位置で走査するか
    # returns:: 指定された軸の上にドットが存在: false, 無い: true
    def vline_blank?(x)
      for y in 0...@height
        return false if self[x, y] == BLACK
      end
      return true
    end

    # 上下のマージン削除した新しいPixelMatrixを返す。
    # returns:: PixelMatrix
    def cutoff_vmargin
      pixel = PixelMatrix.new(0, 0, true)
      head = 0
      tail = self.height - 1

      head.upto(tail) do |y|
        if !self.hline_blank?(y) then
          head = y
          break
        end
      end
      tail.downto(head) do |y|
        if !self.hline_blank?(y) then
          tail = y
          break
        end
      end

      head.upto(tail) do |y|
        0.upto(self.width - 1) do |x|
          pixel[x, y - head] = self[x, y]
        end
      end

      return pixel
    end
    
    # 自身の上下のマージン削除する。cutoff_vmarginの破壊版。
    # 速度稼ぎのために直接@matrixを触ったり、すこしずるをしている。
    # 効率は、ほんの少しだけ速くなったような誤差の範囲のような感じ。
    # returns:: self (PixelMatrix)
    def cutoff_vmargin!
      head = 0
      tail = self.height - 1

      head.upto(tail) do |y|
        if !self.hline_blank?(y) then
          head = y
          break
        end
      end
      tail.downto(head) do |y|
        if !self.hline_blank?(y) then
          tail = y
          break
        end
        @matrix.delete(y) if @matrix.has_key?(y)  # XXX
      end

      if head > 0 then
        head.upto(tail) do |y|
          next if !@matrix.has_key?(y)            # XXX
          @matrix[y - head] = @matrix.delete(y)   # XXX
        end
      end
      self.height = tail - head + 1

      return self
    end
    
    def each_with_axis
      for x in 0...@width
        for y in 0...@height
          yield(x, y)
        end
      end
    end
  end
end

class DeCAPTCHA::Site::YourFileHost::Token
DIGITS_ASSOC = [
  # 0
  ["00000000011111110000000000",
   "00000001111111111110000000",
   "00000011111000001111000000",
   "00001111111100010011110000",
   "00011111100000110000111000",
   "00111111000000100001111000",
   "00111111110001000001111100",
   "01111111000010000011111110",
   "01111100000110000111111110",
   "01111111000100000111111110",
   "11111100001000001111111111",
   "11100000001000011111111111",
   "11111000010000011111111111",
   "11111000110000111111111111",
   "10000000100001111111111111",
   "01100001000001111111111110",
   "01100010000011111111111110",
   "01100010000111111111111110",
   "00111100000111111111111100",
   "00011100001111111111111000",
   "00001100011111111111111000",
   "00001111111111111111100000",
   "00000011111111111111000000",
   "00000000111111111100000000"],
  # 1
  ["00001",
   "00111",
   "11111",
   "11111",
   "10001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001",
   "00001"],
  # 2
  ["0000011111111110000000",
   "0001111111111111000000",
   "0011110000000010000000",
   "0110000000000100000000",
   "1100000000001100011110",
   "1000000000001000001111",
   "1000000000010000000111",
   "1000000000110001111111",
   "1000000000100001111111",
   "1000000001000000011111",
   "0100000011000001111111",
   "0011000010000111111110",
   "0011000110000001111110",
   "0000001100000111111100",
   "0000001000011111110000",
   "0000011000000111100000",
   "0000110000000000000000",
   "0000111111111111111111",
   "0001111111111111111110",
   "0011111111111111111100",
   "0011111111111111111100",
   "0111111111111111111000",
   "0111111111111111110000"],
  # 3
  ["000000011111111110000000",
   "000001111111111111100000",
   "000011100000011111111000",
   "000111000000111111111000",
   "000110000001111111111100",
   "000100000001111111111100",
   "000100000011111111111100",
   "000110000111111111111000",
   "000010000111111111111000",
   "000000001111111111100000",
   "000000011111111111000000",
   "000000011111111110000000",
   "000000000000001100000000",
   "000000000000011100011100",
   "000000000000111000111110",
   "000000000000110000001110",
   "000000000001110001111111",
   "110000000011100011111111",
   "111000000111000000111110",
   "011100000110001111111100",
   "001111001110000111111000",
   "000011111100000011100000",
   "000000011000111000000000"],
  # 4
  ["0000000000011",
   "0000000000011",
   "0000000000111",
   "0000000001111",
   "0000000001111",
   "0000000011111",
   "0000000111111",
   "0000000110111",
   "0000001100111",
   "0000011100111",
   "0000011000111",
   "0000110000111",
   "0001110000111",
   "0001100000111",
   "0011000000111",
   "0111000000111",
   "0111111111111",
   "1111111111111",
   "0000000000111",
   "0000000000111",
   "0000000000111",
   "0000000000111",
   "0000000000111"],
  # 5
  ["000000001111111111111110",
   "000000011111111111111100",
   "000000111111111111111100",
   "000000111111111111111000",
   "000001111111111111110000",
   "000011100000000000000000",
   "000011011111111110000000",
   "000111111111111111000000",
   "001111100000000111000000",
   "001110000000000110001100",
   "000000000000000100011110",
   "000000000000001100000110",
   "000000000000011000011111",
   "000000000000011001111111",
   "000000000000110000011111",
   "000000000001100000111111",
   "110000000001100111111110",
   "011000000011000001111110",
   "011100000110000011111100",
   "001111000110011111111000",
   "000111111100001111110000",
   "000001111100000011000000",
   "000000001000011000000000"],
  # 6
  ["000000000000000110000000",
   "000000010001111111111000",
   "000001110000011100111100",
   "000011100000011000001000",
   "000111000011111000000000",
   "001111000001110000000000",
   "001110000000111100000000",
   "011110001111111111100000",
   "011100000111000011110000",
   "011000000010000001111000",
   "011000011110000011111100",
   "010000111100000111111110",
   "100000001000000111111110",
   "100001111000001111111111",
   "000011110000011111111111",
   "000000100000011111111111",
   "000011100000111111111111",
   "001111000001111111111110",
   "000010000001111111111110",
   "001110000011111111111100",
   "000111000111111111111000",
   "000011111111111111110000",
   "000000111111111111000000",
   "000000000111111000000000"],
  # 7
  ["0011111111111110001111",
   "0011111111111100000011",
   "0111111111111000000110",
   "1111111111111000111100",
   "1111111111110000001100",
   "0000000000000000011000",
   "0000000000000011111000",
   "0000000011000000110000",
   "0000001110000011100000",
   "0000011110001111100000",
   "0000111100000011000000",
   "0001111000000110000000",
   "0001111000111110000000",
   "0011110000001100000000",
   "0011110000001000000000",
   "0011100011111000000000",
   "0011000001110000000000",
   "0001000000110000000000",
   "0000000111100000000000",
   "0000000111000000000000",
   "0000000011000000000000",
   "0000001110000000000000",
   "0000001100000000000000"],
  # 8
  ["0000000111111111110000000",
   "0000011111111111111100000",
   "0000001111100000011110000",
   "0000000001110000000111000",
   "0011000011111000000011100",
   "0011100001111100000011100",
   "0011110000001110000011100",
   "0001111000011111000111000",
   "0000111100000111101110000",
   "0000011110000001111100000",
   "0000001111000011111110000",
   "0000111111110000011111000",
   "0011110011111000001111100",
   "0111100001111100011111100",
   "0111000000111110000111110",
   "1111000000011111000000111",
   "1111000000001111100011111",
   "1111000000000011111000010",
   "0111100000000001111100000",
   "0011110000000000111110000",
   "0001111110000001111110000",
   "0000011111111111111000000",
   "0000000011111110000000000"],
  # 9
  ["000000111111111110000000",
   "000001111111111111100000",
   "000111111111111001111000",
   "001111111111110000010000",
   "011111111111110000010000",
   "011111111111100000100000",
   "011111111111000001100000",
   "111111111110000001000111",
   "111111111110000010000001",
   "111111111100000110000111",
   "011111111000000100011111",
   "011111111000001000001111",
   "001111110000011000011111",
   "001111100000010001111110",
   "000111110000100000011110",
   "000001111111100000111110",
   "000000011111000111111100",
   "000000000010000001111100",
   "000000000110000001111000",
   "000100001100011111110000",
   "000111001100001111100000",
   "000011111000001111000000",
   "000000010000110000000000"],
]
end
__END__

2008-10-06

YourFileHostのCAPTCHA画像をなんとかする

破ろうぜ!CAPTCHA画像♪(うっうーん)

そんなわけで、みんな大好きなYourFileHostだけども、最近みてみたら、なんかCAPTCHA認証がついているわけじゃないですか。

でもこれってさーCAPTCHAといっても見るからに危ういというか、見れば見るほど簡単に破れてしまいそうな気がして、どうにもむずがゆいアンニュイな気分になってしまうわけです。

そんで、このたび適当にいじってみたところ、それなりに推測できるコードが書けたので、ここに張ってみますね。

やってることは単純で、こんな感じ

  1. CAPTCHA画像ファイルをよみこむ
  2. ピクセルごとにグレイスケールにして、それで2値にする
  3. この時点でじゃまな背景が消えて、文字の形がわかってしまうわけです
  4. 縦軸に点がないところで切って、文字ごとにばらばらにする
  5. 文字画像のサンプルと比較して、一致率が高いものを採用

使い方は、こんな感じで

require 'decaptcha.rb'
captcha_string = DeCAPTCHA.decode(filename)
if !captcha_string.nil? then
  # 判別成功時の処理
else
  # 判別失敗時の処理。失敗することもあるのでよしなに。
end

あ、Ruby/GD2入れといてね。sudo gem install gd2とかで入ります。多分。

判別率はそこそこ良い感じになったんだけども、富豪プログラミングがたたってか、いかんせん遅いです。

手元だと1枚判別するのに20秒くらいかかることもある。

そんなわけで誰かチューニングしてくれるとうれしい。

追記

実行速度を上げた改良版はこっちにうpしました。以下のコードは参考程度に。

コード (decaptcha.rb)

#!/usr/local/bin/ruby
require 'rubygems'
require 'gd2'
require 'pp'

#
#= CAPTCHA画像解析モジュール
# CAPTCHA画像ファイルを食わすとあら不思議Stringが出てくるよ。
# YourFileHostのやつに対応。
#
#== Usage
# decoded_str = DeCAPTCHA.decode("some_captcha_image.gif")  #=> String
# 失敗したらnilが返る。
#
module DeCAPTCHA
  DEBUG = false

  #=== CAPTCHA画像デコード
  # file::    画像ファイル名のパス
  # method::  未指定でよい。男は細かい事を気にするな。
  # returns:: CAPTCHA画像解析結果(String) or nil (デコード失敗時)
  def self.decode(file, method = DeCAPTCHA::Site::YourFileHost)
    return method.new(file).decode
  end



  #= CAPTCHA画像デコードクラス
  # このクラスサブクラスはimport, tokenize, stream_parseメソッドの
  # 実装を含む必要がある。
  class Site
    def initialize(file = nil)
      @pix = nil
      self.import(file) unless file.nil?
    end
    def decode
      return stream_parse(tokenize())
    end
  end

  #= YourFileHostのCAPTCHA画像を解析するクラス
  class Site::YourFileHost < Site
    def import(file)
      @pix = PixelMatrix.new.import(file)
      return self
    end

    # importしたイメージ(PixelMatrix)から、文字と思わしきパターンを
    # 抽出して上下マージンを切り取ってArrayにして返す。
    # returns:: Array of PixelMatrix
    def tokenize
      getter, tokenizer = lambda {|queue|
        [ lambda { return queue },
          lambda {|x, pixel|
            if pixel.nil? then
              x, pixel = tokenizer.call(x, PixelMatrix.new(0, 0, true))
              queue << pixel
              return x
            end
  
            if !@pix.in_range?(x, 0) or @pix.vline_blank?(x) then
              return [x, pixel]
            end
            x0 = pixel.width
            @pix.vline(x).inject(0) do |y, color|
              pixel[x0, y] = color
              y + 1
            end
            return tokenizer.call(x + 1, pixel)
          }
        ]
      }.call([])

      x = 0
      while (x < @pix.width)
        x = @pix.vline_blank?(x) ?
          x + 1 :
          tokenizer.call(x, nil)
      end

      getter.call.map do |token|
        # Token.new.import(token.cutoff_vmargin.shrink)
        Token.new.import(token.cutoff_vmargin)
      end
    end

    # PixelMatrixのArrayを受け取り、数字を判別。
    # tokens:: Array of PixelMatrix
    # returns:: String (判別結果)
    def stream_parse(tokens)
      r = []
      tokens.inject(nil) do |prev, cur|
        r << cur.guess
      end

      rs = r.map {|x| x.to_s}.join('')
      if rs.length != 4 then
        if DEBUG then
          puts '- guess failed. dumping guess result of each token:'
          tokens.each_index do |i|
            print "##{i}: "
            pp tokens[i].candidate
          end
          puts
        end
        return nil
      end
      return rs
    end

    class Token
      @@digits = nil
      attr_accessor :candidate

      def initialize
        if @@digits.nil? then
          # 文字画像サンプルを作っておく
          @@digits = DIGITS_ASSOC.map {|assoc|
            PixelMatrix.new(0, 0, true).import_assoc(assoc) }
        end

        @candidate = Hash.new
      end

      # PixelMatrixを受け取り、文字画像サンプルと比較して
      # 一致率を計算しておく。
      # pixel:: PixelMatrix
      # returns:: self
      def import(pixel)
        gcd = lambda {|a, b|
          a, b = [b, a] if a < b
          return a if b == 0
          r = a % b
          return gcd.call(b, r)
        }
        lcm = lambda {|a, b| a * b / gcd.call(a, b) }
        mul_to_lcm = lambda {|a, b|
          g = gcd.call(a, b)
          [b / g, a / g]
        }

        0.upto(@@digits.size - 1) do |i|
          if (@@digits[i].width - pixel.width).abs   > 4 or
             (@@digits[i].height - pixel.height).abs > 4 then
            @candidate[i] = -1  # 比較対象とサイズが違いすぎ
            next
          end

          mul_ax, mul_bx = mul_to_lcm.call(@@digits[i].width,  pixel.width)
          mul_ay, mul_by = mul_to_lcm.call(@@digits[i].height, pixel.height)
          enlarged_width  = @@digits[i].width  * mul_ax
          enlarged_height = @@digits[i].height * mul_ay

          # 文字画像サンプルと比較対象画像のサイズをあわせる
          # 幅・高さをそれぞれ適当整数倍して、最小公倍数に合わせて比較
          # (めんどうだから)
          correct_bits = 0
          (0...enlarged_width).each do |x|
            (0...enlarged_height).each do |y|
              if @@digits[i][x/mul_ax, y/mul_ay] ==
                 pixel[x/mul_bx, y/mul_by] then
                correct_bits += 1
              end
            end
          end
          
          @candidate[i] = correct_bits * 100 /
                          (enlarged_width * enlarged_height)
        end

        return self
      end

      # importのときの比較結果をもとに文字を推測
      # returns:: Fixnum or nil(失敗時)
      def guess
        digit, ratio = @candidate.sort {|a, b| a.last <=> b.last}.last
        digit = nil if ratio < 0 or ratio < 80
        return digit
      end
    end
  end


  #= 画素マトリックスクラス
  # 画像ファイルを食わせると、各ピクセル(画素)を2値(black(1) or white(0))に
  # 変換して、内部で保持する。
  # 以後、Matrixクラスのような感じで個々の画素アクセスできる。
  class PixelMatrix
    BLACK = 1
    WHITE = 0

    attr_accessor :width
    attr_accessor :height

    # width::  幅
    # height:: 高さ
    # is_flexible:: 自動的に伸張するか
    def initialize(width = 0, height = 0, is_flexible = false)
      @matrix = Hash.new {|hash, key| hash[key] = Hash.new(WHITE)}
      @width, @height, @flexible = width, height, is_flexible
    end

    # file:: 画像ファイル名のパス
    # brightness_threshold:: 画素を黒とみなす閾値 (0 - 255, default: 0x40)
    # returns:: self (DeCAPTCHA::PixelMatrix)
    def import(file, brightness_threshold = 0x40)
      gd = GD2::Image.import(file)
      @width, @height = gd.width, gd.height

      self.each_with_axis do |x, y|
        color = gd[x, y]
        greyscale = (color.red + color.green + color.blue) / 3
        self[x, y] = (greyscale > brightness_threshold) ?
          WHITE :
          BLACK
      end
      return self
    end

    # reverse function of to_assoc
    def import_assoc(assoc)
      assoc.inject(0) do |y, columns|
        columns.inject(0) do |x, color|
          self[x, y] = color
          x + 1
        end
        y + 1
      end
      return self
    end

    # PixelMatrixを画像ファイルとしてexport
    # file:: 新たに作る画像ファイル名のパス
    def export(file)
      gd = GD2::Image::IndexedColor.new(@width, @height)
      gd.palette << GD2::Color::WHITE
      gd.palette << GD2::Color::BLACK
      self.each_with_axis do |x, y|
        gd[x, y] = {
          WHITE => GD2::Color::WHITE,
          BLACK => GD2::Color::BLACK,
        }[self[x, y]]
      end
      gd.export(file)
      return self
    end

    def to_assoc
      (0...@height).map do |y|
        (0...@width).map do |x|
          self[x, y]
        end
      end
    end

    # 指定された位置の画素を返す。
    # returns:: PixelMatrix::BLACK(1) or WHITE(0)
    def [](x, y)
      if !@flexible and !in_range?(x, y) then
        raise RangeError
      end
      return @matrix[y][x]
    end

    # 画素に値を設定。
    # returns:: PixelMatrix::BLACK(1) or WHITE(0)
    def []=(x, y, val)
      unless in_range?(x, y) then
        raise RangeError unless @flexible
        @width  = (x >= @width)  ? x + 1 : @width
        @height = (y >= @height) ? y + 1 : @height
      end

      @matrix[y][x] = val
    end

    def in_range?(x, y)
      ((0...@width) === x and (0...@height) === y)
    end

    # 指定された軸をもとに画素を走査し、Arrayに変換。
    # 例えば、to_a(:vertical, 10) とすると、x == 10 な列を取り出して
    # Arrayにして返す。
    #
    # axis:: 軸を指定 (:vertical または :horizontal)
    # pos:: 位置を指定。_axis_で指定した軸と直交する軸における位置を指定。
    def to_a(axis, pos)
      {:vertical => lambda {
        (0...@height).map {|y| self[pos, y]}
       },
       :horizontal => lambda {
        (0...@width).map  {|x| self[x, pos]}
       },
      }[axis].call
    end

    # returns:: Array
    def hline(y)
      self.to_a(:horizontal, y)
    end

    # returns:: Array
    def vline(x)
      self.to_a(:vertical, x)
    end

    # X軸方向に画素を走査。
    # y:: どの位置で走査するか
    # returns:: 指定された軸の上にドットが存在: false, 無い: true
    def hline_blank?(y)
      hline(y).find {|color| color == BLACK }.nil? ?
        true : false
    end

    # Y軸方向に画素を走査。
    # x:: どの位置で走査するか
    # returns:: 指定された軸の上にドットが存在: false, 無い: true
    def vline_blank?(x)
      vline(x).find {|color| color == BLACK }.nil? ?
        true : false
    end

    # 上下のマージン削除した新しいPixelMatrixを返す。
    # returns:: PixelMatrix
    def cutoff_vmargin
      pixel = PixelMatrix.new(0, 0, true)
      head = 0.upto(self.height - 1) do |y|
        break(y) if !self.hline_blank?(y)
      end
      tail = (self.height - 1).downto(0) do |y|
        break(y) if !self.hline_blank?(y)
      end

      head.upto(tail) do |y|
        y0 = pixel.height
        self.hline(y).inject(0) do |x, color|
          pixel[x, y0] = color
          x + 1
        end
      end

      return pixel
    end
    
    def each_with_axis
      (0...@width).each {|x|
        (0...@height).each {|y|
          yield(x, y)}}
    end
  end
end

class DeCAPTCHA::Site::YourFileHost::Token
DIGITS_ASSOC = [
# 0
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
],
# 1
[[0, 0, 0, 0, 1],
 [0, 0, 1, 1, 1],
 [1, 1, 1, 1, 1],
 [1, 1, 1, 1, 1],
 [1, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1],
 [0, 0, 0, 0, 1]],
# 2
[[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
 [0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
 [0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]],
# 3
[[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
 [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
 [1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0],
 [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
# 4
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1],
 [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1],
 [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1],
 [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1],
 [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1],
 [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1],
 [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1]],
# 5
[[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
 [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
# 6
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
 [0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
# 7
[[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1],
 [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
# 8
[[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
 [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
 [0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0],
 [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
 [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1],
 [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0],
 [0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
# 9
[[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1],
 [0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1],
 [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
 [0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
]
end


__END__
Dir.glob('*.gif').each do |file|
  puts "Processing file: #{file}"
  p DeCAPTCHA.decode(file)
end

__END__

2008-07-31

[][][][]無題

百万回繰り返された例の件について書いてみるよ。あ、タイトルは必要ないよね?このタグだけ見ればわかるものw

Perl

  • sigil 汚い、my our local 汚い。
  • ->が汚い、ドットにしてよ。Perl6ではドットになるんだって?やったぁ。
  • とにかくコードを見るだけでげんなりする。
  • クラス機構が後付けなのがめんどくせー。Exporter使うのだるい
  • とにかく文法がアレすぎる。あ、でも後置修飾子はおきにいり。
  • でもはえー、ちょうはえー。
  • ライブラリ超使える。もうなんでもできる。

総評:肉は腐りかけがうまい

PHP

  • 名前がださい。
  • ライブラリがださい。関数名がださい。
  • もうとにかください、見るのも嫌。

総評:作った人間はドS、使ってる人間はドM。

Python

総評:とにかく微妙というか、中途半端につかいにくい。いまだにPerlが生きていたり、Rubyキャッチアップされてるのも納得の出来。これがLL界を制覇したらPerlよりうっとうしい。

Ruby

  • 基本的な機能には文句ない。メソッドチェインとかブロック構文とかもうさいこう。
  • なんで定数に代入可なんだろう。警告出るからいいけど。
  • なんでも式だから乱用したら解読しにくそう。
  • ボキャブラリや慣習が他の言語からの流用が多いから覚えやすい。
  • ライブラリが貧弱。
  • バイト文字処理が貧弱。
  • require 'rubygems' が汚いしめんどくさい。RUBYOPTつかえ?そういう問題じゃない。
  • いちばんおそい。でもPythonとどっこいどっこいだからあんまりきにしない。
  • 日本でしか人気ないから結構あっという間にすたれそう。
  • よくまとまってて使いやすいけど、特に目新しい点はないよね。人気があんまりないから変な要望がなくてごちゃごちゃしてないって部分はあるかも。

総評Perlライブラリが流用できたら最高。できなかったらNIH症候群にかかる。

いじょう、ストレスたまったドヘタプログラマが真夏の暑いときに油をぶちまけてみたよ☆

2008-02-10

http://anond.hatelabo.jp/20080209175616

Both require capabilities of design. Both inquire beauties.

2008-02-09

科学芸術

先生日本人のくせに・・・。

テスト科学芸術の共通点を書けって言われた。

Here's what I said:

One of the similarities between art and science is that both require observation and creation.

In art, you have to obeserve your internal and external world. Then, you creat your own work of art, by which you express yourself.

Similarly in science, you first obeserve natural phenomena or what's happening in front of you. Then, you creat a law or principle of the nature.

とりあえず、科学芸術も鋭い観察力と創造する力が必要なんじゃないかなー。

2008-02-02

jcode.plっぽい使い方で

$_POST = array_map('htmlspecialchars', $_POST); 
$_GET = array_map('htmlspecialchars', $_GET);
$HTTP_COOKIE_VARS = array_map('htmlspecialchars', $HTTP_COOKIE_VARS);

//$_SERVER = array_map('htmlspecialchars', $_SERVER); 
$_SERVER['HTTP_USER_AGENT'] = htmlspecialchars($_SERVER['HTTP_USER_AGENT']);
$_SERVER['PHP_SELF']  = htmlspecialchars($_SERVER['PHP_SELF']);

//改行コード→改行タグ
function del_tag($str) {
return $str = str_replace("\r\n", "<br>", $str);
}

$_POST = array_map("del_tag", $_POST);
$_GET = array_map("del_tag", $_GET);

$HTTP_COOKIE_VARS = array_map('del_tag', $HTTP_COOKIE_VARS); 
$_SERVER['HTTP_USER_AGENT'] = del_tag($_SERVER['HTTP_USER_AGENT']);
$_SERVER['PHP_SELF']  = del_tag($_SERVER['PHP_SELF']);

//タブ除去
function del_tab($str) {
return $str = str_replace("\t", "", $str);
}
$_POST = array_map("del_tab", $_POST);
$_GET = array_map("del_tab", $_GET);
$HTTP_COOKIE_VARS = array_map('del_tab', $HTTP_COOKIE_VARS); 
$_SERVER['HTTP_USER_AGENT'] = del_tab($_SERVER['HTTP_USER_AGENT']);
$_SERVER['PHP_SELF']  = del_tab($_SERVER['PHP_SELF']);

セキュリティ全然詳しくないだけども・・・

例えばこんな感じのファイルを置いてから、最初に

require 'secure.php';

そのファイルをincludeするように先頭に書いておくだけで、いろんなサニタイズとかを一気にやってくれるような仕組みってないのかな(SQLインジェクションとやらもちゃんと防げるようにして)。

無ければ増田でみんなで作る程度の価値はありそうなものだと思う。

こういうのって名前出して公開しちゃうと危険だから2ch増田向きだと思うし。

2007-12-16

require "win32/sound"

include Win32

# wav ファイル再生

Sound.play("foo.wav")

# システムサウンドの再生

Sound.play("SystemAsterisk",Sound::ALIAS)


require "win32/etc"

include Win32

# リモートシステム上のユーザ情報の表示

Etc.passwd('\\some_machine'){ |pwstruct|

p pwstruct

}

# ローカルシステム上のグループ情報の表示

Etc.group{ |gstruct|

p gstruct

}

2007-11-01

PerlClass::Data::Inheritableの解析

唐突にClass::Data::Inheritableのソースコードについて説明してやんよ。

使い方とかの説明はこの辺でも読んでから出直して来い、ごるぁ!

まぁとりあえずソース見てみろ、下記にはっつけてやっからよぉ!


 1: package Class::Data::Inheritable;
 2:
 3: use strict qw(vars subs);
 4: use vars qw($VERSION);
 6: $VERSION = '0.06';
 7: 
 8: sub mk_classdata {
 9:     my ($declaredclass, $attribute, $data) = @_;
10: 
11:     if( ref $declaredclass ) {
12:         require Carp;
13:         Carp::croak("mk_classdata() is a class method, not an object method");
14:     }
15: 
16:     my $accessor = sub {
17:         my $wantclass = ref($_[0]) || $_[0];
18: 
19:         return $wantclass->mk_classdata($attribute)->(@_)
20:           if @_>1 &amp;&amp; $wantclass ne $declaredclass;
21: 
22:         $data = $_[1] if @_>1;
23:         return $data;
24:     };
25: 
26:     my $alias = "_${attribute}_accessor";
27:     *{$declaredclass.'::'.$attribute} = $accessor;
28:     *{$declaredclass.'::'.$alias}     = $accessor;
29: }
30: 
31: 1;

短いソースだなーこれ。でもな、なめんじゃねーぞ。短いけど色々な技術が盛り込まれてんだよコレはよぉ。


ハイ、まず3行目。

かるくstrictについて説明してやんよ。心して聞けよオマエラ。

strictっつーのはだな、つまりPerlにおける曖昧な部分をすこーしだけチェックしてくれるスグレモノなんだなコレが。

とりあえずざっくり言うと三つの機能があってだな、下記のよーに書くわけだ。


 use strict 'vars';
 use strict 'subs';
 use strict 'refs';

varsってーのは簡単に言うとmyとかourとか宣言しろボケってやつですわ。

subsは裸体は許さんってやつですの、$とか%とかついていない裸の文字列をエラーにしてくれんだよ。

refsってのが一番やっかいな代物でな、これはムツカシイ言葉で言うとシンボリックリファレンスってんだが、要は変数名に変数を使うとエラーにしてくれるってこったよ。

で、これら全部ひっくるめてuse strict;なんだな。わかったか?オラ!

ちゅーことはだ、3行目を見ると意図的にrefsだけ外してるのがわかるよな。

つまりコレはこのコードのどこかで変数名に変数を使うってことを明示していることにもなるわけだ。けけけ。

あーもういいもういい、次だ、次。

4,5行目を見てみろよ。今時our使わずにuse vars使うなんてどんだけー

ははは、まぁまてよ。

ourってのは明示的にグローバル変数を定義するもんなんだが、このourってやつが導入されたのがPerl5.6からなんだよ。

Perl5.5のころはourなんてなかったからグローバル変数定義すんのにこのuse varsを使っていたわけだ。

つまりこのモジュールはPerl5.5環境でも動くように配慮しているわけなんだな、ちゃんちゃん。ほほほ。

あーもう全然すすまねーよ。チクショウ、が、ま・・・・。

で、11-14行目。これはref関数使って$declaredclassがオブジェクトだったら死ぬって処理だ。

require CarpっつーのはCarpモジュールを動的にロードしてるっていうことだよぅ。

で、Carp::croak関数使ってエラー文はいて死ぬ、と。ちなみにこのCarp::croakってはまぁdie関数みたいなもんなんだ。

違いとしてはエラーの発生した原因を呼び出し元の奴のせいにして自分は悪くないんだよってアピールすることかな。まぁ実際使ってみりゃわかるよ。

さぁ、16行目。本編突入だ。長かった。長い道のりだったなお前ら。

sub {}ってのは無名サブルーチン(関数リファレンス)ってやつだ。で、ここで注目すべき点はただひとつ!!!!!

19-23行目あたりをぼーっとみてると$declaredclass, $attribute, $dataっていう変数を使用していることがわかる。

これらの変数は9行目で受け取ったmk_classdataへの引数だ。

ここで問題が発生する。

ダダダダン!ダダダダン!ここで問題が発生する!

myで宣言された変数賞味期限スコープの終端だ。それはわかるな?

つまり9行目で宣言された$declaredclass, $attribute, $dataといった変数どもは29行目のスコープの終端で消滅してしまうわけだ。

しかし!その消えてしまうはずの変数どもをsub {}という無名サブルーチンの中で使用してしまっている!!!

これが世間一般に語られているクロージャという仕組みなのだ!!!!!!うはははははははh!!!

本来生涯をまっとうするはずだった変数たちが別のサブルーチンの中にまぎれてしまうとその別のサブルーチンが消えてなくなるまでは死ぬことを許されなくなるのである!!!ざ・不☆老☆不☆死!

なんたる奇妙奇天烈なことであるが、この現実を受け入れることによってお前らの道が開けるんだ!!!すげーだろぉがよぉ!!

ボクはッ、キミがッ、クロージャを受け入れるまでッ、殴るのをやめないッ!


さて、肝心の16-24行目のアクセサ部分の処理の解説だけども、

引数が渡されてなければ特になんの処理もせずに$dataを返している。$dataってのは死ぬことを許されなくなったカワイソウな変数君だ。

つまり、Class::Data::Inheritableってやつはアクセサに渡された値をどこで保存してるのかというと、紛れも無いこの$data君に他ならない。

$data君がニート君になっちゃうとたちまちデータの読み書きができなくなるのであまり働かせ過ぎないように注意しよーね!

ハイ、次はアクセサに引数が渡された時の処理だけどな、20行目を見てみろ。$declaredclassに格納されてる値はmk_classdataメソッドを使用したときに格納された値になる。


 package Hoge;
 use base qw/Class::Data::Inheritable/;
 Hoge->mk_classdata('hoge_accessor');

つまり上記の処理で例えると、$declaredclassには'Hoge'という文字列が入ってることになんだな。

で、この'Hoge'と$wantclassに入ってる値を比較しているわけだが、


 package Hoge;
 use base qw/Class::Data::Inheritable/;
 Hoge->mk_classdata('hoge_accessor');
 
 Hoge->hoge_accessor('aaa');

上記の処理で例えると$wantclassには$declaredclassと同じく'Hoge'が入ってくることになんだな。うっひょー。

んで、20行目のif文は$wantclassと$declaredclasが違う場合にだけ19行目の処理を実行しているわけだからこの場合はスルーするわけだぁ。ひょひょひょ。

じゃあだな、$wantclassと$declaredclasが違う場合ってどんな場合?ってことだが、下記に例を示すから目ん玉引ん剥いて網膜から直接見てみろよこのボケ野郎どもが。


 package Hoge;
 use base qw/Class::Data::Inheritable/;
 Hoge->mk_classdata('hoge_accessor');
 
 package Foo;
 use base qw/Hoge/;
 
 Foo->hoge_accessor('bbb');

HA!HA!HA!こういう場合だよ米ベー。$wantclass=Fooで$declaredclas=Hogeになるんで19行を実行し、Fooをベースにしてmk_classdataを呼ぶことでFooに同じ名前の新たなアクセサを提供し、元クラスHogeの値を壊さないようにするわけですなぁ。

考えた人すごいですなぁ。これがClass::Data::Inheritableが継承可能なクラス変数といわれる由縁でするまする。

で、最後の26-28行目はコレらの便利な処理をしてくれる$accessorさんをクラスに登録するというわけですよぉ。

27,28行目の*ってのは型グロブ変数ってという奴で、型グロブに対して無名サブルーチンを突っ込むと動的に関数を定義できるんだなぁコレが。

でここで、初めに俺が語った話を覚えてるか?へっ、オマエラなら覚えてないだろうなけっけ。use strictの話だよ。refsだよrefs。

ここでrefsを省いていたのが利いて来るんだ。refsって何だった?ホラ言ってミソ?

うんうん。変数名に変数を使えないようにするだったね。

で良く見てみると型グロブ変数に対して「$declaredclass.'::'.$attribute」っていう変数を使おうとしているよね?これをしたかったからrefsだけ仲間外れにしてたわけですね。

はは。

あー、あー、あー。

これで終わりだよぅ。みんなわかったかな!?

コレ読んでもわからんやつはもう死ぬか、もしくはわからん用語について死ぬほど調べてもっかい読みなおしてみろこのド低のぅッ・・・ごふんごふん、このクサレ脳みそがぁ!!!!!!!!!!!!11



プログラ増田のあなぐら

2007-10-20

http://anond.hatelabo.jp/20071019153453

よもや、レスがついてるとは思わなかった。

興味を持ってもらえてサンクスです。

entry = diary.entry('20070712231804')

エントリー指定してたからなんなんだろうと思って。

editもできるってことなのかな?

書き込んだあとの編集機能はいまんとこなし。

上記は指定したidエントリを引っ張ってくる。

# get entry from id
entry = diary.entry('20070712231804')

# puts entry title
puts entry.title

# puts entry content
puts entry.content

で、そもそもRubyに詳しくない自分からするとちゃんとした使い方がそれでもわからない。

バカでごめんねなんだけど、どうやって使えばいいの?

たとえば、エントリタイトル一覧(1ページ目だけだけど)を出力するなら

diary = Masuda::Diary.new

diary.entries do |entry|
  puts entry.title
end

こんな感じかな。

新しいエントリを登録するなら

diary = Masuda::Diary.new

diary.login('my_id', 'my_pass')
diary.post('koko wa title ne', <<EOS)
koko ni kizi no honbun wo kaku
EOS

とか。


ずらーっと増田らしきものを読み込む。

そりゃそうだわなと思いながら文字化けの山。

文字化けなのは、たぶん増田エントリ(UTF-8)をそのまんま取得しているせいだと思う。

なんでスクリプトを実行するときにフィルタを通すとか

~$ ruby hoge.rb | nkf -Ws

出力するときにSJISに変換するとか。

require 'rubygems'
require 'masuda'
require 'kconv'

diary = Masuda::Diary.new
diary.entries.each do |entry|
  puts entry.title.tosjis
  puts entry.content.tosjis
end

あと

session[:diary] = diary.raw

diary = Masuda::Diary.restore(session[:diary])

の...って何でしょうか??

Masuda::Diary#rawインスタンスシリアライズするメソッド。

ログイン済みのインスタンスシリアライズしてセッションにつっこんどいて、次のリクエストでも使いまわすとか。


わかりづらい文章で申し訳ない。

2007-10-18

Ruby Masuda Library

http://rubyforge.org/projects/masuda/

作った。

インストール
gem install masuda
使い方
require 'rubygems'
require 'masuda'

diary = Masuda::Diary.new
diary.entries.each {|entry| puts entry.content }
entry = diary.entry('20070712231804')
puts <<EOS
  #{entry.title}
  #{entry.content}
EOS
entry.trackbacks.each {|trackback| puts trackback.snippet }

diary.login('my_id', 'my_pass')
diary.my_entries.each {|entry| puts entry.content }
diary.post('Ruby is ...', <<EOS)
A dynamic, open source
programming language with a
...
EOS

session[:diary] = diary.raw
...
diary = Masuda::Diary.restore(session[:diary])

2007-09-07

ようこそ、℃-uteLisp の世界へ

発祥: http://ex23.2ch.net/test/read.cgi/morningcoffee/1188654905/


はじめに

Scheme という Lisp 語族言語を用いて ℃-ute相関関係プログラムし、様々な角度から関係性を分析する手法を紹介していきます(ソースコードは最後に張ります)。


まずは、メンバー間の関係を「リスト」というデータ型で表現します。例えば「栞菜->愛理」という関係

(kanna . airi)

という形で表すことができます。これに、「大好き」という情報を付加し、ついでにその関係の性質を数値化したものを加えると

((kanna . airi) (desc "大好き") (score . 1))

のようになり、関係図における一つの矢印の情報データ化できたことになります(暫定的に、好意は 1、良好・中立は 0、険悪は -1 の3段階で表すことにします)。

メンバー間の全ての関係性をこのデータ単位で定義し、データベース化しておくことで、色んな条件に基づいた検索やスコア計算などが可能となります。


例 1: リンク状況の調査

ここで相関関係図における矢印を「リンク」と呼ぶことにして、あるメンバーから他のメンバーへどのようにリンクし、またリンクされているかを調べることができます。

関係の中からリンクの起点を抽出してソートしてみると

(sort-nodes (number-list (from-links)))

結果:

((kanna . 6) (saki . 5) (maimi . 4) (erika . 3) (mai . 3) (chisato . 3) (airi . 2))

栞菜ちゃんがメンバー全員にリンクを張っていることが分かり、℃-ute ラブっぷりが伺えます。なっきーにも同様の事が言えます。例の「女の子が好き」発言を数値的に裏付ける結果と言えるかもしれません。

ただ、データ不足でリンク件数がまだ少ないのと、リンクの性質(好意/反感など)までは分からない点を考慮する必要があるでしょう。


例 2: 被リンク状況の調査

同様に、リンクの終点の件数を調べてみます。

(sort-nodes (number-list (to-links)))
((chisato . 5) (erika . 5) (kanna . 4) (maimi . 4) (airi . 4) (mai . 3) (saki . 1))

えりかちゃんと千聖ちゃんが高ポイントです。メンバーからの人気や注目度の高さを示すデータですが、千聖ちゃんの場合敵対的なリンクが2件含まれている点に注意してください。

なっきーの被リンク数が極端に少ないですが、単純にデータ不足のためだと思われます。はぶら(ryとか言わないようにお願いします。


例 3: 愛情度の評価

リンクに付随するスコアを計算することで、愛情の度合いを測ることができるのではないか、という考えに基づく研究です。

まず、全ての関係性を対象として、スコアマイナス関係を抽出してみます。

(filter-nodes (lambda (n)
		(< (score-relation n) 0)))

結果:

(((kanna . chisato) (desc "愛理に手出すんじゃねぇよ") (score . -1))
 ((saki . chisato) (desc "愛理に手出すんじゃねぇよ") (score . -1)))

件数だけを得ると

(length (filter-nodes (lambda (n)
			(< (score-relation n) 0))))
2

僅か2件です。

良好・中立的な関係

(length (filter-nodes (lambda (n)
			(= (score-relation n) 0))))
8

愛に満ちた関係

(length (filter-nodes (lambda (n)
			(> (score-relation n) 0))))
16

非常に多いです。舞美ちゃんの「℃-ute同士でラブラブなんですよ」発言(例のラジオ)を数値的に裏付ける結果と言えるんじゃないでしょうか。

次に、メンバーごとのスコアを算出してみます。Lisp 的には以下のようにフィルタリングと畳み込み (fold) で計算することができます。例えば

(foldr (lambda (n acc)
	 (+ (get-score n) acc))
       0
       (filter-nodes (cut to? <> 'kanna)))

栞菜ちゃんに対するリンクスコアが得られます。結果:

3

上式を一般化して一挙にメンバー全員に適用してみると

(sort-nodes (map (lambda (x)
		   (cons x (score-loved x)))
		 (all-members)))

結果:

((airi . 4) (kanna . 3) (mai . 2) (erika . 2) (maimi . 2) (saki . 1) (chisato . 0))

愛理ちゃんが好意を寄せられやすい傾向が伺えます。

今度は逆方向のスコアを計算してみると

(sort-nodes (map (lambda (x)
		   (cons x (score-loving x)))
		 (all-members)))
((kanna . 3) (maimi . 3) (chisato . 2) (airi . 2) (saki . 2) (mai . 1) (erika . 1))

まいまいえりかちゃんが特に堅い・一途だという傾向を読み取ることができます。


例 4: 相性の調査

今度は組み合わせ(カップリング)の評価です。

2点間相互のリンクスコアを加算したものを「相性」と考えられるものとします。最大値 (互いに好意を寄せている場合の数値) は現在スコアリング方式では 2 です。例えば

(score-between 'kanna 'airi)

の値は

2

となります。1 であれば一方通行と考えます。

関係性が未定義の場合もあるので 0 のものを除外して算出すると

(sort-nodes (filter (lambda (n)
		      (not (= (cdr n) 0)))
		    (map (lambda (n)
			   (cons n (apply score-between n)))
			 (all-combinations))))
(((chisato mai) . 2)
 ((chisato airi) . 2)
 ((airi kanna) . 2)
 ((saki kanna) . 2)
 ((kanna maimi) . 2)
 ((erika maimi) . 2)
 ((saki airi) . 1)
 ((saki erika) . 1)
 ((kanna mai) . 1)
 ((maimi airi) . 1)
 ((saki chisato) . -1)
 ((kanna chisato) . -1))

となります。若干ピンとこない部分もあるかも知れませんが、計算上は矛盾無くデータの内容を表しています。

参考までに、スコア 1 の相互関係の中身を見てみると

(map (lambda (p)
       (find-relation (cons (caar p) (cadar p))
		      identity))
     (filter (lambda (n)
	       (= (cdr n) 1))
	     (map (lambda (n)
		    (cons n (apply score-between n)))
		  (all-combinations))))
(((kanna . mai) (desc "喰ってやるよ") (score . 1))
 ((saki . airi) (desc "好き") (score . 1))
 ((maimi . airi) (desc "良き妹") (score . 1))
 ((saki . erika) (desc "彼氏にしたい") (score . 1)))

のようになります。


まとめ

以上の調査を経て気になった問題点を列挙してみます。

特に最初の点に関して、「百合的」なるものの質的評価がなかなか難しいと感じました。例えば「大好き」も「良き妹」も同じ 1 と評価してしまっているのが妥当かどうか、といったことです。

また、スレにて与えられた情報を評価・分析する方法としては有効だとしても、逆方向のフィードバックの手段がなかなか見つからないというのが三つ目の問題です(技術力不足とも言います)。(注:画像化の方法が分かりました。追記参照)


最後に、プログラムソースを示します。実行には PLT Scheme が必要です。文字コードUTF-8 で保存した上で、(load "c-ute.ss") としてください。文字化けする場合はターミナルUTF-8 を表示できるよう設定する必要があります。がんばってください。


プログラム

c-ute.ss:

(require (lib "etc.ss")
         (lib "list.ss")
         (lib "26.ss" "srfi")
         (lib "delete.ss" "srfi" "1"))

;;; Utilities

(define true? (compose not not))

(define (ignore _) #f)

(define fif
  (case-lambda
    ((predicate consequent)
     (fif predicate consequent ignore))
    ((predicate consequent alternative)
     (lambda (x)
       (if (predicate x)
           (consequent x)
           (alternative x))))))

(define (concat! xs) (apply append! xs))

(define (mapconcat f lst sep)
  (let lp ((str (f (car lst)))
           (lst (cdr lst)))
    (if (null? lst)
        str
        (lp (string-append str sep (f (car lst)))
            (cdr lst)))))

(define (slice-string str len)
  (let lp ((res '())
           (str str))
    (if (<= (string-length str) len)
        (reverse! (cons str res))
        (lp (cons (substring str 0 len) res)
            (substring str len)))))

(define (break-string str len)
  (mapconcat identity (slice-string str len) "\\n"))

;; NOTE: input and output ports have to be either file-stream or #f
;; (i.e., cannot be a string port)
(define (run exe opt in out)
  (let-values (((p p-i p-o p-e)
                (subprocess out in #f exe opt)))
    (subprocess-wait p)
    (close-input-port p-e)))

;;; Database

;; http://ja.wikipedia.org/wiki/%E2%84%83-ute

(define names
  '((erika . "えりか") (maimi . "舞美") (saki . "早貴") (airi . "愛理")
    (chisato . "千聖") (mai . "舞") (kanna . "栞菜")))

(define (symbol->name sym)
  ((fif true?
        cdr)
   (assq sym names)))

(define nodes '())
(define edges '())

(define (relate from to desc score)
  (let ((n (cons from to)))
    (or (find-relation n
                       (lambda (r)
                         (let ((d (assq 'desc r))
                               (s (assq 'score r)))
                           (set-cdr! d (cons desc (cdr d)))
                           (set-cdr! s (+ score (cdr s))))))
        (begin
          (set! nodes (cons n nodes))
          (set! edges (cons (cons n `((desc ,desc)
                                      (score . ,score)))
                            edges))))))

(define (find-relation n k)
  ((fif true? k)
   (assoc n edges)))

(define (related? x y)
  (find-relation (cons x y) (lambda (_) #t)))

(define (from? n x)
  (eq? (car n) x))

(define (to? n x)
  (eq? (cdr n) x))

(define flip-relation
  (case-lambda
    ((n)
     (and (related? (cdr n) (car n))
          (cons (cdr n) (car n))))
    ((n k)
     ((fif true? k)
      (flip-relation n)))))

(define (get-score n)
  (cdr (assq 'score n)))

(define (get-description n)
  (cdr (assq 'desc n)))

(define (describe-relation n)
  (find-relation n get-description))

(define (score-relation n)
  (or (find-relation n get-score) 0))

(define (print-node . ns)
  (for-each (cute find-relation <>
                  (lambda (r)
                    (display
                     (format "| ~a => ~a  (~a)~%"
                             (caar r) (cdar r)
                             (mapconcat (lambda (s)
                                          (string-append "\"" s "\""))
                                        (cdr (assq 'desc r))
                                        ", ")))))
            ns))

(define (iter-nodes k)
  (let lp ((nodes nodes))
    (unless (null? nodes)
      (k (car nodes))
      (lp (cdr nodes)))))

(define (filter-nodes p)
  (let ((ns '()))
    (iter-nodes (fif p
                     (cut find-relation <> (lambda (n)
                                             (set! ns (cons n ns))))))
    ns))

(define (from-links)
  (map car nodes))

(define (to-links)
  (map cdr nodes))

(define (all-members)
  (delete-duplicates! (from-links)))

(define (all-pairs) nodes)

(define (ordered-pairs)
  (concat! (map (lambda (x)
                  (map car
                       (sort (filter-nodes (cute to? <> (car x)))
                             (lambda (x y)
                               (> (get-score x) (get-score y))))))
                (sort-nodes (map (lambda (x)
                                   (cons x (score-loved x)))
                                 (all-members))))))

(define (all-combinations)
  (let lp ((cs '()) (ns nodes))
    (if (null? ns)
        cs
        (let ((n (car ns)))
          (lp (if (member (list (cdr n) (car n))
                          cs)
                  cs
                  (cons (list (car n) (cdr n)) cs))
              (cdr ns))))))

;; number-list :: [a] -> [(a . Int)]
(define (number-list ls)
  (let lp ((ns '()) (ls ls))
    (if (null? ls)
        ns
        (let ((x (car ls)))
          (lp ((fif not
                    (lambda (_) (cons (cons x 1) ns))
                    (lambda (n)
                      (set-cdr! n (add1 (cdr n)))
                      ns))
               (assq x ns))
              (cdr ls))))))

;; sort-nodes :: [(a . Int)] -> [(a . Int)]
(define (sort-nodes ns)
  (sort ns (lambda (x y)
             (> (cdr x) (cdr y)))))

(define (diff-nodes ms ns)
  (let lp ((ds '()) (ns ns))
    (if (null? ns)
        (sort-nodes ds)
        (lp (let* ((n (car ns))
                   (m (assq (car n) ms)))
              (cons (cons (car n)
                          (- (cdr m) (cdr n)))
                    ds))
            (cdr ns)))))

(define (get-total-score x p)
  (foldr (lambda (n acc)
           (+ (get-score n) acc))
         0
         (filter-nodes (cut p <> x))))

(define (score-loved x)
  (get-total-score x to?))

(define (score-loving x)
  (get-total-score x from?))

(define (score-between x y)
  (+ (score-relation (cons x y))
     (score-relation (cons y x))))

(define (-> x)
  (display (format "~%Links from [~a]~%" x))
  (iter-nodes (fif (cut from? <> x)
                   print-node)))

(define (<- x)
  (display (format "~%Links towards [~a]~%" x))
  (iter-nodes (fif (cut to? <> x)
                   print-node)))

(define (<-> x)
  (display (format "~%Reciprocal links for [~a]~%" x))
  (iter-nodes (fif (cut to? <> x)
                   (lambda (n)
                     (flip-relation n
                                    (lambda (m)
                                      (print-node m n)))))))

(define (<=> x)
  (display (format "~%Reciprocal matches for [~a]~%" x))
  (iter-nodes
   (fif (cut to? <> x)
        (lambda (n)
          (flip-relation n
                         (lambda (m)
                           (if (ormap (lambda (x)
                                        (ormap (lambda (y)
                                                 (equal? x y))
                                               (describe-relation m)))
                                      (describe-relation n))
                               (print-node m n))))))))

(define (<?> x)
  (let ((to (assq x (number-list (from-links))))
        (from (assq x (number-list (to-links)))))
    (display (string-append
              (format "~%Link statistics for [~a]~%"
                      x)
              (format "| ~a => ~a (love ~a)~%"
                      x
                      (cdr to)
                      (score-loving x))
              (format "| ~a => ~a (love ~a)~%"
                      (cdr from)
                      x
                      (score-loved x))))))

(define (info x)
  (for-each (cut <> x)
            (list <- <-> <=> -> <?>)))

;;; GraphViz (http://www.graphviz.org/) support

(define graphviz "C:/Program Files/ATT/Graphviz/bin/dot.exe")

(define (nodes->dot ns)
  (string-append "digraph cute {\n"
                 ;;"\tordering=out;\n"
                 ;;"\trankdir=LR;\n"
                 "\toverlap=true;\n"
                 "\tnode[fontname=\"msgothic.ttc\"];\n"
                 "\tedge[fontname=\"msgothic.ttc\",fontsize=9];\n"
                 (let lp ((str "") (ns ns))
                   (if (null? ns)
                       str
                       (let* ((n (car ns))
                              (s (score-relation n)))
                         (lp (string-append
                              str
                              (format "\t\"~a\" -> \"~a\""
                                      (symbol->name (car n))
                                      (symbol->name (cdr n)))
                              (format "[label=\"~a\",color=\"~a\","
                                      (break-string
                                       (car (describe-relation n))
                                       7)
                                      (cond ((> s 0) "red")
                                            ((= s 0) "green")
                                            (else "blue")))
                              (format "style=\"bold~a\"];\n"
                                      (if (and (not (= s 0)) (< s 1) (> s -1))
                                          ",dashed"
                                          "")))
                             (cdr ns)))))
                 "}"))

(define (write-dotfile dot file)
  (and (file-exists? file) (delete-file file))
  (with-output-to-file file
    (lambda ()
      (display dot)))
  file)

(define (dot->png dot png)
  (call-with-input-file (write-dotfile dot "c-ute.dot")
    (lambda (in)
      (and (file-exists? png) (delete-file png))
      (call-with-output-file png
        (lambda (out)
          (run graphviz "-Tpng" in out)))))
  'done)

;;; Setup database

;; Based on:
;; http://ex23.2ch.net/test/read.cgi/morningcoffee/1188654905/116-142
(begin
  (relate 'maimi 'erika "大好き" 1)
  (relate 'maimi 'kanna "良き妹" 1)
  (relate 'maimi 'airi "良き妹" 1)
  (relate 'maimi 'mai "姉妹" 0)
  (relate 'erika 'maimi "一番可愛いよ" 1)
  (relate 'erika 'kanna "仲間" 0)
  (relate 'erika 'chisato "おソロパジャマ" 0)
  (relate 'kanna 'erika "仲間" 0)
  (relate 'kanna 'maimi "好き" 1)
  (relate 'kanna 'saki "喰ってやるよ" 1)
  (relate 'kanna 'mai "喰ってやるよ" 1)
  (relate 'kanna 'airi "大好き" 1)
  (relate 'kanna 'chisato "愛理に手出すんじゃねぇよ" -1)
  (relate 'saki 'maimi "荷物整理" 0)
  (relate 'saki 'erika "彼氏にしたい" 1)
  (relate 'saki 'kanna "興味がある" 0.5)
  (relate 'saki 'chisato "愛理に手出すんじゃねぇよ" -1)
  (relate 'saki 'airi "好き" 1)
  (relate 'airi 'kanna "受け入れる" 1)
  (relate 'airi 'chisato "最近親密" 1)
  (relate 'mai 'erika "保護者" 0)
  (relate 'mai 'maimi "姉妹" 0)
  (relate 'mai 'chisato "恋人" 1)
  (relate 'chisato 'erika "おソロパジャマ" 0)
  (relate 'chisato 'mai "恋人" 1)
  (relate 'chisato 'airi "最近親密" 1))

;; query relations / draw graphs

(if (file-exists? graphviz)
    (dot->png (nodes->dot (ordered-pairs))
              "c-ute.png")
    (for-each info (all-members)))

追記(グラフ描画について)

Graphviz というソフトによって関係図を可視化できる、ということを教えていただきました(既に上プログラムを実行すると自動的に関係画像を作成するようにしてあります)。ここでは技術的な観点から幾つか注意点を挙げておきます。


まず、Scheme プログラムから Graphviz を動かす方法について。コマンドラインからの起動のように、プログラムへのオプション文字列で入出力ファイルを指定する方法ではどうも上手く行きませんでした。調査の結果、入出力ファイルポートScheme 側で用意しておく必要があるようです。処理系によって異なりますが、PLT Scheme の場合 subprocess という関数を次のように呼び出します。

(subprocess output-port input-port #f "/path/to/dot.exe" "-Tpng")

ここで output-port は png画像ファイルへの出力ポート。input-port は dot ファイルグラフの定義ファイル)の入力ポートです。エラーポートは必要無いでしょう (#f)。

dot という名前の実行ファイルが、関係図のような有向グラフを描画するプログラムです。最後にオプション文字列として出力形式を指定します(png, jpeg, gif, etc.)。


次に dot ファイルScheme で書く方法ですが、以下の基本的な有向グラフの書式

digraph g {
  A -> B;
  B -> C;
  C -> A;
}

を理解すれば、後は実直に Schemeデータを当てはめて format 関数等で変換するだけです。

(string-append
 "digraph g {"
 (format "~a -> ~a;" (car node) (cdr node))
 "}")

問題は、ノードを配置する順番によって出来上がる画像が変わってくる、ということです。

より見た目に分かりやすくするための工夫としては、相互にリンクするノード同士が dot ファイル上でも近接して出力されるようにすると良いでしょう。関連の強いものが画像の上でも近くに表示されるようになります。

また上述(特に例3)のスコア概念を応用し、スコアの低いものが後に出力されるようにすることで、重力感覚に一致するような関係図を得ることができるでしょう。

- 転職ならen
- 派遣ならen
2ページ中1ページ目を表示(合計:31件)