身につくC言語22回目 乱数と配列でグループ分け

クラスでグループに分かれるときなど、くじ引きなどさまざまな方法が利用されると思います。

今回はグループに分けるということを、プログラム特有の「乱数」を利用してやっていきます。

今回は22人のクラスを5グループに分けるという状況を考えます。まずは、自分で考えてみましょう。

 

乱数の使い方

まずは乱数の使い方を紹介します。


#include<stdio.h>
#include<stdlib.h>
int main(void)
{
	double a;
	a=rand();
	
	printf("%lf",a);
	return 0;
}

これで数字が出力されたはずです。

乱数を利用するにはstdlib.hをインクルードする必要があります。

しかし、何度やっても同じ数字になるので「ほんとに乱数?」と思うかもしれませんが、ほんとに乱数です。

次は、違う結果にする方法を紹介します。


#include<stdio.h>
#include<stdlib.h>
int main(void)
{
	double a;
	
	srand(2233);
	a=rand();
	
	printf("%lf",a);
	return 0;
}

これで先ほどの数字とは変わっていると思います。

ここではsrand()の中の数字を変更することで、さらに変化を与えることができます。srandの中の数字は乱数のシート番号です。好きな値を入れましょう。

ですが、いちいち変えるものまためんどくさいでしょう。次のような書き方でいちいちソースコードを換えなくても違う値が出てきます。


#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(void)
{
	double a;
	
	srand((unsigned) time(NULL));
	a=rand();
	
	printf("%lf",a);
	return 0;
}

srandの中を見てください。「(unsinged) time(NULL)」と書くことで、実行毎に結果を変えることができます。

stdlib.hのほかに、time.hをインクルーとする必要があるので注意しましょう。

もう一つ、特定の数を出力する方法を紹介します。


#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(void)
{
	int a;
	srand((unsigned) time(NULL));
	
	a=rand()%3;
	
	printf("%d",a);
	return 0;
}

rand()で出てきた数を3でわって、余りを取得しています。

このような書き方で「0,1,2」の整数を取得することができます。

 

クラスをグループに分ける

乱数についても触れたところで、さっそくグループ分けに入っていきます。

まず大まかな流れを考えます。

  • 出席番号を割り振る
  • 1グループあたりの人数を調べる
  • 出席番号ごとにグループ番号を与える
  • シャッフルする
  • 出力

これらを関数化してプログラムを作成していきます。

 

プログラム全体


#include<stdio.h>
#include<stdlib.h>
#include<time.h>

#define PERSON 22
#define GROUP 5
#define TIMES 1000

void attendance_number(int* numbers);			//出席番号を割り振る
void people_check(int* group_person);			//1グループあたりの人数を調べる
void numbering(int* people1,int* group_person);	//出席番号ごとにグループ番号を与える
void shuffle(int* people1);						//シャッフル
void print(int people[2][PERSON]);				//出力

int main(void)
{
	int people[2][PERSON];
	int group_person[GROUP];
	
	srand((unsigned)time(NULL));
	
	attendance_number(people[0]);		//出席番号を割り振る
	people_check(group_person);			//1グループあたりの人数を調べる
	numbering(people[1],group_person);	//出席番号ごとにグループ番号を与える
	shuffle(people[1]);					//シャッフル
	print(people);						//出力
	
	return 0;
}

/*出席番号を割り振る*/
void attendance_number(int* numbers)
{
	int i;
	for(i=0;i<PERSON;i++)
	{
		numbers[i]=i+1;
	}
}

/*1グループあたりの人数を調べる*/
void people_check(int* group_person)
{
	int quotient=PERSON/GROUP;			//商
	int remainder=PERSON%GROUP;			//余り
	int i;
	
	for(i=0;i<GROUP;i++)
	{
		group_person[i]=quotient;
	}
	for(i=0;i<remainder;i++)
	{
		group_person[i]+=1;
	}
}

/*出席番号ごとにグループ番号を与える*/
void numbering(int* people1,int* group_person)
{
	int i,j;
	int count=0;
	for(i=0;i<GROUP;i++)
	{
		for(j=0;j<group_person[i];j++)
		{
			people1[count]=i+1;
			count++;
		}
	}
}

/*シャッフル*/
void shuffle(int* people1)
{
	int num;
	int ran1,ran2;
	int i;
	for(i=0;i<TIMES;i++)
	{
		ran1=rand()%PERSON;
		ran2=rand()%PERSON;
		
		num=people1[ran1];
		people1[ran1]=people1[ran2];
		people1[ran2]=num;
	}
}
/*出力*/
void print(int people[2][PERSON])
{
	int i,j;
	for(i=0;i<2;i++)
	{
		if(i==0)
		printf("出席番号\n");
		else if(i==1)
		printf("チーム番号\n");
		for(j=0;j<PERSON;j++)
		{
			printf("%3d",people[i][j]);
		}
		putchar('\n');
	}
	putchar('\n');
	
	printf("最終出力\n");
	for(i=0;i<GROUP;i++)
	{
		printf("チーム%d: ",i+1);
		for(j=0;j<PERSON;j++)
		{
			if(people[1][j]==i+1)
			printf("%3d",people[0][j]);
		}
		putchar('\n');
	}
}

一気に理解しようとすると難しいので、関数ごとに細かく見てみましょう。

 

main関数


int main(void)
{
	int people[2][PERSON];
	int group_person[GROUP];
	
	srand((unsigned)time(NULL));
	
	attendance_number(people[0]);		//出席番号を割り振る
	people_check(group_person);			//1グループあたりの人数を調べる
	numbering(people[1],group_person);	//出席番号ごとにグループ番号を与える
	shuffle(people[1]);					//シャッフル
	print(people);						//出力
	
	return 0;
}

プログラムにおいて、メイン関数はできるだけシンプルにすると見やすくなります。ということで、main関数に関しては難しくないということがわかるでしょう。

少し解説すると

int people[2][PERSON];

というのは、人数分の配列を二つ用意しろという意味です。people[0][]には出席番号、people[1][]にはグループ番号が入ります。

int group_person[GROUP];

というのは、グループあたりの人数です。今回はGROUP=5なので5つの配列が作成されます。そして、配列の中身は[5,5,4,4,4]となります。

日本語に直すと、
グループ1は5人、2は5人、3は4人、4は4人、5は5人
ということです。

合計はしっかり22になりますね。(5+5+4+4+4)

 

出席番号を割り振る


void attendance_number(int* numbers)
{
	int i;
	for(i=0;i<PERSON;i++)
	{
		numbers[i]=i+1;
	}
}

これも比較的簡単でしょう。

難しいとすれば、引数がポインタになっているところでしょうか。

もちろん、引数を「int numbers[PERSON]」としてもいいですが、少しでもポインタになれるようにとこちらにしました。

 

1グループあたりの人数を調べる


void people_check(int* group_person)
{
	int quotient=PERSON/GROUP;			//商
	int remainder=PERSON%GROUP;			//余り
	int i;
	
	for(i=0;i<GROUP;i++)
	{
		group_person[i]=quotient;
	}
	for(i=0;i<remainder;i++)
	{
		group_person[i]+=1;
	}
}

「人÷グループ」で出てくる数というのは、「1グループあたりの最低限の人数」ということはわかりますか?

この場合は、4です。(22/5=4)
グループ1~グループ5までありますが、そのどのグループも4人以上は確定しているということです。

 

あとはあまりの割り振りです。
今回の余りは2なので、グループ1とグループ2が1人増えます。

よって、最終的はなgroup_person[]の中身は「5,5,4,4,4」となるのです。

 

出席番号ごとにグループ番号を与える


void numbering(int* people1,int* group_person)
{
	int i,j;
	int count=0;
	for(i=0;i<GROUP;i++)
	{
		for(j=0;j<group_person[i];j++)
		{
			people1[count]=i+1;
			count++;
		}
	}
}

この関数では、乱数など関係なしに、機械的にグループ番号を割り振っています。

出席番号1~5はグループ1に、出席番号6~10はグループ2に…というような感じです。

均等に割り振ることはできましたが、これでは公平といえません。ということで、次のシャッフルで「運任せ」にグループを決めます。

 

シャッフル


void shuffle(int* people1)
{
	int num;
	int ran1,ran2;
	int i;
	for(i=0;i<TIMES;i++)
	{
		ran1=rand()%PERSON;
		ran2=rand()%PERSON;
		
		num=people1[ran1];
		people1[ran1]=people1[ran2];
		people1[ran2]=num;
	}
}

これが、今回の一番重要な部分です。

rend()%PERSONで0~PERSONまでの乱数を取得していることがわかります。ここでは、0~21ですね。

num=people1[ran1];
people1[ran1]=people1[ran2];
people1[ran2]=num;

では、乱数で出た値の配列の中身を入れ替えています。
例えば、ran1,ran2=3,6だったとしたら、
people[3]の中身と、people[6]の中身が入れ替わるということです。

1回入れ替えただけでは、全然変わっていないですが、何回も繰り返すことによってごちゃごちゃに混ぜることができます。

繰り返す回数を指定しているのがTIMESです。
#define TIMES 100
の値を変えることで繰り返す数を変えることができます。

 

出力


void print(int people[2][PERSON])
{
	int i,j;
	for(i=0;i<2;i++)
	{
		if(i==0)
		printf("出席番号\n");
		else if(i==1)
		printf("チーム番号\n");
		for(j=0;j<PERSON;j++)
		{
			printf("%3d",people[i][j]);
		}
		putchar('\n');
	}
	putchar('\n');
	
	printf("最終出力\n");
	for(i=0;i<GROUP;i++)
	{
		printf("チーム%d: ",i+1);
		for(j=0;j<PERSON;j++)
		{
			if(people[1][j]==i+1)
			printf("%3d",people[0][j]);
		}
		putchar('\n');
	}
}

あとは出力するだけです。

people[0]には出席番号、people[1]にはグループ番号が入っているということを忘れないようにしましょう。

 

まとめ

たしかに、プログラムの乱数をつかってグループを決めると時間短縮になりますが、やはり自分の手で引くくじ引きが個人的にスリルが味わえるので好きです。

なんていってしまったら、元も子もないんですけれどね。