2009年12月22日火曜日

RとC言語で関数ポインタ:書き直し

以前書いたソースコードの焼き直しをしてみました。
今度は中央値の計算も実装してみました。

#include <stdio.h>
#include <string.h>
#include <stdlib.h> // qsortに必要

double mean(double d[],int n)
{
 int i;
 double sum = 0.0;

 for( i=0 ; i<n ; i++ ){
  sum += d[i];
 }
 return sum/n;
}

// qsort用の関数.qsortに渡せる形の関数を作成する必要がある.
// 仮引数や返り値の形式は指定されていることに注意.
int doublecmp(const void *a,const void *b){
 return( *(double *)a - *(double *)b );
}

double median(double d[],int n)
{
 int i;
 double dCpy[n];

 // コピーを作成するのは元データの順序保持のため
 // 今回の統計量を計算するだけのコードでは不要ではあるが.
 for( i=0 ; i<n ; i++ ){
  dCpy[i] = d[i];
}

 qsort(dCpy,n,sizeof(double),doublecmp);

 if( n % 2 ){// 奇数
  return dCpy[n/2];
 }else{// 偶数
  return (dCpy[n/2] + dCpy[n/2 -1])/2.0;
 }
}

double min(double d[],int n)
{
 int i;
 int idx = 0; // 最小値の添字
 for( i=0 ; i<n ; i++ ){
  if( d[idx] > d[i] ){
   idx = i;
  }
 }
 return d[idx];
}

double max(double d[],int n)
{
 int i;
 int idx = 0; // 最大値の添字
 for( i=0 ; i<n ; i++ ){
  if( d[idx] < d[i] ){
   idx = i;
  }
 }
 return d[idx];
}

/*
double (*p[])(double [],int)={mean,median,min,max,NULL};
char *cp[]={"mean","median","min","max",NULL};
*/

typedef struct funcs{
 double (*p)(double [],int);// 関数へのポインタ
 char *cp;// 入力の文字列と比較する関数を表す文字列へのポインタ
}FUNCS;

FUNCS f[]={
 {mean,"mean"},
 {median,"median"},
 {min,"min"},
 {max,"max"},
 {NULL,NULL}
};// ここで一元管理.最後のNULLは終端を示す番兵

double (*setfunc(char *str))(double d[],int n)
{
 int i;

 /*
 for( i=0 ; cp[i] != NULL ; i++ ){
  if( !strcmp(cp[i],str) ){
   return p[i];
  }
 }
 */
 for( i=0 ; f[i].cp != NULL ; i++ ){
  if( !strcmp(f[i].cp,str) ){
   return f[i].p;
  }
 }
 printf("error in setfunc\n");
 return NULL; // 入力の文字列が不適切な場合
}

int main(void)
{
 double (*f)(double d[],int n);
 double a[10]={0,1,2,3,4,5,6,7,8,19};
 f = setfunc("mean");
 printf("%lf\n",f(a,10));
 f = setfunc("min");
 printf("%lf\n",f(a,10));
 f = setfunc("max");
 printf("%lf\n",f(a,10));

 return 0;
}


gccによるコンパイル後の実行結果は、以下の通り。

mean   = 5.500000
median = 4.500000
min    = 0.000000
max    = 19.000000


構造体を用いて管理することによって、関数を表す文字列と関数がまとまり、
対応が分かりやすくなって、バグが出にくくなったのではないかと思う。

ライブラリのqsortを利用しているが、本来は全体をソートする必要はなく、
およそ途中までのソートができていれば良いので、そのあたりは工夫できるかも。

2009年10月17日土曜日

C++のコピーコンストラクタ

C++のコピーコンストラクタのありがたみに関する調査.
コピーコンストラクタが役に立つのは以下の3つの場合である.

・宣言文でオブジェクトを使用して,他のオブジェクトを宣言する
・関数に仮引数としてオブジェクトを渡すとき
・関数の戻り値として使用する一時オブジェクトを作成したとき

例えば,仮引数にクラスcarのオブジェクトを渡す
void printobj(car obj){ ... }
の場合は,コピーコンストラクタなしでは,正確なコピーを行うので,
ポインタ変数があると,関数を抜けるときに,デストラクタが呼ばれて
メモリの解放が行われる.
さらに関数を呼んだ側でデストラクタを呼ぶと,ポインタ変数の指す先のメモリは
既に解放された状態であるから,ここで二重解放なってしまう.

二重解放がまずい理由は,
ttp://d.hatena.ne.jp/aki-yam/20081205/1228490763
によると,2つのポインタ変数が同じアドレスを指しているときに
その症状が現れるということらしい.

int *p1 = new int;// エラーチェックは省略
*p1 = 100;
delete p1;
int *p2 = new int;
delete p1;//ここで二重解放
*p2 = 200;
ここで p1 == p2だとすると厄介ということである.

他にも例があるかもしれないが,これだけでも不味いということが
分かったのでよしとしよう.

正確なコピーを作らせないように挙動を制御するのが,
コピーコンストラクタの役割である.

2009年10月15日木曜日

C++のアクセス制御

C++でクラスに必ず出てくる,
public,private,protected,virtualあたりを
自分なりにまとめてみた.継承に注目して書いてみる.
間違っていたら是非指摘していただきたい.

publicで継承
基本クラスのアクセス制御が、そのまま派生クラスに引き継がれる.
基本クラスのpublicな部分は、派生クラスでもpublicのまま.
基本クラスのprivateもprotectedな部分もそのままになる.

privateで継承
基本クラスを全部privateにしてしまう.
派生クラスを呼び出す外部からは、アクセスできない.ただし,
派生クラス自身から基本クラスのpublicな部分へのアクセス自体は可能である.

基本クラスのprotected
基本クラス自身と派生クラス以外からはアクセスできない.

virtual
B <- d1 <- D(Bが間接基本クラスで,Dが派生クラス)
B <- d2 <- D
と継承すると,DからBを用いようとすると,どちらから継承した
Bを用いるのか分からず,エラーになる.それを避けるために,
virtualをつけて,ひとまとめにして曖昧さを避けることができる.

2009年10月8日木曜日

RとC言語で関数ポインタ

Rで関数ポインタの動作というものを確認したことがあります.
具体的な問題設定は,複数の入力文字列に対応させて,
データの入ったテーブルの各列で統計量を計算するというものでした.
(例えば1列目は最小値を、2列目は平均値...のように列ごとに設定する)
まずはRで書いた,文字列で統計量を計算する関数を返す
ソースコードを載せます.

setfunc <- function(type){
 if( type == "min" ){
  return(min)
 }else if( type == "max" ){
  return(max)
 }else if( type == "median" ){
  return(median)
 }else if( type == "mean" ){
  return(mean)
 }
}


d <- c(1:10,rep(10,5))
d
f <- setfunc("mean")
f(d)
f <- setfunc("max")
f(d)
f <- setfunc("median")
f(d)
f <- setfunc("n")
f(d)
この実行結果は下記のようになる。
> d <- c(1:10,rep(10,5))
> d
[1]  1  2  3  4  5  6  7  8  9 10 10 10 10 10 10
> f <- setfunc("mean")
> f(d)
[1] 7
> f <- setfunc("max")
> f(d)
[1] 10
> f <- setfunc("median")
> f(d)
[1] 8
> f <- setfunc("n")
> f(d)
エラー:  関数 "f" を見つけることができませんでした 
これをCで実装したらどうなるかとhttp://ujihisa.blogspot.com/ を書いているujihisaさんとskypeで2時間余り議論して,ようやく動いたのがこれ. medianについては未実装で,実行結果もありませんがご容赦を.
#include <stdio.h>
#include <string.h>

double mean(double d[],int n)
{
 int i;
 double sum = 0.0;
 double *dp;

 for( i=0 ; i<n ; i++ ){
  sum += d[i];
 }
 return sum/n;
}

double median(double d[],int n)
{
 return -1;
}

double min(double d[],int n)
{
 int i;
 int idx = 0; // 最小値の添字
 for( i=0 ; i<n ; i++ ){
  if( d[idx] > d[i] ){
   idx = i;
  }
 }
 return d[idx];
}

double max(double d[],int n)
{
 int i;
 int idx = 0; // 最大値の添字
 for( i=0 ; i<n ; i++ ){
  if( d[idx] < d[i] ){
   idx = i;
  }
 }
 return d[idx];
}


double (*p[])(double [],int)={mean,median,min,max,NULL};
char *cp[]={"mean","median","min","max",NULL};


double (*setfunc(char *str))(double d[],int n)
{
 int i;

 for( i=0 ; cp[i] != NULL ; i++ ){
  if( !strcmp(cp[i],str) ){
   return p[i];
  }
 }
 printf("error in setfunc\n");
 return NULL; // 文字列に一致したものがなかった
}

int main(void)
{
 double (*f)(double d[],int n);
 double a[10]={0,1,2,3,4,5,6,7,8,9.0};
 f = setfunc("mean");
 printf("%lf\n",f(a,10));
 f = setfunc("min");
 printf("%lf\n",f(a,10));
 f = setfunc("max");
 printf("%lf\n",f(a,10));

 return 0;
}

すぐに思いつく改良点は,
・*pと*cpをバラバラではなく,構造体でまとめて持たせる
・長さについては,入力エラーチェックが必要.
といったところでしょうか.また,改良して議論したいと思います.

今日の結論としては,Rと比較すると,C言語の場合はプログラマがポインタをいちいち作らないといけないので,非常に面倒で,かつ間違えやすいということです.
言語の特徴を知って,実装に最適なものを選べということですね.

追記(2011/04)
上記の改良、および中央値の実装を行いました。
http://entertainment-lab.blogspot.com/2009/12/rc.html

2009年10月5日月曜日

sweep関数に関しての調査

Rのプログラミングに関する本を読んでいて,
sweep関数の挙動が文章の説明だけでは分からなかった.
ぐぐってみるとRjpWikiにあったので,調査してみた.

基本的な使い方は
sweep(x, MARGIN, STATS, FUN="-", check.margin=TRUE, ...)
となっていて,配列に対し,MARGINの方向にSTATSを用いてFUNを施す
という意味のようである.デフォルトでは引き算になるとのこと.
check.marginは次元のチェックをしてくれる.FALSEにするとわずかに速くなるが,
次元が一致していることをすでに知ってなければ使わないようにと書いてある.
基本はTRUEにしておけということらしい.

sweepというと一掃するいう意味で,試合で相手をシャットアウトしたときに
用いられるよね.語感と少し違うよう関数のような気がする.

それはさておき,実際に動作を試してみよう.
x <- matrix(1:16,nrow=4)
y <- 1:nrow(x)
sweep(x,1,y)
sweep(x,2,y)
sweep(x,1,y,"/")
としてみると,実行結果は
> x
[,1] [,2] [,3] [,4]
[1,]    1    5    9   13
[2,]    2    6   10   14
[3,]    3    7   11   15
[4,]    4    8   12   16
> sweep(x,1,y)
[,1] [,2] [,3] [,4]
[1,]    0    4    8   12
[2,]    0    4    8   12
[3,]    0    4    8   12
[4,]    0    4    8   12
> sweep(x,2,y)
[,1] [,2] [,3] [,4]
[1,]    0    3    6    9
[2,]    1    4    7   10
[3,]    2    5    8   11
[4,]    3    6    9   12
> sweep(x,1,y,"/")
[,1]     [,2]     [,3] [,4]
[1,]    1 5.000000 9.000000   13
[2,]    1 3.000000 5.000000    7
[3,]    1 2.333333 3.666667    5
[4,]    1 2.000000 3.000000    4
となった.
2番目の結果では行ごとに1から4が引かれていて,
3番目の結果では列ごとに1から4が引かれている.
MARGINで1を指定すると行方向,2を指定すると列方向に演算が
行われている.この辺はapply関数に良く似ていると思う.

また最後の例では,デフォルトの引き算から割り算に変えてみると,
行ごとにそれぞれ1から4で割られていることが分かる.

文章と実際の例があると分かりやすいですね.

2009年10月3日土曜日

aggregate関数の動作に関する調査

Rを用いてグループごとに集計したいという場合に用いるものに,aggregate関数があるらしい.今まではインデックスでグループごとに切り出して操作していた(フィルタという)が,それよりも楽に作業ができそうなので,どのような挙動になるか調べてみた.

基本的な使い方はヘルプによると,

aggregate(x, by, FUN, ...)

となっている.「データxをリスト構造のbyのグループごとに,関数FUNで統計量としてまとめる」という使い方のようである.FUNはスカラを返すものでないとダメらしい.早速

example(aggregate)

にあった例の一部を引っ張り出して

aggregate(state.x77, by = list(Region = state.region), FUN=mean)
aggregate(state.x77, by = list(Region = state.region,Cold = state.x77[,"Frost"] > 130), FUN=mean)

で動作を確認してみる.アメリカの州ごとの国勢調査のデータから,各地域の平均値が得られるはずである.結果は,

> aggregate(state.x77, by = list(Region = state.region), FUN=mean)
Region Population   Income Illiteracy Life Exp    Murder  HS Grad    Frost      Area
1     Northeast   5495.111 4570.222   1.000000 71.26444  4.722222 53.96667 132.7778  18141.00
2         South   4208.125 4011.938   1.737500 69.70625 10.581250 44.34375  64.6250  54605.12
3 North Central   4803.000 4611.083   0.700000 71.76667  5.275000 54.51667 138.8333  62652.00
4          West   2915.308 4702.615   1.023077 71.23462  7.215385 62.00000 102.1538 134463.00

> aggregate(state.x77, by = list(Region = state.region,Cold = state.x77[,"Frost"] > 130), FUN=mean)
Region  Cold Population   Income Illiteracy Life Exp    Murder  HS Grad    Frost      Area
1     Northeast FALSE  8802.8000 4780.400  1.1800000 71.12800  5.580000 52.06000 110.6000  21838.60
2         South FALSE  4208.1250 4011.938  1.7375000 69.70625 10.581250 44.34375  64.6250  54605.12
3 North Central FALSE  7233.8333 4633.333  0.7833333 70.95667  8.283333 53.36667 120.0000  56736.50
4          West FALSE  4582.5714 4550.143  1.2571429 71.70000  6.828571 60.11429  51.0000  91863.71
5     Northeast  TRUE  1360.5000 4307.500  0.7750000 71.43500  3.650000 56.35000 160.5000  13519.00
6 North Central  TRUE  2372.1667 4588.833  0.6166667 72.57667  2.266667 55.66667 157.6667  68567.50
7          West  TRUE   970.1667 4880.500  0.7500000 70.69167  7.666667 64.20000 161.8333 184162.17

となった.リストの中に論理式が入った場合の例が興味深い.また,Southに分類される州でCold=TRUEとなるものはないということがすぐ分かる.

比較のため,面倒だがインデックス操作で挙動を確認してみる.

> apply(state.x77[state.region == "Northeast",],2,mean)
Population       Income   Illiteracy     Life Exp       Murder      HS Grad        Frost         Area 
5495.111111  4570.222222     1.000000    71.264444     4.722222    53.966667   132.777778 18141.000000

結果の一行目に一致していることが確認できた.逐一インデックス操作で切り出していたらスクリプトを書くのも面倒なので,今後はaggregateを使おう.

2009年9月29日火曜日

FizzBuzz問題

今回は腕試しにFizzBuzz問題をR言語を用いて解いてみた.どこかでコンピュータサイエンスの学科を卒業した連中でも,この問題がさらっと解けるか怪しいのが多いという記事を読んだのだが,本当なのだろうかと疑います.

FizzBuzz <- function(n){
if( n < 1 ){
return(-1)
}
strs <- character(n)
for( i in 1:n ){
if( i %% 3 == 0  && i %% 5 == 0 ){
strs[i] <- "FizzBuzz"
}else if( i %% 3 == 0  ){
strs[i] <- "Fizz"
}else if( i %% 5 == 0 ){
strs[i] <- "Buzz"
}else{
strs[i] <- as.character(i)
}
}
return(strs)
}

こうしておいて,いくつかの入力を試してみると,

> FizzBuzz(-3)
[1] -1
> FizzBuzz(20)
[1] "1"        "2"        "Fizz"     "4"        "Buzz"     "Fizz"    
[7] "7"        "8"        "Fizz"     "Buzz"     "11"       "Fizz"    
[13] "13"       "14"       "FizzBuzz" "16"       "17"       "Fizz"    
[19] "19"       "Buzz"    

となって,無事できてそうな雰囲気である.入力が文字列型ならばエラーで落ちてくれるのでOKとしておこう.

2009年9月26日土曜日

this is a test posting

This is a test posting.

#include <stdio.h>

int main()
{
printf("Hello world!");
return 0;
}

Wow! source code's color changed!

フォロワー

ページビューの合計