プログラミング学習 Day 2:PASCALのプログラム例

今回は、プログラムの中でよく使われるデータ型データ構造などについて説明してから、PASCALのプログラム例を見てみます。前回、プログラミングのテーマを与えましたが、プログラム例はもっと簡単なものに絞ります。

■データ

まず、「データ」ということをもっと詳しく考えてみましょう。普通の人はデータと言われれば、単語、文、数字、テキスト、音声、画像、ビデオを考えるでしょう。現代のコンピュータ(携帯も含めて)を使っているとこれらのデータはあたりまえに操作されているように見えます。アナログからディジタルにデータ形態が変わって、音楽レコードや美術などの世界もアナログが消えつつあります。

コンピュータのハードウエアに近いところのデータは、0,1のビットとよばれる単位でその組み合わせによって表現されているため、あらゆるデータがそのビットに変換され加工・保存がされています。例えば、スマホで撮った写真はビットに変換されるため、画像の解像度、つまりどのサイズの領域をビット単位で変換するかによって、シャープな写真になったり、ぶれたような写真になったりします。あるいは、モーフィングといわれる面白い変更を写真に加えることも可能になります。

■オブジェクトとインスタンス

前回のTuringマシンでは0,1のビットをデータとして考えていましたが、HLP言語(high-level programming language)では人間が普通の生活で考えている、あるいは目にしているレベルの文字や数字を基本単位として扱うことができます。

もっと抽象化のレベルをあげると、日常語で使われる言葉や頭の中で考える概念を基本単位として扱えることも可能になります。言葉とその概念の例として、「花」という単語から想起される「意識」と目の前に咲いている実際の「バラ」との間の関係をプログラムの中で表現できるのか、という問題を考えてみます。

HLP言語において、オブジェクトとインスタンスという概念を導入します。抽象的な(概念的な)「花」(Flower)をオブジェクトとして定義します。そして、花の種類、カテゴリとしての「バラ」はそのオブジェクトのインスタンスと表現します。

でも、「バラ」も抽象化されたオブジェクトと捉え、現実に庭に今咲いている「特定のバラ」を「バラ」というオブジェクトのインスタンスと定義することも可能です。抽象化レベルの上から下までの階層構造を表現できる仕組みがHLPに必要ということになります。

■オブジェクトの階層構造と継承

「花」<-「バラ」というオブジェクト同士の階層構造があり、これは継承inheritance)と呼ばれています。この構造があれば、「花」の持つ性質や属性が「バラ」にも引き継がれて、さらに「バラ」を説明するためには「バラ」特有の属性のみを定義すればいいことになります。

この考え方は、オブジェクト指向と呼ばれて、それをHLPの中で表現できるオブジェクト指向言語が生まれています。

■レコードを構成する属性、属性のデータ型

例えば、「花」を表現するために、花の名前、色、季節を考えてみます。これを表現するデータ構造として、PASCALやPythonではレコードrecord)という概念が導入されています。

レコードを定義するために、いくつかの「属性」を考えて指定することが必要です。例えば、「花」の属性として、name, color, seasonを考えます。

Record  Flower
name
color
season
end-of-record

この属性を指定するときは、HLPで扱えるデータ型を「属性」に明記します。まず、HLPで扱える一般的なデータ型として、文字列String)と整数Integer)を考えてみましょう。Stringというデータ型がないHLPでも、配列というデータ型を持つので、文字の配列という定義ができます。例えば、人の名前は、LastNameとFirstNameに分けて定義しそれぞれを文字の配列にします。

Record PersonName
LastName: Array of char
FirstName: Array of char
end-of-record

PASCALのようにStringというデータ型がある場合は、文字の配列ではなく、そのままStringというデータ型を使うことになります。

PersonName = record
LastName: string;
FirstName: string;

■変数(最大値をみつける例題)

「数字列から最大数をみつける」という問題を与えられたときに、子供でもできることは、数字の書かれたカードを2枚づつ拾って比べてみて、大きい方をポケットにれておいて小さい方を捨てる、という方法です。これを、日常語で書くと以下のようになりまどろっこしい。

—数字列を読み込む(カードを机に並べる)
—もし数字列が空なら最大値はない(カードが机にない)
—まず最大値に最初の数字を入れる(最初のカードは次のカードを取るまでは最大値とみなしポケットに入れる)
—数字列に未処理のものが残っている間は以下を繰り返す(まだとっていないカードが机にのこっている間は)
—未処理の最初の数字を取り出す(残っている中からひとつを取る)
—もしその数字が最大値より大きい時は、最大値にその数字を代入する(最大値のカードをポケットに入れなおして小さい方を捨てる)
—もし未処理の数字がなければ、今の最大数が答えとなる(すべてのカードを机から取ったときには終了となり、ポケットに残ったカードが最大値となる)

これをプログラムに書き直すためには、何が必要でしょうか。ここでは、「数字の書かれたカード」と「それを並べる机という概念(配列のデータ構造を持つ変数)」、「最大値を入れるポケット(変数)」が必要です。

日常語ではそれ・彼・彼女・あなたなどの代名詞がありますが、自由に使える変数名がありません。変数名に値をアサインする(つける)言葉がないのです。プログラムではたくさんの変数が必要となるので、日常語では表現しきれません。

プログラムでは、変数およびデータ型が最初に使うべき道具になります。

■擬似コードで表現してみる(最大値をみつける例題)

以下では、日常語ではなく、もうすこしプログラミング寄りの表現をしたいので、そのための表記方法を作りましょう。それは疑似コードpseudocode)と呼ばれるものですが、表記方法syntax)は自分で決めてもかまいません。日本語は不便なので英語表記が望まれます。

なぜこれが必要なのか? それは特定のHLPに依存しないプログラミングをするためです。それができれば使う言語はあとで選べばよい。

以下は私が考えた表記方法ですが、完全なものではなく理解を深めるためのものです。
ここでは数字列を表現するために「配列」を使ってみます。

L は、数字の配列を持つ変数
largestitem は、それぞれ数字を入れる変数
はアサインのための記号
 A←Bは Bの中の値をAにアサイン代入)するという意味です。
 変数に値を入れることを代入といいます。
input は、データの読み込み
output は、データの書き込み
L.size は数字列の長さ

L にある数字の配列をアサインするのは、
 L=[10,20,3,4,11]
あるいは、
 L[1]=10
 L[2]=20
 L[3]=3
 L[4]=4
 L[5]=11
と表記します。
(ここでは配列の最初をL[1]としましたが、実際のプログラミング言語ではL[0]とするものも多くあります。)

最大値をみつけるアルゴリズムを、この表記方法のコードを使って表すと、

Algorithm max_val
Input: A list of numbers L.
Output: The largest number in the list L.
if L.size = 0 return null
largest L[1](largestに数字列Lの最初のitemを入れる)
for each item in L do(数字列Lから順番にitemを取り出して以下のことを繰り返す)
if item > largest then(もしitemがlargestより大きい時は、次のことを実行)
largest item(largestにitemを入れる)
return largest(終了でlargestを返す)

■アルゴリズム

こういったアルゴリズムAlgorithm)は、プログラムの中核をなす論理構造です。アルゴリズムという概念は、プログラミングの中でもっとも重要です。

アルゴリズムは、数学やコンピュータサイエンス(CS)の研究者たちが長年研究を重ねて発明、発見をしてきたもので、Knuthという科学者がその集大成として本を書いています。プログラマをめざす人には必須の本です。
The Art of Computer Programming

ここでは最も有名なソート(並べ替え)のアルゴリズムもたくさん紹介されていますが、このような本を読むときは、まず自分でロジックを考えることが大切です。もちろん、この本に書かれているアルゴリズムを自分で考え出すことはほぼ不可能ですが、自分ならどのように考えるかという論理的思考が重要です。

歴史に残る有名な科学者もソートの問題に取り組んでいます。フォンノイマンマージソートを考えました。磁気テープしかなかった時代にはこのマージソートを私も仕事でよく使っていました。例えば、売上管理のマスターテープには、今までの売り上げデータが商品名と日付でソートされて入っています。新たな月間の売上伝票には、商品名と日付でソートされてトランザクションとして入っています。この2つのテープから新しいマスターテープを作成するときにマージソートを使うのです。用途に応じて適切なアルゴリズムを選択することが大切です。

■PASCALのプログラムを見る…前に、PASCALのシンタックス

上記の最大値を求めるプログラムを今度はPASCAL言語で書いてみたいと思いますが、その前にPASCALについて説明しましょう。

PASCALのシンタックス(syntax)は、BNFという形式で以下の本にまとめられています。
JensenとWirthのPascal User Manual and Report(第2版, 1978)
Wirth
はPACALを作った科学者です。

文法チェックのプログラムを作成するために考えられたBNF形式の一部をここで紹介します。以下で、「A ::= B」はAをBと定義するという意味です。 [ ] は0回か1回の出現、 { } は0回以上の繰り返しを表します。 ‘ ‘ で囲まれた文字列はそのままコードに書きます。 | は選択肢の区切りです。ここですべてを理解する必要はありませんが、新しい言語を設計する人がこの方式で記述することで、言語の使用者は正確に言語のシンタックスを理解できるのです。

program ::= ‘program’ Identifier ‘;’ block ‘.’
(プログラムは以下のように定義される。program プログラム名; ブロック.)
block ::= [ label_part ] [ const_part ] [ type_part ] [ var_part ] { subroutine } statement_part
(ブロックは以下のように定義される。省略可能なlabel_partなど & 省略可能なsubroutine & statement_part)
label_part ::= ‘label’ label_list ‘;’ label_list ::= Integer { ‘,’ Integer }
const_part ::= ‘const’ const_declaration { const_declaration } const_declaration ::= Identifier ‘=’ constant ‘;’
type_part ::= ‘type’ type_declaration { type_declaration } type_declaration ::= Identifier ‘=’ type ‘;’ type ::= simple_type | array_type | record_type | set_type | file_type | pointer_type | …
var_part ::= ‘var’ var_declaration { var_declaration } var_declaration ::= identifier_list ‘:’ type ‘;’
subroutine ::= ‘procedure’ | ‘function’ sub_decl ‘;’ block ‘;’
statement_part ::= ‘begin’ statement_sequence ‘end’ statement_sequence ::= statement { ‘;’ statement }
statement ::= [ assignment | procedure_call | compound_statement | if_statement | case_statement | while_statement | repeat_statement | for_statement | goto_statement | empty_statement ]
assignment ::= variable ‘:=’ expression
if_statement ::= ‘if’ expression ‘then’ statement [ ‘else’ statement ]
while_statement ::= ‘while’ expression ‘do’ statement
for_statement ::= ‘for’ Identifier ‘:=’ expression ′to′|′downto′ expression ‘do’ statement
repeat_statement ::= ‘repeat’ statement_sequence ‘until’ expression
case_statement ::= ‘case’ expression ‘of’ case_list ‘end’ case_list ::= case_element { ‘,’ case_element } { ‘;’ case_list } case_element ::= constant_list ‘:’ statement
procedure_call ::= Identifier [ ‘(‘ expr_list ‘)’ ]
expression ::= simple_expression [ relop simple_expression ] simple_expression ::= [ sign ] term { addop term } term ::= factor { mulop factor } factor ::= Identifier | number | string | ‘(‘ expression ‘)’ | set_constructor | ‘nil’ | ‘not’ factor | function_call
— relop = ‘=’ | ‘<>’ | ‘<‘ | ‘<=’ | ‘>’ | ‘>=’ — addop = ‘+’ | ‘-‘ | ‘or’ — mulop = ‘*’ | ‘/’ | ‘div’ | ‘mod’ | ‘and’

ここで注目してほしいのは、ブロックblock)のステートメント部分を表現している
statement_part ::= ‘begin’ statement_sequence ‘end’ statement_sequence ::= statement { ‘;’ statement }
ステートメントを表現している
statement ::= [ assignment | procedure_call | compound_statement | if_statement | case_statement | while_statement | repeat_statement | for_statement | goto_statement | empty_statement ]
です。どのような構造キーワードを使うのかがわかります。

また、それらより前にある
type_part ::= ‘type’ type_declaration { type_declaration } type_declaration ::= Identifier ‘=’ type ‘;’ — type ::= simple_type | array_type | record_type | set_type | file_type | pointer_type | … var_part ::= ‘var’ var_declaration { var_declaration } var_declaration ::= identifier_list ‘:’ type ‘;’
によって、すべての変数は、
変数名: データ型
というように、ブロックで使用するために定義しておくことがわかります。また、変数の使われ方およびデータ型が規定されています。

■PASCALのプログラム:最大値をみつける

以下は、この表記に準じた100個までの入力した数字の中から最大値を求めるPASCALのコードです。キーワードが何を表現しているのかがよくわからない人もいると思いますが、プログラミングの基礎編の回で説明することにします。

以下は、基礎がある程度わかっている人向けの内容です。よくわからない人は、ある程度学習が進んでから戻ってきてください。

このプログラムでは、まず何個の数字を入力するのかをユーザが指定できるようにしています。同時に上限を決めておいて、例えば100個までとするなら101個では多すぎるので、そのエラーチェックも入れています。

これを、PASCALのコンパイラを使って実際にPCで動かしてみてください。PASCALのPCへの導入方法は付録1で説明しておきますが、PCにWSLという仮想LINUXを動かす環境を導入するのが簡単です。

program max_val;(プログラム名を宣言)
uses SysUtils;(システムユーティリティを利用可能にする)
var(変数の宣言)
  a: array[1..100] of Integer;(aは100個までの整数を格納できる配列)
  n, i, m: Integer;(n, i, mは整数)
begin
  Write(‘How many items? (1..100): ‘);(ユーザに処理したい個数を尋ねる)
  ReadLn(n);(ユーザーが入力した個数nを読み込む)
  if (n < 1) or (n > 100) then(もしnが1より小さい、または100より大きかったら)
  begin
    WriteLn(‘n is too small or too large’’);(ユーザにエラーメッセージを出す)
    Exit;(プログラムを終了する)
  end;
  WriteLn(‘Number of integer: ‘, n, ‘:’);(ユーザに整数の個数のメッセージを出す)
  for i := 1 to n do(1からnまで繰り返す)
    Read(a[i]);(ユーザが入力した整数を受け取って、aに格納していく)
  m := a[1];([暫定的な最大値として]配列の最初の整数をmに入れる)
  for i := 2 to n do(2からnまで繰り返す)
    if a[i] > m then(もしi番目の整数がmよりも大きかったら)
      m := a[i];(mをその値で更新する)
  WriteLn(‘Max is : ‘, m);(ユーザに最大値のメッセージを出す)
end.

■PASCALとPythonの違い:エラーが見つかるタイミング

ところで、PASCALPythonの大きな違いは、コンパイラ時にエラーを見つけられるか、実行時にしかみつけられないかです。これは、プログラムのバグ(誤り)を少なくなるようにしたいプログラマにとって重要なテーマです。

Pythonでは、変数のデータ型を事前に定義せず、データ型が値の代入時に動的に決まるためエラーが生じやすくなります。例えば、
var3=1
var2=’a’
var1 = var2 + var3
と書くと、実行時にエラーが出ます。
TypeError: can only concatenate str (not “int”) to str

PASCALであればコンパイル時にエラーが見つかるので、実行時のエラーが少なくなります。
program test;
var
  var1, var2, var3 : integer;
begin
  var3 := 1;
  var2 :=  ’a’;
  var1 := var2 + var3;
end.
test-pc.pas(6,9) Error: Incompatible types: got “Char” expected “SmallInt”

■PASCALのプログラム:ソート

PASCALに戻って、ソートプログラムを見てみましょう。プログラムを読みながら何をしているのか考えてみてください。これは、有名なバブルソートというアルゴリズムを表現しています。このコードを見て、なぜこれが優れたアルゴリズムなのか、そのアルゴリズムの魅力を見つけましょう。中心のアルゴリズムは BubbleSortで短いプログラムです。

program max_val;
uses
  SysUtils, Math;
type
  TIntArray = array of Integer;
procedure Swap(var a, b: Integer);
var tmp: Integer;
begin
  tmp := a; a := b; b := tmp;
end;
procedure BubbleSort(var a: TIntArray);
var
  i, j, n: Integer;
  swapped: Boolean;
begin
  n := Length(a);
  for i := 0 to n – 2 do
  begin
    swapped := False;
    for j := 0 to n – 2 – i do
      if a[j] > a[j+1] then
      begin
        Swap(a[j], a[j+1]);
        swapped := True;
      end;
    if not swapped then Exit;
  end;
end;

Pythonによる同様のプログラムは付録2に載せておきますので、比較してみてください。アルゴリズムは同じであることがわかります。

■参考文献

初心者の人は、以下のテキストでPythonを使ったプログラミングの基礎を学ぶことができます。
PythonプログラミングABC』松尾正信 著・寺下陽一 監修 近代科学社

■付録2:PythonによるBubble Sortのプログラム

9行のプログラムです。
def bubble_sort(a: List[int]):
    n = len(a)
    for i in range(n – 1):
        swapped = False
        for j in range(0, n – 1 – i):
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                swapped = True
        if not swapped:
            return

■付録1:PASCALのドキュメント

PASCALの導入方法(linux):
sudo apt update sudo apt install fpc lazarus

マニュアルやWindowやiOSへの導入は、以下のURLを見てください。
https://www.freepascal.org/docs-html/current/prog/prog.html
https://gitlab.com/freepascal.org/fpc

導入後のコンパイルと実行のためのコマンドは、(Linux版)
fpc max_val.pas
./max_val

著者:松尾正信
株式会社京都テキストラボ代表取締役