はじめに
1959年に産声を上げたプログラミング言語であるCOBOLは、今なお世界中で動いています。
しかし今ではレガシーシステムの代表格に取り上げられており、COBOLを使うエンジニアもどんどん高齢化しています。
当然ナウなヤングにバカウケである競技プログラミングサイトことAtCoderでは、COBOLのような太古の言語を使う人はほとんどいません*1。
しかしCOBOLを使ってみたいという風変わりな人がどこかにいるかも知れないので、そういった人に向けて記事を書き留めておきます。
なお本気で競プロをしたい場合は勧められない*2のでこれ以上読まないのが身のためです。
また目的はAtCoderの問題を解くことなので、解くのに必要ない仕様についてはほぼ触れません。
また筆者はにわかコボラーなため、この記事には嘘も書いてあるかもしれません。ご承知おきください。
(指摘コメントもお待ちしております)
言語の違いについて
現在*3、AtCoder上では2つのCOBOLが選択できます。
OpenCOBOLとは現在GnuCOBOLと呼ばれている、GPLのCOBOLコンパイラです。仕様はCOBOL2014準拠とのこと。
2つの違いですが、Freeの方はフリーフォーマットである一方、Fixedの方は昔の名残*4でどの列に何を書いて良いかが指定されています。
簡単に示すと以下の通りです。
列数 | 内容 |
---|---|
1-6 | 列番号の領域(空白でも可) |
7 | 標準領域(コメントなどの区分) |
8-72 | メイン領域 |
73-80 | コメント領域 |
基本的には処理内容を8-72列目に書けばFreeでもFixedでも通ると理解しておけば良いかと思います。
面倒な方は行頭からコードを書いてFreeの方でSubmitしましょう。その場合行頭に*>
を記載すればコメント扱いになります。
以下の記事は、原則どちらでも通るように記載します。
全体構成
COBOLのコードはこのような構成になっています。
IDENTIFICATION DIVISION. PROGRAM-ID. <プログラム名>. ENVIRONMENT DIVISION. *> 環境を定義する DATA DIVISION. FILE SECTION. *> ファイルを定義する WORKING-STORAGE SECTION. *> 変数を定義する PROCEDURE DIVISION. *> 処理内容を記載する STOP RUN. END PROGRAM <プログラム名>.
各DIVISIONについて
IDENTIFICATION DIVISION
PROGRAM-ID
を記載するのに使います。
PROGRAM-ID
は3-31文字*5で、英数字とハイフン(先頭・末尾以外)が使えます。
ENVIRONMENT DIVISION
基本的に省略しても構いませんが、入力が8,191列を超える場合は環境を指定する必要があります。
(詳しくは後述)
DATA DIVISION
ここではWORKING-STORAGE SECTION
で変数を定義します。
また入力が8,191列を超える場合はFILE SECTION
にファイルを定義する必要があります。
(こちらも詳しくは後述)
PROCEDURE DIVISION
ここにメインの処理を書きます。
変数について
COBOLの変数定義はDATA DIVISION
のWORKING-STORAGE SECTION
内に以下のように記載します。
<階層番号> <変数名> PIC <データ型> [VALUE <初期値>] [<その他>].
例:
01 YYYYMMDD. 03 YYYY PIC X(4). 03 MM PIC X(2). 03 DD PIC X(2).
階層番号
2桁の数字で表され、データの階層を表しています。
例の場合ですと01
のYYYYMMDD
は03
のYYYY
・MM
・DD
がくっついてできたものだと考えれば良いかと思います。
親は01
から始まり、子は基本的にそれより大きければ良いですが、03
, 05
, ...と奇数で増やしていくのが通例です。
変数名
予約語と被らない30文字以内で定義できます。
データ型
COBOLの本髄であるところのデータ型について説明します。
文字列
任意の英数字はX
で表されます。日本語項目はN
ですが、AtCoderでは使わないでしょう。
英数字3文字ならXXX
で表されます。繰り返しはカッコでも記述でき、X(3)
でも同じ意味を持ちます。
Xは前詰めのため、X(10)
にAtCoder
を代入するとAtCoder
となり、後ろ3文字に空白が入ります。
数値
識別子 | 意味 |
---|---|
S | 符号 |
9 | 数値(ゼロパディング) |
Z | 数値(ゼロサプレス) |
V | 小数点(DISPLAYでは非表示) |
. | 小数点 |
例えば998244353
は9(9)
、+1234.56
のような数値はS9(4).9(2)
で表されます。
数値型は最大で18桁までしか扱えないようです*6。
小数点以下の桁数まで指定できるため、計算時に浮動小数点のような誤差が発生しないというCOBOLの長所が出てきます。
初期値
変数はVALUE
句を用いて初期値を設定できます。
指定しない場合はZERO
やSPACE
が入ります。
また桁数が足りない場合、文字列の場合は後ろにスペースが、数値の場合は前に0が追加されます。
初期値にはHIGH-VALUE
(すべての文字コードがFF
扱い)やLOW-VALUE
(すべての文字コードが00
扱い)を指定することもできます。
なお初期値を""
にするとエラーになるため、SPACE
や" "
を指定しましょう。
その他について
配列
配列はOCCURS
を使って定義します。
指数は1-indexedです。
なおlevel 01
, 77
にはOCCURS
句を使えないのでご注意ください。TIMES
は書いても書かなくても良いです。
<階層番号> <変数名> PIC <データ型> OCCURS <配列の長さ> [TIMES].
例:
01 DP-L. 03 DP PIC 9(10) OCCURS 10.
もちろん多次元配列にすることもできます。
以下の例だとDP(3 4)
とスペース区切りにすることでアクセスできます。
01 DP-L. 03 DP1 OCCURS 10. 05 DP PIC 9(10) OCCURS 10.
配列(可変)
配列の大きさは可変にできます。
以下の例だと、N
の大きさによって配列A
の大きさが1-100の範囲で変動します。
01 N PIC 9(10). 01 A-L. 03 AI OCCURS 1 TO 100 TIMES DEPENDING ON N. 05 A PIC 9(5).
再定義
ある変数を、「文字列としても数値としても扱いたい!」「文字の区切り方を複数定義したい!」という声に応えるのがREDEFINES
句です。
なおlevel 01
, 66
, 88
にはREDEFINES
句は使えません。
01 WK. 03 S PIC X(3). 03 S-R REDEFINES S. 05 S-9 PIC 9(3).
このように定義するとS
を文字列として使うと同時に、S-9
を用いれば数値としても扱えます。
入出力
AtCoder第一歩の標準入出力について説明します。
入力
入力を受け取る方法はACCEPT
文とREAD
文の2通りがあります。
ACCEPT文を使う方法
ACCEPT
文を使えば、1行読み取って変数に格納できます。
ACCEPT INP.
ただしACCEPT
文は最大8,190文字しか読み取れないため、それ以上は次のREAD
文が必要となります。
READ文を使う方法
入力が8,191列を超える場合はREAD文を用いて解決できます。
まずFILE-CONTROL
で行順ファイルであることを定義しておきます。
次にFILE SECTION
でファイルを定義することで、PROCEDURE DIVISION
でREADできるようになります。
ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT SYSIN ASSIGN TO KEYBOARD ORGANIZATION LINE SEQUENTIAL. DATA DIVISION. FILE SECTION. FD SYSIN. 01 INP PIC X(200000). WORKING-STORAGE SECTION. 01 STR PIC X(200000). 01 N PIC 9(18). PROCEDURE DIVISION. OPEN INPUT SYSIN. READ SYSIN INTO STR. READ SYSIN. *> 上はSTRに、下はINPに代入される CLOSE SYSIN.
READ
文はACCEPT
文と組み合わせて利用することもできます。
PROCEDURE DIVISION. OPEN INPUT SYSIN. ACCEPT N. READ SYSIN INTO STR. CLOSE SYSIN.
スペース区切りの文字列を分割する方法
AtCoderをする上でスペース区切りの入力を分割するのが必須ですが、入力があらかじめわかっている場合とわかっていない場合で対応が異なります。
受け取り項目が何個かわかっている場合
何個に分割されるかが入力前にわかっている場合、UNSTRING
文を用いれば分割できます。
UNSTRING <分割前> DELIMITED BY <デリミタ> INTO <分割後1> [<分割後2> [...]].
例:がスペース区切りで与えられる場合
UNSTRING INP DELIMITED BY SPACE INTO N M K.
受け取り項目が何個かわかっていない場合
問題文中にが指定されていて、入力をと分割したい場合に、他の言語ではSplit関数などがありますが、COBOLの場合は少し面倒です。
WORKING-STORAGE SECTION
でポインタを定義し、分割の回数だけUNSTRING
を繰り返す必要があります。
01 PT PIC 9(18) VALUE 1. *> (中略) PERFORM VARYING I FROM 1 BY 1 UNTIL I > N UNSTRING INP DELIMITED BY SPACE INTO A(I) WITH POINTER PT END-PERFORM.
出力
出力にはDISPLAY
文を使います。スペース区切りで複数指定すれば、勝手に結合して出力されます。
またWITH NO ADVANCING
を指定すると、改行無しで出力できます。
DISPLAY "Yes". DISPLAY A WITH NO ADVANCING. DISPLAY " " B.
ただしこのままの場合、9
で定義した数値は00123
のようにゼロパディングで出力されます。
解決法の1つとして、数値をZ(桁数-1)9
に入れTRIM
関数を使う方法があります。
01 ANS PIC 9(15). 01 ANS-Z PIC Z(14)9. *> (中略) MOVE ANS TO ANS-Z. DISPLAY FUNCTION TRIM(ANS-Z).
最後に
ここまでCOBOLの概要や入出力について触れてきました。
記事が長くなってしまったので、四則演算や代表的な関数を次回取り上げます。