ホーム » 2015 » 7月 » 07

日別アーカイブ: 2015年7月7日

2015年7月
« 6月   8月 »
 1234
567891011
12131415161718
19202122232425
262728293031  

最近の投稿(電子情報)

アーカイブ

カテゴリー

集合の処理

リスト構造は、必要に応じてメモリを確保するデータ構造であり、データ件数に依存しないプログラム が記述できる。その応用として、集合処理を考えてみる。

2進数を用いた集合計算

リストによる集合の前に、もっと簡単な集合処理を考える。 データ件数の上限が少ない場合には、2進数を用いるとC言語のビット演算命令で 和集合、積集合が考えられるので、簡単となる。

以下のプログラムは、0〜31の数字を2進数の各ビットに対応付けし、 ba = {1,2,3} , bb = {2,4,6} , bc= {4,6,9} を要素として持つ集合で、ba ∩ bb , bb ∩ bc の計算を行う例である。

void bit_print( unsigned int x ) {
for( int i = 0 ; i < 32 ; i++ )
if ( (x & (1 << i)) != 0 )
printf( "%d " , i ) ;
printf( "\n" ) ;
}
void main() {
unsigned int ba = (1<<1) | (1<<2) | (1<<3) ; // ba = {1,2,3}
unsigned int bb = (1<<2) | (1<<4) | (1<<6) ; // bb = {2,4,6}
unsigned int bc = (1<<4) | (1<<6) | (1<<9) ; // bc = {4,6,9}
bit_print( ba & bb ) ; // ba ∩ bb
bit_print( bb & bc ) ; // bb ∩ bc
bit_print( ba | bc ) ; // ba ∪ bc
}

このような、2進数を用いた処理で有名なものとして、エラトステネスのふるいによる素数がある。 このアルゴリズムでは、各bitを整数に対応付けし、 素数で無いと判断した2進数の各桁に1の目印をつけていく方式である。

unsigned int prime = 0 ;
void filter() {
for( int i = 2 ; i < 32 ; i++ ) {
if ( (prime & (1 << i)) == 0 ) {
for( int j = i + i ; j < 32 ; j += i )
prime |= (1 << j) ;
}
}
for( int i = 2 ; i < 32 ; i++ ) {
if ( (prime & (1 << i)) == 0 )
printf( "%d\n" , i ) ;
}
}

リスト処理による積集合

前述の方法は、リストに含まれる/含まれないを、2進数の0/1で表現する方式である。 しかし、2進数であれば、unsigned int で 32要素、unsigned long long int で 64 要素が上限となってしまう。 (32bitコンピュータ,gccの場合)

しかし、リスト構造であれば、リストの要素として扱うことで、要素件数は自由に扱える。 また、今までの授業で説明してきた cons() などを使って表現すれば、 簡単なプログラムでリストの処理が記述できる。

// 先週までに説明してきたリスト構造と補助関数
struct List {
int     data ;
struct List* next ;
} ;
struct List* cons( int x , struct List* n ) {
struct List* ans ;
ans = (struct List*)malloc( sizeof( struct List ) ) ;
if ( ans != NULL ) {
ans->data = x ;
ans->next = n ;
}
return ans ;
}
void print( struct List* p ) {
for( ; p != NULL ; p = p->next ) {
printf( "%d " , p->data ) ;
}
printf( "\n" ) ;
}
int find( struct List* p , int key ) {
for( ; p != NULL ; p = p->next )
if ( p->data == key )
return 1 ;
return 0 ;
}

例えば、積集合(a ∩ b)を求めるのであれば、リストa の各要素が、リストb の中に含まれるか find 関数でチェックし、 含まれたものだけを、ans に加えていく…という考えでプログラムを作ると以下のようになる。

struct List* set_prod( struct List* a , struct List* b ) {
struct List* ans = NULL ;
for( ; a != NULL ; a = a->next ) {
if ( find( b , a->data ) )
ans = cons( a->data , ans ) ;
}
return ans ;
}
void main() {
struct List* a = cons( 1 , cons( 2 , cons( 3 , NULL ) ) ) ;
struct List* b = cons( 2 , cons( 4 , cons( 6 , NULL ) ) ) ;
struct List* c = cons( 4 , cons( 6 , cons( 9 , NULL ) ) ) ;
print( set_prod( a , b ) ) ;
print( set_prod( b , c ) ) ;
}

例題として、和集合、差集合などを考えてみよう。

リストの共有と削除の問題

リスト処理では、mallocを使うが、メモリリークをさせないためには、使用後のリストの廃棄は重要である。 リストの全要素を捨てる処理であれば、以下のようになるであろう。

void list_free( struct List* p ) {
while( p != NULL ) {
struct List* d = p ;
p = p->next ;
free( d ) ; // 順序に注意
}
}

一方、前説明の和集合のプログラムを以下のように作った場合、list_freeの処理は問題となる。

struct List* set_union( struct List*a , struct List*b )
{
struct List* ans = b ;
for( ; a != NULL ; a = a->next )
if ( !find( b , a->data ) )
ans = cons( a->data , ans ) ;
return ans ;
}
void main() {
struct List*a = cons( 1 , cons( 2 , cons( 3 , NULL ) ) ) ;
struct List*b = cons( 2 , cons( 3 , cons( 4 , NULL ) ) ) ;
struct List*c = set_union( a , b ) ;
// a,b,cを使った処理
// 処理が終わったので、a,b,cを捨てる
list_free( a ) ;
list_free( b ) ;
list_free( c ) ;
// c = { 1 , (bのリスト) }
// (b)の部分は先のlist_free(b)で解放済み
}

UML構造図

UMLの構造図の書き方の説明。 詳しくは、参考ページのUML入門などが、分かりやすい。

クラス図

クラス図は、構造図の中の基本的な図で、 枠の中に、上段:クラス名、中段:属性(要素)、下段:メソッド(関数)を記載する。 属性やメソッドの可視性を示す場合は、"-":private、"+":public、"#":protected 可視性に応じて、"+-#"などを記載する。

関連

クラスが他のクラスと関係がある場合には、その関係の意味に応じて、直線や矢印で結ぶ。
(a)関連:単純に関係がある場合、
(b)集約:部品として持つが弱い結びつき。関係先が消滅しても別に存在可能。
(c)コンポジション:部品として持つが強い結びつき。関係先と一緒に消滅。
(d)依存:依存関係にあるだけ
(e)派生:派生・継承した関係
(f)実現: Javaでのinterfaceによる多重継承

上図の例では、乗り物クラスVehicleから自動車がCarが派生し、 自動車は、エンジン(Engine)を部品として持つ。エンジンは車体と一緒に廃棄なら、コンポジションで実装する。
自動車は、同じく車輪(Wheel)を4つ持つが、自動車を廃棄してもタイヤは別に使うかもしれないので、集約で実装する。 集約で実装する場合は、C++などであれば、ポインタで部品を持ち、部品の廃棄(delete)は、別に行うことになる。

is-a 、has-a の関係

前の課題でのFigureクラスで、Color 情報をどう扱うべきかで、悩んだ場合と同じように、 クラスの設計を行う場合には、部品として持つのか、継承として機能を持つのか悩む場合がある。 この場合には、"is-a"の関係"has-a"の関係で考えると、部品なのか継承なのか判断しやすい。

たとえば、上の乗り物(Vehicle)クラスと、車(Car)のクラスは、"Car is-a Vehicle" といえるので、is-a の関係。 "Car is-a Engine"と表現すると、おかしいことが判る。 車(Car)とエンジン(Engine)のクラスは、"Car has-a Engine"といえるので、has-a の関係となる。 このことから、CarはVehicleからの派生であり、Carの属性としてEngineを部品として持つ設計となる。

オブジェクト図

クラス図だけで表現すると、複雑なクラス関係では、イメージが分かりづらい場合がでてくる。 この場合、具体的な値を図に書き込んだオブジェクトで表現すると、説明がしやすい場合がある。 このように具体的な値で記述するクラス図は、オブジェクト図と言う。 書き方としては、クラス名の下に下線を引き、中段の属性の所には具体的な値を書き込んで示す。

その他の構成図

その他の構成図としては、コンポーネント図(物理的な構成要素から、システムの構造を表現する図)、 配置図(ハードウェアとアプリケーションの関係を図示したもの)、パッケージ図(パッケージ同士の関係をグループ化した図) なども用いる。