2002/5
 Yささんの一言から始まったこの話題、 またまた大きな波紋を呼んで、各スクリプト言語の使い手たちが腕を競いました。

各言語で作った簡易索引メーカー


[お題]簡易索引メーカー

どなたかその他の(スクリプト)言語による例をお願いいたします(^o^)/

awk

作者: Yさ
ちなみに"簡易"なので「読み順にソート」等は省略。「登場順」ですらないですが(^^;)

スクリプト: 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.py

from 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)

作者: ぬ
最初に出したやつに の変更を加えて、こんな感じ

スクリプト: index3.py

from 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に渡すのが普通です。

スクリプト: index6.py

# ソート機能追加
# 初心者ブース対応、詳細説明入り
# 初心者でないと、かなりくどい・・・

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.pl

while (<>) {
  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じゃ動かないでしょうけど

スクリプト: index2.pl

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.pl

open(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

作者: Zazel
日本語対応のsedでは意図したようには動きませんので、未対応状態の sedに漢字コードがEUC-JPのファイルを入力するのがお勧めです。

sedは変数が2つ、しかも片方は主に保存用な感じの言語(^_^)です。 よって唯一の変数に文字列を全部連結して入力しながら、これも ほぼ唯一の操作である置換を駆使してまとめています。そして最終的に 1項目ごとに取り出しながら無理矢理フォーマットして出力してます。

なおSolarisやHP-UXのSYSV系sedのバグ(?)を回避するためのコードが 入っているので少し回り道してます。

スクリプト: index.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

作者: sugi
はっ、いかん。 Ruby が置いてかれちゃう。
とりあえず、閑舎さんの Perl 版をパクっとこう。
# って、ぉぃぉぃ。(^^;;

スクリプト: index.rb

#! 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行が,
[数字][空白一つ][見出し][改行]
のフォーマットしか想定していません. [数字] の前に空白をおいたり,区切りにタブを使ったり, [見出し]を空欄にしたりした場合の動作は未確認です.
見出しには空白文字を含めても構わないですが, 空白文字だけで構成することはできないかもしれません.
それでははじまりはじまり...

スクリプト: index.tex

\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

% 例えば 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}