徒然

思いついたら書きます

AtCoderで始めるCOBOL入門 ~概要・入出力編~

はじめに

1959年に産声を上げたプログラミング言語であるCOBOLは、今なお世界中で動いています。
しかし今ではレガシーシステムの代表格に取り上げられており、COBOLを使うエンジニアもどんどん高齢化しています。
当然ナウなヤングにバカウケである競技プログラミングサイトことAtCoderでは、COBOLのような太古の言語を使う人はほとんどいません*1
しかしCOBOLを使ってみたいという風変わりな人がどこかにいるかも知れないので、そういった人に向けて記事を書き留めておきます。
なお本気で競プロをしたい場合は勧められない*2のでこれ以上読まないのが身のためです。
また目的はAtCoderの問題を解くことなので、解くのに必要ない仕様についてはほぼ触れません。
また筆者はにわかコボラーなため、この記事には嘘も書いてあるかもしれません。ご承知おきください。
(指摘コメントもお待ちしております)

言語の違いについて

現在*3AtCoder上では2つのCOBOLが選択できます。

  • COBOL - Free (OpenCOBOL 1.1.0)
  • COBOL - Fixed (OpenCOBOL 1.1.0)

OpenCOBOLとは現在GnuCOBOLと呼ばれている、GPLCOBOLコンパイラです。仕様は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 DIVISIONWORKING-STORAGE SECTION内に以下のように記載します。

           <階層番号> <変数名> PIC <データ型> [VALUE <初期値>] [<その他>].

例:

           01 YYYYMMDD.
               03 YYYY PIC X(4).
               03 MM PIC X(2).
               03 DD PIC X(2).

階層番号

2桁の数字で表され、データの階層を表しています。 例の場合ですと01YYYYMMDD03YYYYMMDDがくっついてできたものだと考えれば良いかと思います。
親は01から始まり、子は基本的にそれより大きければ良いですが、03, 05, ...と奇数で増やしていくのが通例です。

変数名

予約語と被らない30文字以内で定義できます。

データ型

COBOLの本髄であるところのデータ型について説明します。

文字列

任意の英数字はXで表されます。日本語項目はNですが、AtCoderでは使わないでしょう。
英数字3文字ならXXXで表されます。繰り返しはカッコでも記述でき、X(3)でも同じ意味を持ちます。
Xは前詰めのため、X(10)AtCoderを代入するとAtCoder となり、後ろ3文字に空白が入ります。

数値

識別子 意味
S 符号
9 数値(ゼロパディング)
Z 数値(ゼロサプレス)
V 小数点(DISPLAYでは非表示)
. 小数点

例えば9982443539(9)+1234.56のような数値はS9(4).9(2)で表されます。
数値型は最大で18桁までしか扱えないようです*6
小数点以下の桁数まで指定できるため、計算時に浮動小数点のような誤差が発生しないというCOBOLの長所が出てきます。

初期値

変数はVALUE句を用いて初期値を設定できます。
指定しない場合はZEROSPACEが入ります。
また桁数が足りない場合、文字列の場合は後ろにスペースが、数値の場合は前に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> [...]].

例:N M Kがスペース区切りで与えられる場合

           UNSTRING INP DELIMITED BY SPACE INTO N M K.
受け取り項目が何個かわかっていない場合

問題文中に Nが指定されていて、入力を A_1, A_2, ..., A_Nと分割したい場合に、他の言語では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の概要や入出力について触れてきました。
記事が長くなってしまったので、四則演算や代表的な関数を次回取り上げます

*1:執筆時点で10AC以上しているアカウントは14のみ

*2:グローバル変数しかない、ハッシュテーブルがない、関数が少ないなどPythonに勝る部分が速度しかない

*3:執筆時点の2021年11月

*4:FORTRAN77以前にも似た仕様が存在する

*5:8文字のコンパイラもある

*6:数値をCOMP-3で定義すれば19桁以上も扱えるが、AtCoder環境のOpenCOBOL 1.1.0では扱えない