VB.NETで一次元配列を宣言するならこんな感じ。
Dim X(10) As Integer
これで X(0)~X(9) までの10要素が確保できる。
それをC#で書くならこんな感じ。
int[] X = new int[10];
同じく、これで X[0]~X[9] までの10要素が確保できる。
このようにVB.NETでもC#でも配列の始まりは 0 なのだ。
昔のBasicやVB6や今のVBAならこんなのが使える。
Option Base 0 Option Base 1
これを使い分けると、配列の下限を0あるいは1に設定出来る。
例えばエクセルのデータを扱う場合には、行は1から始まる。
そう言う場合には配列も1から始まるほうがエクセルデータの保管には向いている。
さて、VB.NETやC#でプログラミングをしていても、同じ状況に遭遇する事は良くある。
しかし、配列は0から始まると明言されているのでそれを1に変える事は出来ない。
その為に、配列の0行や0列は使わないようにして1行目以降や1列目以降を使うなどで妥協する人も多い。
まあ、多少メモリが無駄になるが実用上はそう言う作戦でも問題は無い。
しかし、どうにかしてVB.NETやC#において配列の開始インデックスを0以外にする方法が無いのだろうか?
自称、配列の下限をどうにかして変更する事に執念を燃やすワテが、猛烈に調査した結果、驚くべきことに、配列下限を任意の数字に設定するテクニックを発見した。
この記事では、その驚くべき配列作成技術を紹介したい。
VB.NETやC#では常に配列の添え字は 0 から始まる
VB.NETやC#では配列の添え字は 0 から始まり、昔のVB6の時代のようにOption Base 1などの手法で変更出来ないと言う事実は、例えばマイクロソフト社の公式サイトにも明記されている。
例えばこのURLには以下の記述がある。
それがこれだ。
ワンポイント ● for VB6
従来の VB6 では、Option Base ステートメントによって、配列の開始添え字を 0 からではなく 1 に変更できましたが、VB .NET では常に配列の添え字は 0 から始まります。VB .NET では、Option Base ステートメントをサポートしていません。
また、従来の VB6 でサポートしていた添え字の下限と上限を指定する以下の表記も、VB .NET ではサポートしません。
[例] VB6 における添え字の範囲指定(VB .NET はサポートしない)
Dim X(5 To 10) As Integer
引用元 マイクロソフト社のサイト
あかんがな。
VB.NETの配列は0から始まる。こんなにはっきりと書いてある。
VB.NETが0からならC#も0からだ。なぜならVB.NETとC#は兄弟みたいなもんだから。
しかし、ワテは諦めない。
ソフトウェアの世界なんて、所詮人間が作り出した単なる道具みたいなもんだから、それらを組み合わせて工夫すればどうにか出来る。
例えばCでこんな小細工マクロを定義すれば、
#define ARRAY(i) intArr[i-1]
配列もどきの ARRAY() は 1から始まる!
子供騙しと言うか低レベルと言うか、まあ、こんな下らないアイディアなら幾らでも思い付くワテである。
その全ソースコードを紹介しよう。
#define ARRAY(i) intArr[i-1] // こんな小細工マクロを定義すると int main() { int intArr[10]; for (int i = 0; i < 10; i++) { // 通常なら0~9でアクセスする配列を intArr[i] = i; } for (int i = 0; i < 10; i++) { printf("intArr[%d]=%d\n", i, intArr[i]); } for (int i = 1; i <= 10; i++) { // 1~10でアクセス出来る。 ARRAY(i) = i; } for (int i = 1; i <= 10; i++) { printf("ARRAY(%d)=%d\n", i, ARRAY(i)); } return 0; }
まあ、取り敢えず実行してみよう。
intArr[0]=0 intArr[1]=1 intArr[2]=2 intArr[3]=3 intArr[4]=4 intArr[5]=5 intArr[6]=6 intArr[7]=7 intArr[8]=8 intArr[9]=9 ARRAY(1)=1 ARRAY(2)=2 ARRAY(3)=3 ARRAY(4)=4 ARRAY(5)=5 ARRAY(6)=6 ARRAY(7)=7 ARRAY(8)=8 ARRAY(9)=9 ARRAY(10)=10 続行するには何かキーを押してください . . .
たしか昔、FORTRAN(DO I=1, IEND)で書かれたプログラムをi=0から始まるC言語に変換する為にこんな小細工が用いられているのを見た事がある。
さて、本題に戻って、冒頭では VB.NETやC#で配列の下限や上限を任意の値に指定する事が可能だと書いた。
それを示さなくてはならない。
C#で開始添字、終了添字を任意に指定出来る二次元配列を作成する
その驚くべき手法がこれだ。
二次元配列 intArr[i, j] を作成するのだが、その添え字は
i = -2 , -1 , 0 , 1 , 2 の5要素
j = -3 , -2 , -1 , 0 , 1 の5要素
だ。
iとjの要素数はこの例では共に5で同じだが、勿論、異なる値でも良い。
その驚くべきC#コードがこれだ。
C#で開始添字、終了添字を任意に指定出来る整数型二次元配列を作成
class Test3_配列添え字を1以外にする小細工 { public static void test4() { var intArr = make_2dim_int_array(-2, -3, 5, 5); LibClass.二次元配列の中身を表示する(intArr, nameof(intArr)); } public static int[,] make_2dim_int_array( int 配列の1次元目の下限, int 配列の2次元目の下限, int 配列の1次元目の長さ, int 配列の2次元目の長さ) { int[,] dataArray = null; int[] LowerBoundArray = { (配列の1次元目の下限), (配列の2次元目の下限) }; int[] LengthArray = { (配列の1次元目の長さ), (配列の2次元目の長さ) }; dataArray = (int[,])Array.CreateInstance(Type.GetType("System.Int32"), LengthArray, LowerBoundArray); return dataArray; } }
test4() を実行してみよう。
なお、配列の中身を表示する関数を即席で作成してみた。
class LibClass { public static void 二次元配列の中身を表示する(int[,] intArr, string arrName) { string typeOfVariable = intArr.GetType().ToString();//"System.Int32[,]" string space = new String(' ', arrName.Length + 1); Console.Write(space + " j ="); for (int j = intArr.GetLowerBound(1); j <= intArr.GetUpperBound(1); j++) { Console.Write("{0,9:d}", j); } Console.WriteLine(); Console.Write(space + "i "); for (int j = intArr.GetLowerBound(1); j <= intArr.GetUpperBound(1); j++) { Console.Write("{0,9:s}", "-----"); } Console.WriteLine(""); for (int i = intArr.GetLowerBound(0); i <= intArr.GetUpperBound(0); i++) { Console.Write("{0}[{1}, j]=", arrName, i); for (int j = intArr.GetLowerBound(1); j <= intArr.GetUpperBound(1); j++) { Console.Write("{0,9:d}", intArr[i, j]); } Console.WriteLine(); } Console.WriteLine(); } }
test4() の実行結果は以下の通り。
どんなもんじゃい!
j = -3 -2 -1 0 1 i ----- ----- ----- ----- ----- intArr[-2, j]= 0 0 0 0 0 intArr[-1, j]= 0 0 0 0 0 intArr[ 0, j]= 0 0 0 0 0 intArr[ 1, j]= 0 0 0 0 0 intArr[ 2, j]= 0 0 0 0 0 続行するには何かキーを押してください . . .
まあ配列添え字がマイナスから始まるなんて言うこんなヘンテコな配列を使う場面は無いかも知れないが、添え字を0や1にして配列を作成出来ると言う点では画期的だ。
なお、この手法は知っている人は知っているが、ワテの知る限り、世間一般に広く知られている手法では無いと思う。
何でかな?
少々癖のある手法ではあるが、別に使っちゃいけない手法でも無いと思うし。
それにも拘わらずマイクロソフト社としては、冒頭に引用したように、VB.NETやC#では配列は0から始まり、1から始まる配列は作れないと明記している。
う~ん、何でそんなに0から始めたいのかが分からない。
上に示したArray.CreateInstance() の手法で任意の開始添え字が指定出来るのに、なんで宣伝しないのだろう。
分からん。
C#で開始添字、終了添字を任意に指定出来るジェネリック型二次元配列を作成
先ほどのサンプルプログラムだと二次元の整数型配列の作成のみの機能だった。
それだと汎用性が乏しくて使い勝手が悪い。
ここはC#のジェネリックの機能を使って汎用性のある関数に作り替えてみた。
それがこれだ!
public static T[,] make_2dim_array_of_T<T>( int 配列の1次元目の下限, int 配列の2次元目の下限, int 配列の1次元目の長さ, int 配列の2次元目の長さ) { T[,] dataArray = null; int[] LowerBoundArray = { (配列の1次元目の下限), (配列の2次元目の下限) }; int[] LengthArray = { (配列の1次元目の長さ), (配列の2次元目の長さ) }; string typeNameStr = typeof(T).ToString(); dataArray = (T[,])Array.CreateInstance(typeof(T), LengthArray, LowerBoundArray); return dataArray; }
上のコードでは、関数make_2dim_array_of_T<T>( )の定義に、今まで登場しなかった <T> と言う記述がある。
まあその部分は <T> でなくても <A> でも <Wareko> でも何でも良い。
要するに呼び出し側で <int> などの型を指定してこのジェネリック型引数を持つ関数を使うと、関数にはそのint型の情報も来るので、この例ではint型の二次元配列を作成する。
<string>で関数を呼び出すと、文字列型の二次元配列を作成する。
そう言う機能がジェネリックだ。C++ならtemplateかな。
また、複数のジェネリック引数を与える事も出来るので、例えば二つなら <T, U> などが良く使われる。
さて、本題に戻って、実際に <string> 型を指定して二次元配列を作成する場合はこんな風に呼び出せば良い。
var strArr = Test3_配列添え字を1以外にする小細工.make_2dim_array_of_T<string>(-2, -1, rLen, cLen);
どう?便利でしょ?
なお、先ほど紹介した配列の中身を表示する関数、
public static void 二次元配列の中身を表示する(int[,] intArr, string arrName)
は、引数が整数型の二次元配列に限定されている。
例えばこの関数もジェネリック<T> で書き換えて、int以外に string とか doubleなどの二次元配列に関しても、中身を表示する機能に書き換える事も可能だ(と思う)。
やる気のある人は、試しにそう言うジェネリック関数を作成してみると良いだろう。
また或いは、今回紹介したのは二次元配列版だが、一次元配列版、三次元配列版なども作ってみると良い。
VB.NET版
上で紹介したC#版のコードをVB.NETに変換してみた。
C#もVB.NETも共に.NET Framework基盤で動くが、両者は互いに変換可能なのだ。
インターネット上にも無料の C# ⇔ VB.NET 相互変換のオンラインサービスを提供するサイトは沢山ある。
例えば、http://converter.telerik.com/ など。
配列添え字を1以外にするC#コードをVB.NETに変換した
Module Module1 Sub Main() Test3_配列添え字を1以外にする小細工.test4() End Sub Class Test3_配列添え字を1以外にする小細工 Public Shared Sub test4() Dim intArr = make_2dim_int_array(-2, -3, 5, 5) LibClass.二次元配列の中身を表示する(intArr, NameOf(intArr)) Dim strArr = make_2dim_array_of_T(Of String)(-2, -1, 5, 5) End Sub Public Shared Function make_2dim_int_array(ByVal 配列の1次元目の下限 As Integer, ByVal 配列の2次元目の下限 As Integer, ByVal 配列の1次元目の長さ As Integer, ByVal 配列の2次元目の長さ As Integer) As Integer(,) Dim dataArray As Integer(,) = Nothing Dim LowerBoundArray As Integer() = {(配列の1次元目の下限), (配列の2次元目の下限)} Dim LengthArray As Integer() = {(配列の1次元目の長さ), (配列の2次元目の長さ)} dataArray = CType(Array.CreateInstance(Type.[GetType]("System.Int32"), LengthArray, LowerBoundArray), Integer(,)) Return dataArray End Function Public Shared Function make_2dim_array_of_T(Of T)(ByVal 配列の1次元目の下限 As Integer, ByVal 配列の2次元目の下限 As Integer, ByVal 配列の1次元目の長さ As Integer, ByVal 配列の2次元目の長さ As Integer) As T(,) Dim dataArray As T(,) = Nothing Dim LowerBoundArray As Integer() = {(配列の1次元目の下限), (配列の2次元目の下限)} Dim LengthArray As Integer() = {(配列の1次元目の長さ), (配列の2次元目の長さ)} Dim typeNameStr As String = GetType(T).ToString() dataArray = CType(Array.CreateInstance(GetType(T), LengthArray, LowerBoundArray), T(,)) Return dataArray End Function End Class Class LibClass Public Shared Sub 二次元配列の中身を表示する(ByVal intArr As Integer(,), ByVal arrName As String) Dim typeOfVariable As String = intArr.[GetType]().ToString() Dim space As String = New String(" "c, arrName.Length + 1) Console.Write(space & " j =") For j As Integer = intArr.GetLowerBound(1) To intArr.GetUpperBound(1) Console.Write("{0,9:d}", j) Next Console.WriteLine() Console.Write(space & "i ") For j As Integer = intArr.GetLowerBound(1) To intArr.GetUpperBound(1) Console.Write("{0,9:s}", "-----") Next Console.WriteLine("") For i As Integer = intArr.GetLowerBound(0) To intArr.GetUpperBound(0) Console.Write("{0}[{1}, j]=", arrName, i) For j As Integer = intArr.GetLowerBound(1) To intArr.GetUpperBound(1) Console.Write("{0,9:d}", intArr(i, j)) Next Console.WriteLine() Next Console.WriteLine() End Sub End Class End Module
その実行結果は以下の通り。
j = -3 -2 -1 0 1 i ----- ----- ----- ----- ----- intArr[-2, j]= 0 0 0 0 0 intArr[-1, j]= 0 0 0 0 0 intArr[ 0, j]= 0 0 0 0 0 intArr[ 1, j]= 0 0 0 0 0 intArr[ 2, j]= 0 0 0 0 0 続行するには何かキーを押してください . . .
冒頭で紹介したC#版と同じ結果になった。
なお、このVB.NETコードはC#版を単純に変換して生成しただけなので、詳細な動作確認はしていない。
でもまあ、多分問題無く動くとは思うが。
VB.NET版コードの説明
Public Shared Sub test4() Dim intArr = make_2dim_int_array(-2, -3, 5, 5) LibClass.二次元配列の中身を表示する(intArr, NameOf(intArr)) Dim strArr = make_2dim_array_of_T(Of String)(-2, -1, 5, 5) End Sub
まずは、int型の二次元配列を作成して、その中身をズラズラと表示する。
次に、ジェネリック版関数で String型を指定して二次元配列を作成している。
その中身を表示する関数は作っていないので、皆さん各自作って下さい。
完成したらコメント欄からお知らせ下さい。
まとめ
配列添え字が0に固定されているC#やVB.NETの仕様が嫌いなワテであるが、仕様ならしようがない。
ちなみに「しょうがない」は本来、「仕様がない」と書くのが正しい。
と言う事は、マイクロソフト社の仕様があるので仕様がない。
なんのこっちゃ。
もう訳分からんので今日はおしまい。
C#、VB.NET、FORTRAN、C、C++のワテ推薦図書
FORTRANやるなら並列化してCPUをバリバリ使いまくるのが男だ!いや女でも。
C++でも高速化を目指したい。
かなり安値で本を買う(本以外も買えます)
2017年のゴールデンウイークを利用して便利なWEBサイトを作ってみた。
名付けて、
何が出来るかと言うと、
Amazon.co.jp
楽天市場
ヤフーショッピング
の三つのショッピングサイトを同時検索して、商品を価格の安い順に表示出来るお買い物支援サイトだ。
「最安価格サーチ」で、
「Androidプログラミング」をかなり安値で探したい人は こちらから >
「Xamarinプログラミング」をかなり安値で探したい人は こちらから >
「C#プログラミング」をかなり安値で探したい人は こちらから >
「Visual Studioプログラミング」をかなり安値で探したい人は こちらから >
もしお使い頂きまして何かご不明な点、改善案などありましたらお知らせ下さい。
コメント