プロが教える店舗&オフィスのセキュリティ対策術

こんにちは。タイトルが少々分かりにくくてすいません。

プログラミングで、どう実現したらいいのか分からない処理があります。

まず、任意の配列Aがあります。ここでは倍精度実数の2次元配列とし、A(i,j)と表します。
その2次元配列の各要素を使って、計算を行いたいものとします。
ここでは単純に、全ての成分を足し集める計算を考えます。
つまり∑(A(i,j))を求めたいということです。

ここで、同様に倍精度実数2次元配列のB(i,j)があるとします。
このB(i,j)に関しても∑(B(i,j))を求めたい時、当然この処理をサブルーチン(またはユーザー定義関数)としてまとめたいと思います。

ですが、このやり方がよく分かりません。
計算したい配列を、サブルーチンの引数にする必要があると思います。
しかしサブルーチン内で具体的に処理を記述する部分で、どうしたらいいのか分かりません。
A(i,j)を足し集める、というループ処理を書いたら、B(i,j)が引数の時には対応出来ません。

内部にC(i,j)というローカル変数を作って、A(i,j)もしくはB(i,j)を内部でC(i,j)に渡してから、C(i,j)を足し集める計算をするのが一つの手でしょうか?
しかしこのサブルーチンが頻繁に繰り返し呼び出される場合、毎度メモリーコピーの作業が生じるのは
処理が遅くなってしまうのでは?という不安があります。

ポインタを使えばいいのでしょうか?引数では配列のアドレスを引っ張ってきて、そのアドレスに対して処理をする・・・というような感じでしょうか。
お恥ずかしながらこれまでポインタを使ったことがないため、これで出来るのかが分かりません。

今回は単純な成分の足し算という例でしたが、もう少し具体的には、
二次元配列の成分i,jが座標を表し、そこに収納されている値を使って、いろいろな計算を行いたいということです。
その計算が頻繁に行われるため、出来るだけスマートかつ高速に動作するものを作りたいと思っています。

ちなみに言語はFortran90/95で、ちょっとマニアックですが、恐らく概念自体は他の言語でも同じだと思うので、Fortranが分からない方でも構いませんのでアドバイスをお願いします。

もしFortranが分かる方がいらっしゃれば、今回の例において、具体的なサンプルプログラムをいただけると非常に助かります。もちろん、無くても構いませんのでよろしくお願いします。

質問者からの補足コメント

  • つらい・・・

    すいません、やはり例が単純過ぎましたので、もしサンプルプログラムを提供してくれる方は、以下の処理が行えるプログラムを書いて下さると嬉しいです。(やり方の概念のみの説明でも、構いません)
    「配列A(i,j)、B(i,j)・・・がある時、任意の成分i,jの値の隣り合う4点の和を出力するようなサブルーチン」
    つまりカレンダーの並びが配列に収納されているとして、任意の月(これが配列A,Bに相当)、任意の日付(これが成分i,jに相当)の周囲4点の和を計算するようなサブルーチン(またはユーザー定義関数)です。
    端っこは周囲4点の計算が出来ないのでは?などということはとりあえず無視して構いません。よろしくお願いします。
    Fortranでなくても、Cならギリギリ分かります。

      補足日時:2015/12/20 15:17

A 回答 (4件)

#2です。


>書き忘れで申し訳ないのですが、実は配列A,Bはサイズが異なる可能性もあります。
>ただ、サイズは明示的に定義しますので、サブルーチンの引数として配列サイズを渡すことは可能です。
>このような場合にも、似たアルゴリズムで同じことの実現は可能でしょうか?

サイズの異なる2次元配列を処理するサブルーチン(sub3)をFortranで作ってみました。
5,2の配列と3,6の配列をsub3に渡し、その内容を印字しています。
(配列の渡し方と受け取り方が、この課題なので値の和は求めていません。)
------------------------------------------
program c120622
integer, parameter :: r_lim=5
integer, parameter :: c_lim=2
integer, parameter :: r_lim2=3
integer, parameter :: c_lim2=6
real(8) x(r_lim,c_lim)
real(8) x2(r_lim2,c_lim2)
do i=1,r_lim
do j=1,c_lim
x(i,j)= 10 * i + j
enddo
enddo
do i=1,r_lim2
do j=1,c_lim2
x2(i,j)= 100 * i + j
enddo
enddo

write(*,*) "----x(5,2)----"
call sub3(x,r_lim,c_lim)
write(*,*) "----x2(3,6)----"
call sub3(x2,r_lim2,c_lim2)
end program c120622

subroutine sub3(c,n,m)
real(8) c(n,m)
do i=1,n
do j=1,m
write(*,*) i,j,c(i,j)
enddo
enddo
end subroutine sub3

ーーーーーーーーーーーーーーーーーーーーーー
以下、実行結果です、
----x(5,2)----
1 1 11.000000000000000
1 2 12.000000000000000
2 1 21.000000000000000
2 2 22.000000000000000
3 1 31.000000000000000
3 2 32.000000000000000
4 1 41.000000000000000
4 2 42.000000000000000
5 1 51.000000000000000
5 2 52.000000000000000
----x2(3,6)----
1 1 101.00000000000000
1 2 102.00000000000000
1 3 103.00000000000000
1 4 104.00000000000000
1 5 105.00000000000000
1 6 106.00000000000000
2 1 201.00000000000000
2 2 202.00000000000000
2 3 203.00000000000000
2 4 204.00000000000000
2 5 205.00000000000000
2 6 206.00000000000000
3 1 301.00000000000000
3 2 302.00000000000000
3 3 303.00000000000000
3 4 304.00000000000000
3 5 305.00000000000000
3 6 306.00000000000000

fortranは、10年以上前のことなので、忘れていますが、たぶん、正しく処理できていると思います。
    • good
    • 0

なんとなくですが、以下のような感じになるのでは・・・?


ただ、計算結果の戻し方がわかりません。


real A(10,20)
real B(20,30)

call sub(A, 10, 20)
call sub(B, 20, 30)

subroutine sub(C,n,m)
real C(n, m)
real kekka
do i=1,n
do j=1,m
 kekka = kekka + C(i, j)
enddo
enddo
end subroutine sub
    • good
    • 0

>A(i,j)を足し集める、というループ処理を書いたら、B(i,j)が引数の時には対応出来ません。


サブルーチン側では、Aが渡されれば、Aとして計算し、Bが渡されればBとして計算します。

>つまりカレンダーの並びが配列に収納されているとして、任意の月(これが配列A,Bに相当)、任意の日付(これが成分i,jに相当)の周4点の和を計算するようなサブルーチン(またはユーザー定義関数)です。

これを作ってみました。
get_waがサブルーチンですが、Aが渡されれば、Aとして計算し、Bが渡されればBとして計算します。
-----------------------------------------
#include <stdio.h>

double get_wa(double arrayX[12][31],int i,int j)
{
double sum = 0;
if ((i - 1) >= 0) sum += arrayX[i-1][j];
if ((i + 1) < 31) sum += arrayX[i+1][j];
if ((j - 1) >= 0) sum += arrayX[i][j-1];
if ((j + 1) < 31) sum += arrayX[i][j+1];
printf("get_wa内で印字:i=%d j=%d sum=%f\n",i,j,sum);
return sum;
}

main ()
{
int i,j;
double suma;
double sumb;
double arrayA[12][31];
double arrayB[12][31];
//配列に値を設定する
for(i = 0; i < 12;i++){
for(j = 0; j < 31;j++){
arrayA[i][j] = i*10 + j;
arrayB[i][j] = i*100 + j;
}
}
suma = get_wa(arrayA,2,3);
sumb = get_wa(arrayB,2,3);
printf("get_waの結果:suma=%f sumb=%f\n",suma,sumb);
suma = get_wa(arrayA,0,0);
sumb = get_wa(arrayB,0,0);
printf("get_waの結果:suma=%f sumb=%f\n",suma,sumb);
}
---------------------------------------------------
以下実行結果です。
get_wa内で印字:i=2 j=3 sum=92.000000
get_wa内で印字:i=2 j=3 sum=812.000000
get_waの結果:suma=92.000000 sumb=812.000000
get_wa内で印字:i=0 j=0 sum=11.000000
get_wa内で印字:i=0 j=0 sum=101.000000
get_waの結果:suma=11.000000 sumb=101.000000
ーーーーーーーーーーーーーーーーーーーーーーーー
arrayAとarrayBは、格納する値を変えています。
i,jの周囲の4点、つまり、以下の4点の和を計算しています。端っこは、計算しないようにしています。
i-1,j
i+1,j
i,j-1
i,j+1
-------------------------------------------
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
アドレスなどの概念を使わずとも、配列を丸ごとサブルーチンに渡せるのでしょうか?
型とサイズは一致している必要がありそうですが・・・
書き忘れで申し訳ないのですが、実は配列A,Bはサイズが異なる可能性もあります。
ただ、サイズは明示的に定義しますので、サブルーチンの引数として配列サイズを渡すことは可能です。
このような場合にも、似たアルゴリズムで同じことの実現は可能でしょうか?
今、Cの動作環境が無くて確認できず、申し訳ありません。

ありがとうございました。

お礼日時:2015/12/20 18:51

配列の型がすべて同一であるのであれば


関数のパラメータを
配列の先頭アドレス,i要素数,j要素数 とすれば いいだけの話だと思いますけど
(質問タイトルのまんまだけど)

Fortranは昔触ったことあるけどすっかり忘れ去ってるのでCで書きますが
雑なサンプル(未確認なのとネストわかりやすくするため 大文字・小文字・全角が入り乱れてます)
main()

  double A[10][20];
  double B[30][10];

  sub(A,10,20);
  sub(B,30,10);


sub(double *addr, int i, int j)
{
  double Total=0.0;

for(int y=0; y<i; y ++)
  {
   for(int x=0; x<j; x ++)
    {
      //データの総計
      Total =Total + *(addr+(y * x) + x) ;
      //各配列要素までの総計データでバッファ内容を更新
      *(addr+(y * x) + x) = Total ;
    }
  }

といったような形で扱えます
    • good
    • 0
この回答へのお礼

丁寧な回答ありがとうございました。
*(addr+(y * x) + x)という部分は、*()の中身がメモリにおける先頭アドレスからの距離でしょうか?*()を付与する事で、アドレスの中身を具体的に参照すると。
C言語では、A[0][0]~A[9][0]までが連続したアドレスに収納され、次のアドレスにA[0][1]が入っていると解釈すれば、そうなりそうです。
どこかで、Fortranはこの順番が、C言語と逆だと聞いたので、その場合はx,yを逆にすれば良いのでしょうか。

もう一つ、
sub(A,10,20);
の部分は
sub(&A,10,20);
でなくて構わないのでしょうか?
サブルーチン内でaddrはポインタとして宣言されているのだと思います。
ポインタにアドレスを渡すときには
addr=&A; のような記述をすると思うのですが、サブルーチンに引数を渡す時には、事情は違うのでしょうか?

また、回答待ちの間に少しFortranについて調べていたのですが、どうもFortran90/95では&や*に相当する演算子が無いみたいで、どう教えていただいたプログラムをFortranに移植しようか迷っているところです・・・
Fortranではあくまでポインタはある変数の「別名」として使えるだけみたいで、アドレスを直接的にいじる方法が、無さそうなのです。
つまり、Cでいうところの
double *ptr;
double data;
ptr=&data;
として、以後は*ptr(結局dataに収録された数字であって、dataの別名として*ptrを使う)を使えるだけで、ptr(アドレスの値)を参照する方法が、多分ですが、無い感じです。
もし何かお気づきの点ありましたらよろしくお願いします。
分かりにくい点、私の理解が足りない点、多々あると思います。申し訳ありません。

ありがとうございました。

お礼日時:2015/12/20 18:40

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!