Yささんの一言から始まったこの話題、 またまた大きな波紋を呼んで、各スクリプト言語の使い手たちが腕を競いました。
[お題]簡易索引メーカー
- [内容]ページ・名称データから次のような索引を作成し、表示する。
言い出しっぺの法則 ... 1,11,29 インタープリター ... 12,30 :
- [作成元データ サンプル] index.txt
1 言い出しっぺの法則 2 漢字コード 3 教典 4 作法 5 師叔 6 スクリプト 7 正規表現 演算子 8 人柱 9 マークアップ言語 10 らくだ本 11 言い出しっぺの法則 12 インタープリター 13 改行コード 14 改行文字 15 漢字コード 16 教典 17 コンパイラー 18 作法 19 情報通有 20 師叔 21 スクリプト 22 正規表現 演算子 23 人柱 24 符号化 25 マークアップ言語 26 メタキャラクター 27 メタ文字 28 らくだ本 29 言い出しっぺの法則 30 インタープリター 31 改行コード 32 改行文字 33 漢字コード 34 教典 35 コンパイラー 36 作法 37 情報通有 38 師叔 39 スクリプト 40 人柱
どなたかその他の(スクリプト)言語による例をお願いいたします(^o^)/
awk
ちなみに"簡易"なので「読み順にソート」等は省略。「登場順」ですらないですが(^^;)
スクリプト: index.awk{ page=$1; name=substr($0, index($0,$2)); tbl[name]=tbl[name] page ","; } END{ for(i in tbl) printf("%-20s ... %s\n", i, substr(tbl[i], 1, length(tbl[i])-1)); }
実行方法
gawk -f index.awk index.txt補足
↓こんなデータ test.txt で実行すると
29 言い出しっぺの法則 30 インタープリター 1 言い出しっぺの法則 11 言い出しっぺの法則 12 インタープリター
↓こうなります。
インタープリター ... 30,12 言い出しっぺの法則 ... 29,1,11対処方法
とりあえず以下の方法で対処できますd(^^;)
>mawk32 -f prefix.awk test.txt | sort | mawk32 -f index.awk[ENTER]
# prefix.awk { page=$1; name=substr($0, index($0,$2)); printf("%10d %s\n", (page+0), name); }
↓こうなります
インタープリター ... 12,30 言い出しっぺの法則 ... 1,11,29
Python(1)
また例のごとく、betterな別解がございましたらよろしくです。
スクリプト: index.py#!/usr/bin/env python from sys import argv from re import split dict = {} file = open(argv[1],'r') line = file.readline() while line: list = split(r'\s',line[:-1]) for key in list[1:]: try: dict[key] = dict[key] + list[0] + ',' except KeyError,err: dict[key] = list[0] + ',' line = file.readline() for key in dict.keys(): print '%-20s... %s'%(key,dict[key][:-1])
実行方法
次の a) あるいは b) により実行する。a) python index.py index.txt b) ./index.py index.txt
Python(2)
別解というほどのものではないのですが、対応が分かるように なるべくindex.awkと同じ名前にしてみました。
スクリプト: index2.pyfrom sys import argv from re import split tbl = {} file = open(argv[1],'r') line = file.readline() while line: list = split(r'\s',line[:-1]) for name in list[1:]: page = list[0] try: tbl[name] = tbl[name] + page + ',' except KeyError,err: tbl[name] = page + ',' line = file.readline() file.close() # 入れ忘れてた(^^; for i in tbl.keys(): print '%-20s... %s'%(i,tbl[i][:-1])
Python(3)
最初に出したやつに
- string.split()を使用
- map.get()を使用
- stringモジュールのjoin()を使用
の変更を加えて、こんな感じ
スクリプト: index3.pyfrom sys import argv from string import join tbl = {} file = open(argv[1],'r') line = file.readline() while line: list = line[:-1].split() page = list[0] for name in list[1:]: tbl[name] = tbl.get(name,[]) + [page] line = file.readline() for name in tbl.keys(): print '%-20s ... %s'%(name,join(tbl[name],','))
Python(4)
ベターかどうかわかんないけど、多分コードの量は少ないです。
スクリプト: index4.py#!/usr/bin/env python # PEPの文字列の拡張メソッドを使用していますので、 # splitメソッドが動かない場合は、string.split(str)を # 利用してください。 from sys import argv import sys file = open(argv[1],"r") f_list = file.readlines() # 全行をリストに読み込む大技 dict = {} for i in f_list: i = i.split() # ここが動かなければ、string.split(i) page = i[0] # ←注意 name = i[1] # if dict.has_key(name): dict[name] = dict[name] + "," + page else : dict[name] = page # print文の書き方は、AWKの連結書式風 for name in dict: print "%-20s" % name , " ... " , dict[name]
Python(5)
う〜ん、コンセプトだけみて、オリジナルのAWKのコードを見ないで作った のがまずかった(汗)。んでわ、ちと改造をば・・・
スクリプト: index5.py#!/usr/bin/env python import sys,string file = open(sys.argv[1],"r") f_list = file.readlines() tbl = {} for i in f_list: i = i.split() # ここが動かなければ、string.split(i) if i.__len__() > 2: # インデックスに空白を含む場合、再連結 i[1]=string.join(i[1:]) i=i[:2] # 余分な項目を取り去って、2項目に・・・ page , name = i if tbl.has_key(name): tbl[name] = tbl[name] + "," + page else : tbl[name] = page for name in tbl: print "%-20s" % name , " ... " , tbl[name]
Python(6)
Perlに負けるな、ということで、ソート機能つき最終版をアップします。
※こーいうのを意地という・・・
※参考までに言うと、私は普通はawkからsortに渡すのが普通です。
# ソート機能追加 # 初心者ブース対応、詳細説明入り # 初心者でないと、かなりくどい・・・ import sys,string # 外部モジュールをインポート # sysは引数リスト(sys.argv)を導入するために、 # stringは連結関数joinを導入するために行っています。 file = open(sys.argv[1],"r") # 引数で与えられたファイル名のファイルを #「読み出し」モードでオープン。 # "r"はread「読み出し」 # "w"はwright「書き込み、更新」 # "a"はadd「追加」 f_list = file.readlines() # readlinesメソッドは、テキストファイルの1行を1項目 # としてリストを作成。 tbl = {} # 辞書(連想配列)として追加できるように、空の辞書を作って # おく。 for i in f_list: # リストの全てのメンバーについて行う i = i.split() # 空白文字を区切りとしてリストを作成 if i.__len__() > 2: # 索引の名前に空白文字があった場合 i[1]=string.join(i[1:]) # 2項以降を2項につなげて放り込み、 i=i[:2] # いらん部分をちょん切る page , name = i # タプル展開。Pythonの必殺技の一つ。 if tbl.has_key(name): # すでに索引名が存在する場合は、 tbl[name] = tbl[name] + "," + page # ページを追加 else : # 今まで無かった場合は・・・ tbl[name] = page # ページと項目を追加 tbl_list = tbl.keys() # 連想配列はアクセスの関係上順不同ハッシュテーブルなので、 # インデックスキーを一旦リストに取り出して・・・ tbl_list.sort() # ソートメソッドで並べ替え for name in tbl_list: print "%-20s" % name , " ... " , tbl[name] # %演算子を文字列に使用すると、C言語のprintfのような出力フォーマット # が指定できる。
Perl(1)
では Perl の場合。ソートもコード順でよければ sort を入れるだけなので入れました。
スクリプト: index.plwhile (<>) { chomp; if (/^(\d+)\s+(.+)/) { $tbl{$2} = ($tbl{$2} eq "")? $1: "$tbl{$2},$1"; } } foreach (sort keys %tbl) { printf("%-20s ... $tbl{$_}\n", $_); }
実行方法
perl index.pl index.txt
Perl(2)
自分の場合はこんなのとか。基本的な構造は全く同じですけど、配列リファレンスで保持してみました。
#Perl4じゃ動かないでしょうけど
use strict; my $sLine; my %hData; while($sLine = <>) { chomp($sLine); if($sLine=~/^(\d+)\s+(.+)/) { push @{$hData{$2}}, $1; } } my $sKey; foreach $sKey (sort keys %hData) { printf "%-20s ... %s\n", $sKey, join(',', @{$hData{$sKey}}); }
Perl(3)
閑舎さんのようにPerlらしくないのですが(^^; せっかく作っていたので。
スクリプト: index3.plopen(FILE,$ARGV[0]); while($line =) { chomp; @list = split(/\s/,$line); $page = $list[0]; for( $i = 1; $i <= $#list; $i++ ) { $name = $list[$i]; $tbl{$name} .= $page . ","; } } close(FILE); foreach $i ( keys( %tbl ) ) { printf("%-20s ... %s\n",$i,substr($tbl{$i},0,-1)); }
PHP
PHP版です。 (PHPらしいかどうかは自信ないです)
スクリプト: index.php<?php $file = fopen($argv[1],'r'); while( $line = fgets($file,8192) ) { $line = chop($line); $list = split(' ',$line); $page = $list[0]; for( $i = 1; $i < count($list); $i++ ) { $name = $list[$i]; $tbl[$name] = $tbl[$name] . $page . ","; } } fclose($file); foreach( $tbl as $i => $value ) { printf("%-20s ... %s\n",$i,substr($value,0,-1)); } ?>
実行方法
php -q index.php index.txt
sed
日本語対応のsedでは意図したようには動きませんので、未対応状態の
sedに漢字コードがEUC-JPのファイルを入力するのがお勧めです。
sedは変数が2つ、しかも片方は主に保存用な感じの言語(^_^)です。
よって唯一の変数に文字列を全部連結して入力しながら、これも
ほぼ唯一の操作である置換を駆使してまとめています。そして最終的に
1項目ごとに取り出しながら無理矢理フォーマットして出力してます。
なおSolarisやHP-UXのSYSV系sedのバグ(?)を回避するためのコードが
入っているので少し回り道してます。
1s/$/\ / s/::*/:&/g s/;/ : /g G s/\n/;/g s/^\([0-9]*\) \([^;][^;]*;\)\(.*;[0-9,]*\) \2/\3,\1 \2/ h ${ s/^/;/ s/;;$/;/ h t nop :nop s/.*;\([0-9,]*\) \([^;]*\);$/\2 \1/ t print b end : print s/ : /;/g s/:\(:*\)/\1/g s/^\(....................\).* \([0-9,]*\)$/\1 ... \2/ p g s/;[^;]*;$/;/ h t nop :end d } d
Ruby
はっ、いかん。
Ruby が置いてかれちゃう。
とりあえず、閑舎さんの Perl 版をパクっとこう。
# って、ぉぃぉぃ。(^^;;
#! ruby # 使用法: ruby index.rb 作成元データ[ENTER] tbl={} while gets if /^(\d+)\s+(.+)/ tbl[$2]=(tbl[$2]) ? "#{tbl[$2]},#{$1}" : $1 end end tbl.sort.each{|e,f| printf("%-20s ... %s\n",e,f)}
TeX(1)
Yさ さんの投稿されたデータのフォーマットの
index.txt という名のファイルを読み込んで
索引のページ(.dvi)をつくります.
データフォーマットについてすこし注意を...
1行が,
[数字][空白一つ][見出し][改行]
のフォーマットしか想定していません.
[数字] の前に空白をおいたり,区切りにタブを使ったり,
[見出し]を空欄にしたりした場合の動作は未確認です.
見出しには空白文字を含めても構わないですが,
空白文字だけで構成することはできないかもしれません.
それでははじまりはじまり...
\newread\indexfile \openin\indexfile=index.txt \endlinechar=-1 \newtoks\titles \newtoks\temp \titles={} \def\ifundefined#1{\expandafter\ifx\csname{#1}\endcsname\relax} \def\push#1#2{ \ifundefined{#1} \expandafter\edef\csname{#1}\endcsname{#2} \titles=\expandafter{\the\titles{#1}} \else \temp=\expandafter\expandafter\expandafter{\csname{#1}\endcsname} \expandafter\edef\csname{#1}\endcsname{\the\temp,#2} \fi } \def\doiterate#1 #2\done{ \push{#2}{#1} } \def\whileiterate{ \read\indexfile to \indexline \ifx\indexline\empty \let\next=\relax \else \expandafter\doiterate\indexline \done \let\next=\whileiterate \fi\next } \whileiterate \closein\indexfile \noindent \def\showone#1{ \ifx#1\relax \let\next=\relax \else \message{#1 ... \csname{#1}\endcsname} #1 \dotfill\ \csname{#1}\endcsname\break \let\next=\showone \fi\next } \expandafter\showone\the\titles\relax \bye
実行方法
[p]tex index.tex
TeX(2)
> Underfull の Warning がでることです.
これも TeX の気持ちになって考えれば解決できました,
> な〜るほど。これは TeX のプリミティブのみで書かれているようですから
> platex の通常の文書の中に入れて使えますね。早速使わせてもらいます(^^)v。
パッケージとして使うには
補助的なマクロをグローバルに定義しない方が良いでしょう.
% 例えば indexmaker.tex と名付ける % % 使用方法は % \input indexmaker.tex % \makeindexfromfile{インデックスファイル名} % % 次の \indexentry を再定義すれば好みの書式になる. \def\indexentry#1#2{#1 \dotfill\ #2} \def\makeindexfromfile#1{ \begingroup \toks0={} \def\readentry##1 ##2\done{ \expandafter\ifx\csname{##2}\endcsname\relax \expandafter\edef\csname{##2}\endcsname{##1} \toks0=\expandafter{\the\toks0{##2}} \else \toks1=\expandafter\expandafter\expandafter{\csname{##2}\endcsname} \expandafter\edef\csname{##2}\endcsname{\the\toks1,##1} \fi } \def\readindex{ \read0 to \indexline \ifx\indexline\empty \let\next=\relax \else \expandafter\readentry\indexline \done \let\next=\readindex \fi\next } \def\showindex##1##2{% \ifx##2\relax \indexentry{##1}{\csname{##1}\endcsname}% \let\next=\relax% \else% \indexentry{##1}{\csname{##1}\endcsname}\break \let\next=\showindex \fi\next{##2}% } \openin0=#1 \endlinechar=-1 \readindex \closein0 \noindent \expandafter\showindex\the\toks0\relax\relax \endgroup } % indexmaker.tex はここまで % % 以下の2行はデモ用,パッケージには不用 \makeindexfromfile{index.txt} \bye実行方法(閑舎補足)
platex では \usepackage コマンドを使うのが標準的なので、その場合、 indexmaker.tex を indexmaker.sty という名前で保存し、 これと同じディレクトリに test.tex を次のような内容で作成、 platex test などとして実行します。
\documentclass[a4j]{jarticle} \usepackage{indexmaker} \begin{document} \makeindexfromfile{index.txt} \newpage ほげほげ \end{document}