ソートに挑戦しよう その1

ソートとは、数字を何らかの規則に従って並べることをいいます。今回は、単純に数値を小さい順に並べて見ましょう。並べる方法は、いくつかあります。ここでは、直接選択法というのをやってみたいと思います。

やり方は簡単で、とりあえずソートしたい数値を一つの配列として最初に格納しておきます。まだ、何もしていませんので、数値の並び方はバラバラのままです。 とりあえす最初の数値から番号をつけていきます。最初の数値を0番として、以下1番、2番、3番・・・と、しておきます。 最初に0番の数値を一番小さいと仮定しておきます。もちろん実際には、その後に並んでいる数字に小さいのがあるかもしれませんので、あくまで仮定です。 まずはじめに、0番以降の数値の中から一番小さい数を見つけて、それを0番の数値と交換します。(もちろん、もし0番の数値が一番小さかったら、交換はしません)これで、0番には確実に一番小さいのが入りました。 ここで、とりあえず0番のことはもう考えません。0番はほっときます。 次に1番以降のことを考えます。 また、さっきやったことと同じように今度は1番を一番小さい数と仮定します。そして、2番以降の中で一番小さかった数を1番と交換します。(もちろん2番目以降の数で見つかった数より1番の数のほうが小さかったら交換はしません)これで1番が一番小さい数になりました。(0番のことは、考えてませんので注意してください。)ここで、0番の時と同じように1番のことも考えません。ほって置きます。今度は、2番以降についてやって行きます。また、2番目を一番小さい数と仮定して、3番目以降から一番小さい数を・・・・。と繰り返して行きます。最終的には、全体を通して、小さい順に並んでいるというわけです。

では、さっそくプログラムを書いてみましょう。 今回は、Editに入力した数値をソートして、ラベルに表示するというようにしたいと思います。 まず、フォームにラベルとEditを5個ずつ貼り付けて下さい。こんな感じになります。

プログラムはこんな感じになりました。

procedure TForm1.Button1Click(Sender: TObject);
var
  i, j, min, soeji, temp: Integer;
  Arr: array[0..4] of Integer;
begin
  Arr[0] := StrToInt(Edit1.Text);
  Arr[1] := StrToInt(Edit2.Text);
  Arr[2] := StrToInt(Edit3.Text);
  Arr[3] := StrToInt(Edit4.Text);
  Arr[4] := StrToInt(Edit5.Text);

  for i := 0 to 4 do
  begin
    min := Arr[i]; // とりあえず一番最初の値を最小とする
    soeji := i;

    for j := i + 1 to 4 do
    begin
      if (Arr[j] < min) then // 小さいのが見つかれば、交換
      begin
        min := Arr[j];
        soeji := j;
      end;
    end;
    temp := Arr[i]; // 交換します
    Arr[i] := Arr[soeji];
    Arr[soeji] := temp;
  end;

  Label1.Caption := IntToStr(Arr[0]);
  Label2.Caption := IntToStr(Arr[1]);
  Label3.Caption := IntToStr(Arr[2]);
  Label4.Caption := IntToStr(Arr[3]);
  Label5.Caption := IntToStr(Arr[4]);

  ShowMessage('ソート完了しました');
end;

二つの値を交換するときに、ここでは、まず一時変数tempに一方の数値を代入して、そしてもう片方の数値をtempに代入した方の変数に代入して、最後にtempから値をもらって交換を成立させています。文章でみると、ややこしくなりますがプログラムで見た場合、分かりやすいと思います。

Edit に文字列が入力された時の処理は?

上記のプログラムの場合、Edit に文字列が入力された場合には例外が生成されます。そこで型変換に失敗した時は、失敗したこと知らせるメッセージを表示して再度ユーザーに整数を入力させるようにして見ましょう。それには、文字列から整数に型変換する際に、StrToInt ではなく TryStrToInt を使ってやります。この TryStrToInt は、与えられた文字列が整数に変換できた場合には True が返され、整数に変換できない時は、False が返されるようになっています。これを用いて、上のプログラムを改良してみます。

新たに ListBox をフォームに貼り付けて下さい。

procedure TForm1.Button3Click(Sender: TObject);
var
  i, j, min, soeji, temp: Integer;
  Arr: array[0..4] of Integer;
begin
  // Edit に入力されたものが整数に変換できない場合には、
  // TryStrToInt は False を返します。
  // not が付いていますので、True は False になり、
  // False は、True になります。
  // つまり、not を付けると、真偽が逆になる事になります。
  if (not TryStrToInt(Edit1.Text, Arr[0])) or
     (not TryStrToInt(Edit2.Text, Arr[1])) or
     (not TryStrToInt(Edit3.Text, Arr[2])) or
     (not TryStrToInt(Edit4.Text, Arr[3])) or
     (not TryStrToInt(Edit5.Text, Arr[4])) then
  begin
    ShowMessage('整数へ変換できませんでした。');
    exit; // 手続きから抜け出します。
          // つまり、これ以降に続くプログラムコードは実行されません。
  end;

  for i := 0 to 4 do
  begin
    min := Arr[i];
    soeji := i;

    for j := i+1 to 4 do
    begin
      if (Arr[j] < min) then
      begin
        min := Arr[j];
        soeji := j;
      end;
    end;
    temp := Arr[i];
    Arr[i] := Arr[soeji];
    Arr[soeji] := temp;
  end;

  for i := 0 to 4 do
    ListBox1.Items.Add(IntToStr(Arr[i]));
end;

TryStrToInt は2つのパラメータをとります。第1パラメータには、整数に変換したい文字列を、第2パラメータには、整数に変換された値が代入されます。

これで、整数に変換できない文字が入力された場合、そのメッセージを表示させる事が出来ました。再度「整数を入力してください」と、メッセージを表示してユーザに再び入力のやり直しをさせる事が出来ます。また、exit はコメントに書いてある様に手続きから抜け出す際に使用します。ループから抜け出す時は、break を用い、手続きから抜け出すには exit を使うと覚えておくと便利かもしれません。

ここでもう一度、TryStrToInt を使用している部分を見てみましょう。

if (not TryStrToInt(Edit1.Text, Arr[0])) or
   (not TryStrToInt(Edit2.Text, Arr[1])) or
   (not TryStrToInt(Edit3.Text, Arr[2])) or
   (not TryStrToInt(Edit4.Text, Arr[3])) or
   (not TryStrToInt(Edit5.Text, Arr[4])) then

どれも同じようなコードになっています。Edit の添え字と、配列 Arr の添え字が異なるだけで、後は同じです。for 文で何とか出来そうに見えます。ですが、コンポーネントである Edit の添え字を、for 文ではちょっと扱えないように見えます。

そんな時は、FindComponent を使ってやります。FindComponent で TEdit のName プロパティをパラメータとして渡してやると、その指定した Name プロパティを持つコンポーネントにアクセスする事が出来ます。具体的には、以下のようにして使います。また、ソートを実行している部分を別の関数にしました。

procedure MySort(var Arr: array of Integer);
var
  i, j, min, soeji, temp: Integer;
begin
  for i := Low(Arr) to High(Arr) do
  begin
    min := Arr[i];
    soeji := i;

    for j := i+1 to 4 do
    begin
      if (Arr[j] < min) then
      begin
        min := Arr[j];
        soeji := j;
      end;
    end;

    temp := Arr[i];
    Arr[i] := Arr[soeji];
    Arr[soeji] := temp;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  NumArr: array[0..4] of Integer;
  EditText: string;
  i: Integer;
begin
  for i := 0 to 4 do
  begin
    // TEdit コンポーネント(Edit1〜Edit5)の Text プロパティを
    // EditText に代入しています。
    EditText := TEdit(FindComponent('Edit'+IntToStr(i+1))).Text;
    if not TryStrToInt(EditText, NumArr[i]) then
    begin
      ShowMessage('不正な文字がありました。');
      exit;
    end;
  end;

  MySort(NumArr); // ソートします。

  for i := 0 to 4 do
    ListBox1.Items.Add(IntToStr(NumArr[i]));
end;

FindComponent に渡す引数は、Name プロパティですので注意してください。(※Name プロパティについては、「Caption と Name プロパティ」を参考にして下さい。)

Name プロパティに規則性がない場合は?

今回の場合、Edit の Name プロパティが Edit1, Edit2..Edit5 となっており、Edit(x) のように x を添え字とし利用することができましたが、例えば Edit の Name プロパティに規則性がない場合には、どのようにしたら良いのでしょうか。

Edit の Name プロパティに規則性がない場合には、フォームに貼り付けてある TEdit コンポーネントを見つけてから、その TEdit の Text プロパティにアクセスしてやる方法があります。具体的には以下のようになります。

procedure MySort(var Arr: array of Integer);
var
  i, j, min, soeji, temp: Integer;
begin
  for i := Low(Arr) to High(Arr) do
  begin
    min := Arr[i];
    soeji := i;

    for j := i+1 to 4 do
    begin
      if (Arr[j] < min) then
      begin
        min := Arr[j];
        soeji := j;
      end;
    end;

    temp := Arr[i];
    Arr[i] := Arr[soeji];
    Arr[soeji] := temp;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  NumArr: array[0..4] of Integer;
  i, index: Integer;
begin
  index := 0;

  for i := 0 to ComponentCount-1 do
  begin
    if Components[i] is TEdit then  // TEdit コンポーネントなら
    begin
      if not TryStrToInt(TEdit(Components[i]).Text, NumArr[index]) then
      begin
        ShowMessage('不正な文字がありました。');
        exit;
      end;
      Inc(index);
    end;
  end;

  MySort(NumArr); // ソートします。

  for i := 0 to 4 do
    ListBox1.Items.Add(IntToStr(NumArr[i]));
end;

ComponentCount は、貼り付けてあるコンポーネントの総数を取得する事が出来ます。Components は、コンポーネントの配列になっています。ここでは、この配列には、TButton と TEdit が格納されている事になります。(Label がフォームに貼り付けてあるならラベルもこの配列に格納されています。)そして、貼り付けてあるコンポーネントを1つずつ TEdit かどうかを調べ、TEdit であるなら、その Text プロパティを取得し、その値を TryStrToInt に渡して整数に変換し、配列 NumArr にその変換された値を代入しています。(整数に変換できない場合は、その旨メッセージを表示し、exit で手続きを終了しています。)

別の用途で使用する Edit がある場合は?

なんとか Name プロパティに規則性がない場合でも、うまく切り抜けられました。ただ今回の場合、フォームに貼り付けてある Edit は整数を入力する為に存在する Edit だけしかありませんが、もし、それ以外の為に使用される Edit がフォームに貼り付けてあった場合は、不具合が発生することになります。ですから、上記の場合でもうまくいかないことになります。これをうまく切り抜けるには、一つの方法として Tag プロパティを使う方法があります。整数を入力する為に存在する Edit には Tag プロパティに適当な数値を代入して、それ以外の Edit とは差別化をはかり区別してやります。Tag プロパティの使用方法は、こちらを参考にして下さい。


up next
Last update: 2002/10/16