さめがめを作る(その 2)

補助問題をとく

さめがめを作成する為の補助問題を考えます。補助問題は、それ自身が目的ではなく、それを考える事が他の問題(ここではさめがめを作るということ)をとくたすけになるという望みがあって考えられる問題のことです。もとの問題(さめがめを作ること)が目的であって、補助問題は目的の為の手段であるといえます。

となりのセルの値はなんですか

下図のように縦に 3 つ、横に 3 つセルが並んでおり、それぞれのセルには番号が付けられています。

セルには一つだけ値を格納できます。セルに適当な数値を入れたのが下図です。

このページでは、次の点について考察します。

例えば、5 番のセルを見てください。値として 2 が格納されています。このセルに対して、

があるということを、どのように調べたらよいかを考えましょう。

上図を見れば、その番号がどのような規則で並べられているかがよく分かります。それは、左上すみのセル番号を 1 とし、右に進めば +1、下に進めば +3 だけ増加しています。だから、5 番のセルであれば、自分のセル番号 5 に対して、

とすれば求まります。が、ちょっとまってください。同じことを 4 番のセルに対しても通用するでしょうか。4 番のセルの左隣にはセルなんてありません。つまりは、「隣にはセルが存在するか」ということも調べる必要があります。

どのセルが端にあるか

セル番号を少し変形した下の図を見てください。

どれも x+3*y という形をしています。x と y のペアは一意です。3 という数字はセルが横にいくつ並んでいるかを表しています(すなわち、横に 3 つ並んでいるということ)。

セルの左(右)隣にはセルがあるかの判定は、こんな感じのコードで表せます:

// 左隣にはセルがあるか

const
  MAX_COL_COUNT = 3; // 横に並べられたセルの数
var
  Num: Integer;
  CellIndex: Integer;
begin
  CellIndex := 4; // 知りたいセルの番号
  Num :=  CellIndex mod MAX_COL_COUNT;

  if Num = 1 then
    // 左隣にはセルがない
  else
    // 左隣にはセルがある
end;


// 右隣にはセルがあるか

const
  MAX_COL_COUNT = 3; // 横に並べられたセルの数
var
  Num: Integer;
  CellIndex: Integer;
begin
  CellIndex := 4; // 知りたいセルの番号
  Num := CellIndex mod MAX_COL_COUNT;
  
  if Num = 0 then
    // 右隣にはセルがない
  else
    // 右隣にはセルがある
end;

セルが上、又は下にあるかについても、左右の場合で考えたような事といっしょです。

このとき、カギとなるのは

という 2 つの条件です。この 2 つがあれば、最初に示した図のようにセル番号が並べられているのであれば、解は求まります。

実際に、これをプログラムで書いてみましょう。実行すると以下のようになります。

選択されたセルインデックスに対して、それに隣接するセルが保持する値を TMemo に表示します。セルが存在しない場合は"なし"と表示しています。

Unit1

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Grids, Unit2;

const
  MAX_COL_COUNT = 3;
  MAX_ROW_COUNT = 3;
  MAX_CELL_COUNT = MAX_COL_COUNT*MAX_ROW_COUNT;

  DOES_NOT_EXIST_CELL = -1;

type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure StringGrid1Click(Sender: TObject);
  private
    { Private 宣言 }
    FCells: array[1..MAX_CELL_COUNT] of TCell;
    function GetEastIndex(x: Integer): Integer;
    function GetWestIndex(x: Integer): Integer;
    function GetNorthIndex(x: Integer): Integer;
    function GetSouthIndex(x: Integer): Integer;
    function ExistNeighborCell(x: Integer): Boolean;
    procedure DisplayCell;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  Randomize;
  StringGrid1.ColCount := MAX_COL_COUNT;
  StringGrid1.RowCount := MAX_ROW_COUNT;

  for i := Low(FCells) to High(FCells) do
    FCells[i] := TCell.Create(Random(100));

  DisplayCell;
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
  i: Integer;
begin
  for i := Low(FCells) to High(FCells) do
    FCells[i].Free;
end;

function TForm1.GetEastIndex(x: Integer): Integer;
begin
  if (x mod MAX_COL_COUNT) = 0 then
    Result := DOES_NOT_EXIST_CELL
  else
    Result := x+1;
end;

function TForm1.GetWestIndex(x: Integer): Integer;
begin
  if (x mod MAX_COL_COUNT) = 1 then
    Result := DOES_NOT_EXIST_CELL
  else
    Result := x-1;
end;

function TForm1.GetNorthIndex(x: Integer): Integer;
begin
  if (x-MAX_COL_COUNT) < 1 then
    Result := DOES_NOT_EXIST_CELL
  else
    Result := x-MAX_COL_COUNT;
end;

function TForm1.GetSouthIndex(x: Integer): Integer;
begin
  if (x+MAX_COL_COUNT) > MAX_CELL_COUNT then
    Result := DOES_NOT_EXIST_CELL
  else
    Result := x+MAX_COL_COUNT;
end;

procedure TForm1.DisplayCell;
var
  i, j, Idx: Integer;
begin
  for i := 0 to MAX_ROW_COUNT-1 do
    for j := 0 to MAX_COL_COUNT-1 do begin
      Idx := (j+1) + MAX_COL_COUNT*i;
      StringGrid1.Cells[j, i] := Format('%d: %d', [Idx, FCells[Idx].Num]);
    end;
end;

procedure TForm1.StringGrid1Click(Sender: TObject);

  procedure DisplayNeighborCellInfo(n, s, e, w: string);
  begin
    Memo1.Lines.Add('');
    Memo1.Lines.Add(Format('         North: %s', [n]));
    Memo1.Lines.Add(Format('West: %s           East: %s', [w, e]));
    Memo1.Lines.Add(Format('         South: %s', [s]));
  end;

  function GetNeighborCellInfo(Idx: Integer): string;
  begin
    if ExistNeighborCell(Idx) then
      Result := IntToStr(FCells[Idx].Num)
    else
      Result := 'なし';
  end;

var
  Idx: Integer;
  n, s, e, w: string;
begin
  Idx := (StringGrid1.Col+1) + MAX_COL_COUNT*StringGrid1.Row;
  n := GetNeighborCellInfo(GetNorthIndex(Idx));
  s := GetNeighborCellInfo(GetSouthIndex(Idx));
  e := GetNeighborCellInfo(GetEastIndex(Idx));
  w := GetNeighborCellInfo(GetWestIndex(Idx));

  Memo1.Clear;
  Memo1.Lines.Add('CellIndex: '+IntToStr(Idx));
  DisplayNeighborCellInfo(n, s, e, w);
end;

function TForm1.ExistNeighborCell(x: Integer): Boolean;
begin
  Result := x <> DOES_NOT_EXIST_CELL;
end;

end.

Unit2

unit Unit2;

interface

type
  TCell = class
  private
    FNum: Integer;
  public
    constructor Create(Num: Integer);
    property Num: Integer read FNum;
  end;

implementation

{ TCell }

constructor TCell.Create(Num: Integer);
begin
  FNum := Num;
end;

end.

TCell クラスのインスタンスがセルを表します。

GetXXXIndex が隣り合うセルのインデックスを求めます。これと ExistNeighborCell 関数を組み合わせて、隣にセルが在るかどうかを調べます。たとえば、左隣のセルを調べる場合は、

var
  Idx: Integer;
begin
  Idx := 現在注目しているセルのインデックス

  if ExistNeighborCell(GetWestIndex(Idx)) then
    // 左隣にはセルが在る
  else
    // 左隣にはセルがない
end;

とします。左隣にセルが在る場合、すなわち、ExistNeighborCell 関数が true を返す場合、GetXXXIndex が返す値(隣のセルのインデックス)を使用して、そのセルが保持する値を取得するようにします。

procedure TForm1.StringGrid1Click(Sender: TObject);

  procedure DisplayNeighborCellInfo(n, s, e, w: string);
  begin
    Memo1.Lines.Add('');
    Memo1.Lines.Add(Format('         North: %s', [n]));
    Memo1.Lines.Add(Format('West: %s           East: %s', [w, e]));
    Memo1.Lines.Add(Format('         South: %s', [s]));
  end;

  function GetNeighborCellInfo(Idx: Integer): string;
  begin
    if ExistNeighborCell(Idx) then
      Result := IntToStr(FCells[Idx].Num)
    else
      Result := 'なし';
  end;

var
  Idx: Integer;
  n, s, e, w: string;
begin
  Idx := (StringGrid1.Col+1) + MAX_COL_COUNT*StringGrid1.Row;
  n := GetNeighborCellInfo(GetNorthIndex(Idx));
  s := GetNeighborCellInfo(GetSouthIndex(Idx));
  e := GetNeighborCellInfo(GetEastIndex(Idx));
  w := GetNeighborCellInfo(GetWestIndex(Idx));

  Memo1.Clear;
  Memo1.Lines.Add('CellIndex: '+IntToStr(Idx));
  DisplayNeighborCellInfo(n, s, e, w);
end;

また、サンプルではサイズが縦 3、横 3 となっていますが、これは定数として定義している MAX_COL_COUNT, MAX_ROW_COUNT の値を変更しさえすれば、自由に大きさを変えることが出来る事にも注目してください。

まとめ

「ある任意のセルの隣にセルが存在するかどうか」を調べ、セルが存在するなら、そのセルが保持する値を取得するプログラムを書きました。

ここで学んだ内容は、さめがめを作るに当たって、とても大切な問題解決への最初の糸口を掴んだ事になります。隣のセルが存在し、そのセルの値を取得できるなら「自分のセルと隣のセルが保持する値が等しいか」ということも調べられます。

補足

「さめがめを作る(その 1)」の繰り返しになりますが、ここで紹介したコードは次回(その 3)へは直接持ち越しません。ページごとに、それぞれ単独のプロジェクトでプログラムを作成してください。


up next
Last update: 2004/3/8