CodeIQ:異世界のブラウザの判定

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「異世界のブラウザの判定」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
【概要】
あなたの知らない遠い世界。
そこにもブラウザがあり、そこにもブラウザ依存コードが必要だった。
で。
user-agent 文字列を与えるのでブラウザの判定をしてください。

【詳細】
この世界には、5種類のブラウザがあります。
各ブラウザの対応すべきバージョンと user-agent 文字列は下表の通り:
※ 【】内の文字列は、OS やマイナーバージョンによって変わります。
※ ★ は、よく意味の分からない謎の番号です。正の整数をピリオド区切りでならべたものになっています。

<Mamella Firedog>
ブラウザの老舗。
以前は Mamella というブラウザを作っていたが、プロジェクトが上手く行かずバージョン6をリリースできず破綻。
サブプロジェクトだった Firedog がメジャーになった。

バージョンuser-agent 文字列
1.0〜50.0 Mamella/5.0 (【OS名】) Lizard/【リリース日】 Firedog/【バージョン】
【リリース日】は、10進数8桁の数字。

<Orange Voyage>
NavOS が搭載された Navel というパソコンを売っている会社が出しているブラウザ。
バージョンuser-agent 文字列
1.0〜10.0 Mamella/5.0 (【OS名】) OrangeKit/【OrangeKitのバージョン】 (like Lizard) Version/【バージョン】 Voyage/★

<AnachroSoft Internet Traveller>
PCシェア9割を超えるOS、Fenetre を出している会社のブラウザ。
一時はブラウザシェア95%を誇っていたが、今では1割弱になっている。
ASIT12 でシェアを取り戻すことを狙っている。
バージョンuser-agent 文字列
2.0〜7.0 Mamella/4.0 (compatible; ASIT 【バージョン】; 【OS名】)
8.0〜10.0 Mamella/5.0 (compatible; ASIT 【バージョン】; 【OS名】)
11.0 Mamella/5.0 (【OS名】; Quadent/7.0; .KNOT SLR; rv:4.0) like Lizard
12.0 Mamella/5.0 (【OS名】; Quadent/7.0) OrangeKit/12.0 Firedog/3.0 (like Lizard) Voyage/4.0 ASIT/12.0

<Kabuki Browser>
シェアは少ないものの、一定のファンを獲得し続けているブラウザ。
バージョン 14 までは Lento という独自エンジンを使っていたが、 バージョン 15 からは後述の Monochrome と同じ Twinkle というエンジンを使うようになった。
バージョンuser-agent 文字列
1.0〜6.0 Mamella/4.0 (【OS名】) Kabuki 【バージョン】
7.0〜8.0 Mamella/4.0 (compatible; ASIT 6.0; ASIT 5.5; 【OS名】) Kabuki 【バージョン】
9.0〜14.0 Kabuki/【バージョン】 (【OS名】) Lento/【Lentoのバージョン】
15.0〜40.0 Mamella/5.0 (【OS名】) OrangeKit/★ (like Lizard) Monochrome/★ Voyage/★ KBK/【バージョン】

<Gluege Monochrome>
検索エンジンで世界を制した会社が突然出してきたブラウザ。
当初は Voyage と同じエンジンを使っていたが、途中でフォークし、今は Twinkle というエンジンを使っている。
バージョンuser-agent 文字列
1.0〜27.0 Mamella/5.0 (【OS名】) OrangeKit/【OrangeKitのバージョン】 (like Lizard) Monochrome/【バージョン】 Voyage/★
28.0〜53.0 Mamella/5.0 (【OS名】) OrangeKit/★ (like Lizard) Monochrome/【バージョン】 Voyage/★

OSについて
OSは、以下のいずれかです。
名称user-agent 内での表記
Anachrosoft Fenetre 88 Fenetre, Fenetre 88, Fen32
Anachrosoft Fenetre Testament Fenetre, Fenetre Testament, Fen32, Fen64
Anachrosoft Fenetre ichtus Fenetre, Fenetre ichtus, Fen32, Fen64
Anachrosoft Fenetre 9 Fenetre, Fenetre 9, Fen32, Fen64
NavOS W NavOS, NavOS W
Tovax Tovax

【入出力】
入力は
Mamella/5.0 (Fen32) Lizard/20050919 Firedog/1.0.0
こんな感じで標準入力から来ます。

出力は、ブラウザ名の略称です。
ブラウザのバージョンは不要です。
ブラウザの略称は下表のとおりです:
ブラウザ名略称
Mamella FiredogMFD
Orange VoyageVYG
AnachroSoft Internet TravellerASIT
Kabuki BrowserKBK
Gluegle MonochromeGMC

さきほどの入力は Firedog 1.0 なので
MFD
を出力すると正解です。

【例】
入力出力
Mamella/5.0 (Fen32) Lizard/20050919 Firedog/1.0.0 MFD
Mamella/4.0 (compatible; ASIT 5.5; Fenetre Testament) ASIT

【補足】
不正な入力に対処する必要はありません。
問題文中にある5つのブラウザ / 6つの OS 以外の user-agent は入力に含まれません。
現実世界の user-agent 文字列は、もっと複雑で混沌としています。

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby

def isKabuki(line)
	if line.index("Kabuki") != nil then return "KBK" end
	if line.index("KBK") != nil then return "KBK" end
	return nil
end

def isMonochrome(line)
	if line.index("Monochrome") != nil then
		if line.index("KBK") != nil then return nil
		else return "GMC"
		end
	end
	return nil
end

def isOrangeVoyage(line)
	if (line.index("Voyage") != nil) && (line.index("Monochrome") == nil) && (line.index("ASIT") == nil) then return "VYG" end
	return nil
end

def isASIT(line)
	if (line.index("ASIT") != nil) && (line.index("Kabuki") == nil) then return "ASIT"
	elsif line.index(".KNOT SLR") != nil then return "ASIT" end
	return nil
end

def check(line)
	funcs = ["isKabuki", "isMonochrome", "isOrangeVoyage", "isASIT"]
	for f in funcs
		ret = send(f, line)
		if ret != nil then return ret end
	end
	return "MFD"
end

# main
while line = gets
	line.strip!
	if line.empty? then next end

	puts check(line)
end

解説

この出題者の問題で私がやった中では一番簡単と思います。
が、面倒くさいです。
ただ、(多分0私のコードはあんまりよろしくありません。

考え方

私の方法はuser agetn文字列から特徴のある部分をひっかっけて、特徴の多いものから判断して行くという方法をとっています。
そして、どれにも引っかからなかったらMFDと答えます。

main

入力値を文字をcheck()に渡し、結果を印字します。

check(line)

ユーザエージェント判別関数を順次呼び出し、結果が得られたらその文字列を返します(30〜34行目)。どのチェックにも引っかからなかったらデフォルトの"MFD"を返します(35行目)。
チェック順はKabuki、Monochrome、OrangeVoyage、ASITで、ループで処理するためsend()を使った動的呼び出しを利用しています。呼び出し順は重要で、より少ない判定条件で判断できるものを先に判断することによってふるい分けしています。

各チェック関数のインターフェースは当該ブラウザなら略称文字列、当該ブラウザでなければnilを返すとしています。

isKabuki(line)

Kabukiブラウザかどうかをチェックします。
入力値に"Kabuki"が含まれているか、"KBK"が含まれるのはKabukiブラウザなので"KBK"を返します。

isMonochrome(line)

Monochromeブラウザかどうかをチェックします。
入力値に"Monochrome"が含まれているもので、"KBK"が含まれないものを"GMC"と判断します。

isOrangeVoyage(line)

OrangeVoyageブラウザかどうかをチェックします。
入力値に"Voyage"が含まれているもので、"Monochrome"と"ASIT"が含まれないものを"VYG"と判断します。

isASIT(line)

ASITブラウザかどうかをチェックします。
入力値に"ASIT"が含まれているもので、"Kabuki"が含まれないもの、入力値に".KNOT SLR"が含まれるものを"ASIT"と判断します。

雑感

問題の仕様だとこれで十分ですが、このプログラムがよろしくない理由を説明します。
最大の問題はuser agent文字列が追加された場合、影響が全体に及ぶことです。もう一つは不明なブラウザを判断しなければいけなくなった時、うまくできないことです。
この辺りをきちんとするとuser agent文字列に対してチェックするための正規表現と略称の組を作って判断する、というのが模範解答かなぁ、と思います。問題にOSや★の仕様について言及があるのもこの辺りを考慮してのことだと思います。