数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
こめかみ

数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#1

投稿記事 by こめかみ » 16年前

取り組んでいた問題の回答が自分のアプローチと違ったので、頭のモヤモヤが晴れません。
課題の指示の意味を汲み取りきれなかった自分が悪いのですが、
せっかくだから自分のやり方で最後までやってみたいと思っているので、
アドバイスよろしくお願いします。

【質問】
数学でいう組み合わせの問題の解き方で、
再帰ではなく
約分を使って解きたい(実装したい)のですがどのように取り組めばよいでしょうか。

今の自作ソースだと10とか20とか二桁台の入力でcombination関数内のjとkの値が膨大になって、
int型だと正常な結果が得られません。(jとkをint型にすることを目指しています)
課題を解きたいのではなく自分で考えた計算式を解きたいので、
題意を満たしていないとか言わないでいただければありがたいです。


以下は、正式な回答と、自分で解きかけた問題(約分ではなく力技)です。
参考までに、自分の解きかけた問題は変数の中身が見えるようにしたものも添付しておきます。

環境はwindowsXPでcygwinです。

正答(再起使用)
/*
異なるnこの整数からrこの整数を取り出す組み合わせの数nCrを求める関数
	int combination(int n, int r){}
を作成せよ。なおnCrは以下のように定義される。
	nCr = (n-1)C(r-1) + (n-1)Cr (ただし、nC0 = nCn= 1,、nC1 = n)
*/

#include <stdio.h>

int combination(int n, int r)//※仮引数はint型でないといけない
{
	if(r==0||r==n)
		return (1);
	else if(r == 1);
		return(n);
	return (combination(n - 1, r - 1) + combination(n - 1, r));
}

int main()
{
	int i, j;

	puts("異なるn個からr個の整数の組み合わせの数を求めます.");
	printf("n:");	scanf("%d", &i);
	printf("r:");	scanf("%d", &j);
	printf("組み合わせの数は%dです.\n", combination( i, j) );

	return 0;
}

自力ソース
/*
異なるnこの整数からrこの整数を取り出す組み合わせの数nCrを求める関数
	int combination(int n, int r){}
を作成せよ。なおnCrは以下のように定義される。
	nCr = (n-1)C(r-1) + (n-1)Cr (ただし、nC0 = nCn= 1,、nC1 = n)
*/

#include <stdio.h>

int combination(int n, int r)//仮引数はint型でないといけない
{
	int i;//ループ用変数
	double j=1;//分子
	double k=1;//分母
	//int bunshi[n],bunbo[[/url];//なんとなく書いてみたのですが、配列の宣言に変数は使えないのですよね。

	//(n-r)がr未満のときとr以上のときとそれ以外、
	if( ( (n-r)<r ) && (n>=r) ){
		for(i=r; i>0; --i){
				j *= (n--);
				k *= (r--);
		}
	}else if( ( (n-r)>=r ) && (n>=r) ){
		for(i=n; i>r; --i){
				j *= (n--);
		}
		for(i=r; i>0; --i){
				k *= (r--);
		}
	}else{
		puts("n>=rで入力してください.");
	}
	return (j/k);
}

int main()
{
	int i, j;

	puts("異なるn個からr個の整数の組み合わせの数を求めます.");
	printf("n:");	scanf("%d", &i);
	printf("r:");	scanf("%d", &j);
	printf("組み合わせの数は%dです.\n", combination( i, j) );

	return 0;
}

説明のために画像使うとファイルの添付ができないようですね。
(画像内の"="一つ余分でしたすみません)
(それ以前に字が汚くてすみません、数学の記号がうまく表せている自信が無かったので手書きしてみました)
添付は改めてさせていただきます。(あまり意味は無いかも知れませんが、一応)

よろしくお願いします。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#2

投稿記事 by こめかみ » 16年前

変数の中身が見えるようにしたものはこちらです。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#3

投稿記事 by こめかみ » 16年前

添付ファイルの

//for(i=n; i>r; --i)だと無限ループになるのは何故?

のコメントは間違いです。すみません。

mats

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#4

投稿記事 by mats » 16年前

焼け石に水程度かもしれませんが、nCr = nC(n-r) を利用して、rが大きい場合に数を減らしてjやkの負担を減らすのも一つの手かと思います

上の例ですと、10C6 = 10C4 とすることでjが151,200 から5040まで減ります


> 今の自作ソースだと10とか20とか二桁台の入力でcombination関数内のjとkの値が膨大になって、
> int型だと正常な結果が得られません。(jとkをint型にすることを目指しています)

jとkはint型じゃなければいけないのでしょうか?
long型や、(gccなら使えるはずなので)long long型にするだけでも大分違いそうな気がしますが・・・


的外れな事を言っていたらすいません;;;

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#5

投稿記事 by こめかみ » 16年前

matsさんありがとうございます。

>nCr = nC(n-r) を利用して、rが大きい場合に数を減らしてjやkの負担を減らす
実は上のソースでもうやっています。
わかりにくいですよね、コメントを書くべきでした、ごめんなさい・

/(n-r)がr未満のときとr以上のときとそれ以外、
if( ( (n-r)<r ) && (n>=r) ){
for(i=r; i>0; --i){
j *= (n--);
k *= (r--);
}
}else if( ( (n-r)>=r ) && (n>=r) ){//←nCr = nC(n-r)
for(i=n; i>r; --i){
j *= (n--);
}
for(i=r; i>0; --i){
k *= (r--);
}
}

>jとkはint型じゃなければいけないのでしょうか?
>long型や、(gccなら使えるはずなので)long long型にするだけでも大分違いそう
それはそうなのですが型の大きさを変えるだけだとどうもイタチゴッコな気がするので、
約分なら根本的な解決になるのではと思い、試みています。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#6

投稿記事 by こめかみ » 16年前

ん?逆かもしれない、
ちょっと検証しなおします。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#7

投稿記事 by こめかみ » 16年前

添付するファイルが間違ってました正常動作しませんね、ごめんなさい。
今急いで正常にコンパイルできるのを探してます。

mats

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#8

投稿記事 by mats » 16年前

> >nCr = nC(n-r) を利用して、rが大きい場合に数を減らしてjやkの負担を減らす
> 実は上のソースでもうやっています。

うわ、恥ずかしい・・・
すいません、ちゃんと読めてませんでした;;;


> //int bunshi[n],bunbo[[/url];
約分を目指すなら、出てきた数字はやはり覚えておく必要がありそうですね
mallocを使用すれば動的に配列を確保できるので、それで bunshi[[/url], bunbo[[/url]を確保してあげて
bunshi[[/url]とbunbo[[/url]で共通の約数を探して約分、
bunbo[[/url]の中身がすべて1になったらbunshi[[/url]をすべて掛けた値を返す、といったところですかね・・・?

もっと頭のいい方法がありそうな気もしますがちょっと思いつかないです;

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#9

投稿記事 by こめかみ » 16年前

>すいません、ちゃんと読めてませんでした;;;
こちらこそすみません、意図した動作をしてませんでした。

ここにとりあえず当初の狙い通りのファイルを添付しますが、
nCr = nC(n-r)
は実装できてません。

明日があるので今日はもう切り上げます、また明日(今日?)よろしくお願いします。

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#10

投稿記事 by たいちう » 16年前

添付ファイルの方は見てないけど、面白そうなのでとりあえず約分してみました。
もっと美しく書けそうなもんだけどね。
// 最小公倍数
int GCD(int u, int v)
{
	int t;
	while (u > 0) {
		if (u < v) {
			t = u;
			u = v;
			v = t;
		}
		u -= v;
	}
	return v;
}

int Combination(int n, int r)
{
	int i, numerator[N] = { 0 }, denominator[N] = { 0 };
	int indexN, indexD;
	int result;

	// 初期値セット
	for (i = 0; i < r; i++) {
		numerator = n - i;
		denominator = i + 1;
	}

	// 約分
	indexD = 0;
	while (denominator[indexD]) {
		indexN = 0;
		while (denominator[indexD] > 1) {
			int gcd = GCD(denominator[indexD], numerator[indexN]);
			if (gcd > 1) {
				denominator[indexD] /= gcd;
				numerator[indexN] /= gcd;
			} else
				indexN++;
		}
		indexD++;
	}

	//	掛け算
	result = 1;
	for (i = 0; i < r; i++)
		result *= numerator;

	return result;
}

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#11

投稿記事 by たいちう » 16年前

Nの定義を貼るのを忘れてましたので、一応追記。
#define N 100

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#12

投稿記事 by こめかみ » 16年前

※すぐ下の投稿に再掲しました。

これが昨日の段階での正しいベストソースです。

同じような名前でバックアップとりまくってたら、
どれが正しいファイルなのかわからなくなってました;

※すぐ下の投稿に再掲しました。


>>たいちうさん

ありがとうございます。

これからじっくり参考にさせていただきます。
なにやら見慣れぬ英語の変数だらけでワタクシ辞書引きまくりなのですが。

あと、どうでもいいですが私のその手持ちの辞書では、
GCDは最大公約数とあるのですが。書き間違いですよね?


>>matsさん
>mallocを使用すれば動的に配列を確保できるので

挑戦してみます。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#13

投稿記事 by こめかみ » 16年前

preで失敬しました。再掲します。
#include <stdio.h>

int combination(int n, int r)//仮引数はint型でないといけない
{
	int i;//ループ用変数
	double j=1;//分子
	double k=1;//分母
	//int bunshi[n],bunbo[[/url];//なんとなく書いてみたのですが、配列の宣言に変数は使えないのですよね。

	//(n-r)がr未満のときとr以上のときとそれ以外、
	if( ( (n-r)>=r ) && (n>=r) ){
		for(i=r; i>0; --i){
				j *= (n--);
				k *= (r--);
		}
	}else if( ( (n-r)<r ) && (n>=r) ){//←nCr = nC(n-r)
		for(i=n; i>r; --i){
				j *= (n--);
		}
		for(i=(n-r); i>0; --i){
				k *= (r--);
		}
	}else{
		puts("n>=rで入力してください.");
	}
	return (j/k);
}

int main()
{
	int i, j;

	puts("異なるn個からr個の整数の組み合わせの数を求めます.");
	printf("n:");	scanf("%d", &i);
	printf("r:");	scanf("%d", &j);
	printf("組み合わせの数は%dです.\n", combination( i, j) );

	return 0;

}

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#14

投稿記事 by こめかみ » 16年前

>> >nCr = nC(n-r) を利用して、rが大きい場合に数を減らしてjやkの負担を減らす
>> 実は上のソースでもうやっています。

>うわ、恥ずかしい・・・
>すいません、ちゃんと読めてませんでした;;;

恥ずかしいのは私の方です。
見返したら全然出来てませんでしたort


hoge

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#16

投稿記事 by hoge » 16年前

ループの向きを逆にしよっか。
そうすれば、確実に割り切れるぞ。

初級者

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#17

投稿記事 by 初級者 » 16年前

もっと単純に考えよう。
配列もmallocも、最大公約数もいらない。


#include <stdio.h>

int combination(int n, int r)
{
    int combi, i;
    
    for (combi = i = 1; i <= r; i++, n--) {
        combi *= n, combi /= i;
    }
    return combi;
}

int main(void)
{
    int n, r;
    
    for (n = 0; n <= 9; n++) {
        for (r = 0; r <= n; r++) {
            printf("%dC%d=%3d ", n, r, combination(n, r));
        }
        putchar('\n');
    }
    return 0;
}

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#18

投稿記事 by こめかみ » 16年前

考えをまとめたいのでにちょっと書きこまさてください。

以下読みにくいですが(スパゲッティコードって奴なのかな?)
現段階でのソースです。

自力で解くつもりですが、calloc関数を使うのが始めてだったりと自信が少しありません。
初心者には致命的に気づきにくいようなミスなどありましたら、
指摘していただければありがたいですが、
質問としてはあまりにまとまっていないと自覚しているので、
あまり積極的なアドバイスはお願いしません。

あくまで考えをまとめるために失礼します。


…とか何とかえらそうに言っといて一つ質問があります。
スレッドの本題とは関係ないのでなにとぞご容赦ください。

上のたいちうさんのプログラムをコピペしてコンパイルしたときに、


#include <stdio.h>

を忘れてコンパイルしたのですが、なんとコンパイルできてしまいました。
環境はcygwinです、どういうことだかご存知の方いらっしゃいませんでしょうか。





"再帰を使わないで、組み合わせを解くプログラム(一応実行できますがバグだらけです)"
#include <stdio.h>
#include <stdlib.h>

// 最大公約数(たいちうさんのを流用)
int GCD(int u, int v)
{
	int t;
	while (u > 0) {
		if (u < v) {
			t = u;
			u = v;
			v = t;
		}
		u -= v;
	}
	return v;
}

int combination(int n, int r)//仮引数はint型でないといけない
{
	int i, j, k, m, x, y, nr;//ループ用変数,nCr = nC(n-r)用変数、その他

	//動的な確保に挑戦
	int *BunShi;//分子
	int *BunBo;//分母

	y = n;
	m = (n-r);//←nCr = nC(n-r)

	//(n-r)がr未満のときとr以上のときとそれ以外、
	if( m<r ){
		x = i = r;
	}
	else if( m>=r ){
		x = i = m = (n-r);//←nCr = nC(n-r)
	}

	BunShi = calloc(n, sizeof(int));
	BunBo = calloc(n, sizeof(int));

	if( n>=r ){

		for(; x>=1; --x){
			BunBo[x] = x;

			for(; y>=i; --y){
				BunShi[y] = y;
				nr = GCD( BunShi[y], BunBo[x] );
printf("in a nr=%d ,BunShi[%d]=%d :BunBo[%d]=%d\n", nr, y, BunShi[y], x, BunBo[x]);//エラーチェック
				BunShi[y] /= nr;
				BunBo[x] /= nr;
printf("in b nr=%d ,BunShi[%d]=%d :BunBo[%d]=%d\n\n", nr, y, BunShi[y], x, BunBo[x]);//エラーチェック
				//if(BunBo[x] == 1){break;}
				//if(BunBo[x] == 0){break;}
			}
printf("out c nr=%d ,BunShi[%d]=%d :BunBo[%d]=%d  ", nr, y, BunShi[y], x, BunBo[x]);//エラーチェック

		}
		
		nr = 1;
		for(; n>=i; --n){
			nr *= BunShi[n];
printf("A  nr=%d ,BunShi[%d]=%d ", nr, n, BunShi[n]);//エラーチェック
			//if(BunShi[n] == 1){break;}
		}


	} else {
		puts("n>=rの整数で入力してください");
		return 0;
	}

	free(BunShi);
	free(BunBo);

	return (nr);
}

int main()
{
	int i, j, n, r;

	puts("異なるn個からr個の整数の組み合わせの数を求めます.");
	printf("n:");	scanf("%d", &i);
	printf("r:");	scanf("%d", &j);
	printf("\n組み合わせの数は%dです.\n", combination( i, j) );

	///*
	//初級者さんの組み合わせ総表示
	for (n = 0; n <= 10; n++) {
		for (r = 0; r <= n; r++) {
		printf("%2dC%d=%3d  ", n, r, combination(n, r));
		}
	putchar('\n');
	}
	//*/

	return 0;

}

>初級者さん

ありがとうございます。
参考にさせてもらってます。

toyo

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#19

投稿記事 by toyo » 16年前

gcc 3では標準ヘッダはインクルードしなくてもいいようです。
gcc 4だと警告が出ます。


こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#21

投稿記事 by こめかみ » 16年前

やっと出来ましたー。
疲れた。。。もうダメかと思った。


>toyoさん
>gcc 3では標準ヘッダはインクルードしなくてもいいようです。
>gcc 4だと警告が出ます。

そんなコンパイラもあるのですか、もうナニを信じたらいいのやら。
自分のヴァージョンがいくつか確認しようとしたのですが、どこに書いてあるのかわかりませんでした。
去年のインストール版はもう古いのですか。


>初級者さん
>どうしてわざわざ難しい方法を使いますかね~。

遊びですからw
プログラムに限らず、その時の自分のレベルよりちょっと上ぐらいの困難が一番面白いと思いませんか?
や、つきあっていただきありがとうございました。


うん、よくわかりませんがこれツールになりませんかね。
再帰を使わず組み合わせを解くサンプルプログラム?
学生の私には用途が全然思いつきませんが。

ソースを一応貼っておきます。
見たい人欲しい人改造したい人はどうぞ。(いるのだろうか?)

動作の保障はしませんが、35までは大丈夫でした(多分)
入力する数値のデータ型をもっと大きいものに変えれば役に立つかも?


以下ソース(添付もしときます)

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

// 最大公約数(たいちうさんのを流用)
int GCD(int u, int v)
{
	int t;
	while (u > 0) {
		if (u < v) {
			t = u;
			u = v;
			v = t;
		}
		u -= v;
	}
	return v;
}

int combination(int n, int r)//仮引数はint型でないといけない
{
	int i, j, k, m, x, y, nr;//ループ用変数,nCr = nC(n-r)用変数、その他

	//動的な確保に挑戦
	int *BunShi;//分子
	int *BunBo;//分母

	k = y = n;
	m = (n/2);//←nCr = nC(n-r)

	//(n-r)がr未満のときとr以上のときとそれ以外、
	if( m>=r ){
		j = x = m = r;
	}
	else if( m<r ){
		j = x = m = (n-r);//←nCr = nC(n-r)
//printf("x=%d m=%d\n",x,i,m);//エラーチェック
	}

	BunShi = calloc(m, sizeof(int));
	BunBo = calloc(m, sizeof(int));

	

	if( n==0  || r==0 || n==r ){
		return (1);

	}else if( n>=r ){
		//初期化(って言うのかな?)
		for(i=0; i<m; ++i){
//printf("\n in A i=%d, j=%d, k=%d, BunBo[j]=%d, BunShi[k]=%d \n",i,j,k,BunBo,BunShi);
			BunBo = j--;//
			BunShi = k--;//
//printf(" in B i=%d, j=%d, k=%d, BunBo[j]=%d, BunShi[k]=%d \n",i,j,k,BunBo,BunShi);
		}

		for(x=0; x<m; ++x){
			for(y=0; y<m; ++y){
				nr = GCD( BunShi[y], BunBo[x] );
//printf("\nin a nr=%d ,BunShi[%d]=%d :BunBo[%d]=%d\n", nr, y, BunShi[y], x, BunBo[x]);//エラーチェック
				BunShi[y] /= nr;
				BunBo[x] /= nr;
//printf("in b nr=%d ,BunShi[%d]=%d :BunBo[%d]=%d\n\n", nr, y, BunShi[y], x, BunBo[x]);//エラーチェック

			}
		}
		
		nr = 1;
		for(i=0; i<m; ++i){
			nr *= BunShi;
//printf("\nA  nr=%d ,BunShi[%d]=%d ", nr, n, BunShi);//エラーチェック
		}


	} else {
		puts("n>=rの整数で入力してください");
		return 0;
	}

	free(BunShi);
	free(BunBo);

	return (nr);
}

int main()
{
	int i, j, n, r;

	puts("異なるn個からr個の整数の組み合わせの数を求めます.(35以下でお願いします.)");
	printf("n:");	scanf("%d", &i);
	printf("r:");	scanf("%d", &j);
	printf("その組み合わせの数は%dです.\n", combination( i, j) );

	//*
	//初級者さんの組み合わせ総表示
	for (n = 0; n <= i; n++) {
		for (r = 0; r <= n; r++) {
		printf("%3dC%-3d=%1.d  ", n, r, combination(n, r));
		}
	putchar('\n');
	}
	//*/

	return 0;

}

初級者

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#22

投稿記事 by 初級者 » 16年前

シンプルで読みやすいコードを如何にして書くか、というのが、
遊びのテーマとしても、よりチャレンジしがいがあると思う。

こめかみ

reduce_process_in_combination.exe

#23

投稿記事 by こめかみ » 16年前

reduce_process_in_combination.exe(予定)


組み合わせの約分の過程を、
全て表示させるまでに改良(?)しました。

教育用(小、中学生向けの)と言い張って、
ここの管理人さんのサンプルプログラミングの館で
配布してみようと思うのですが。


アイディアを下さった
mattさん

約分の方法を提示していただいた
たいちうさん、
初級者さん

よろしいでしょうか。

ご要望があれば合作ということにもしちゃいますが.


賛成にしても反対にしても一言いただければ嬉しいですが、
今日中にご意見が無ければ、
配布の方向で検討させていただきます。

よろしくお願いします。


>初級者さん

>シンプルで読みやすいコードを如何にして書くか、というのが、
>遊びのテーマとしても、よりチャレンジしがいがあると思う。

なるほど、そーいう遊びもアリですね。

こめかみ

Re:reduce_process_in_combination.exe

#24

投稿記事 by こめかみ » 16年前

約分されてない過程まで表示されてしまっている。。。

まだ改良の余地があるようですね;

組木紙織

Re:reduce_process_in_combination.exe

#25

投稿記事 by 組木紙織 » 16年前

今までとは別の方法論で組んでみました。
先に素数列を与えているところがポイントです。
#include <stdio.h>
#include <stdlib.h>


#pragma warning(disable : 4996)

enum Bunssu
{
	bunsi =1,bunbo=-1
};

const int sosuu[/url] ={2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97};

void debug(int table[sizeof(sosuu)/sizeof(int)])
{
	int i;
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		printf("%d: %d\n",sosuu,table);
	}

}

void primeFactor(int table[sizeof(sosuu)/sizeof(int)],int num, Bunssu flag)
{
	int i;
	if(num==0)
	{
		printf("error!\n");
		exit(1);
	}
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		while(num%sosuu==0)
		{
			table += flag;
			num = num/sosuu;
		}
	}
}

unsigned int calc(int table[sizeof(sosuu)/sizeof(int)])
{
	unsigned int result=1;
	int i;
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		if(table>0)
		{
			result *= table*sosuu*bunsi;	
		}
		
		//必要ないが念のため。
		if(table<0)
		{
			printf("error??\n");
			result /= table*sosuu[i]*bunbo;
		}
	}
	return result;
	
}
unsigned int combination(int n, int r)
{
	int i,j;
	int result[sizeof(sosuu)/sizeof(int)];
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		result[i] =0;
	}
	
	for(i=0,j=r;i<r;i++,n--,j--)
	{
		primeFactor(result,n,bunsi);
		primeFactor(result,j,bunbo);
	}
	return calc(result);
	
}

int main()
{

	int i, j;

	puts("異なるn個からr個の整数の組み合わせの数を求めます.(21以下でお願いします.)");
	printf("n:");	scanf("%d", &i);
	printf("r:");	scanf("%d", &j);
	printf("その組み合わせの数は%dです.\n", combination( i, j) );

	return 0;

}

こめかみ

【学習ソフト】組み合わせの約分過程表示プログラム(仮)

#26

投稿記事 by こめかみ » 16年前

↓ほぼ完成版がダウンロードできます。
ttp://cid-d72819af3901253a.skydrive.live.com/self.aspx/%e5%85%ac%e9%96%8b/%e7%b5%84%e3%81%bf%e5%90%88%e3%82%8f%e3%81%9b%e3%81%ae%e7%b4%84%e5%88%86%e9%81%8e%e7%a8%8b%e8%a1%a8%e7%a4%ba%e3%83%97%e3%83%ad%e3%82%b0%e3%83%a9%e3%83%a0|5%e4%bb%ae|6.zip



> く...組木紙織さん...?(読めない)

今疲れ果てててそんな長いの読めないですが、
頭がハッキリしてても読めるかどうか...

面倒じゃなければ是非概念図とかプリーズ (なげやり) (失礼しました)

もうこのスレッドにあるサンプル全部掲載してもらえばいいんじゃないですかね(適当)


後は、exeファイルをダブルクリックで起動できるようにしたり、
アイコンなんかつけられれば面白いのですが。
可能なのでしょうかね。
何を調べればいいのか、ちょっとわかんないですね。

こめかみ

Re:【学習ソフト】組み合わせの約分過程表示プログラム(仮)

#27

投稿記事 by こめかみ » 16年前

画像の順列ぶんの順列が既に間違っていました

教育用とか嘯いといて定義で間違うなんてort
明日修正します・・・

組木紙織

Re:【学習ソフト】組み合わせの約分過程表示プログラム(仮)

#28

投稿記事 by 組木紙織 » 16年前

私の名前は、組木紙織(くみきかみおり)です。
音がわからないといわれますが、普通にそのままの文字のとおりの音となっています。

とりあえず、私のは掲載やめてください。
本気で作ってないので、恥ずかしいです。

>面倒じゃなければ是非概念図とかプリーズ (なげやり) (失礼しました)
概念図とかはありませんが、計算の流れ方を。

1:分子を素因数分解する。
2:分母を素因数分解しながら、約分をする。
3:素因数分解されて出てきた結果を出力する。

という流れになっています。

本当は素因数分解するときの元となる素数列を事前に少数与えておき、
それより先は遅延評価で計算を進めていきたかったのですが、
そこまですると複雑になりすぎると思ったので、事前に全て与えています。

たいちう

Re:【学習ソフト】組み合わせの約分過程表示プログラム(仮)

#29

投稿記事 by たいちう » 16年前

> 賛成にしても反対にしても一言いただければ嬉しいですが、

ということですので。少し。

私がここに書いたものは自由に使っていただいて結構です。
サンプルとして載せる価値があるかどうかは疑問ですが。

(自称)上級者の私にとっては、
「必要ないのにわざわざ約分して作る」というテーマも面白く、
ちょいちょいと作ってみましたが、教育用に相応しいとは思えません。
No:22795の初級者さんのサンプルを紹介する方がよっぽど有意義でしょう。

# あの順番で計算することで、「割り切れずに余りが切り捨てられることがおきず、
# 正しく計算できる」、ということは、あまり自明とは思えませんので、
# そのあたりの解説も加えたら完璧かと。

いずれにしても、誰かが書いて管理人さんも載せる価値があると判断するならば、
何も言うことはありません。多分価値があることなのでしょう。
頑張ってください。

初級者

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#30

投稿記事 by 初級者 » 16年前

分子はnから始まって降順にr個分を掛け、
分母は1から始まって昇順にr個分を掛けるのね。

ここで、「連続する任意のn個の自然数には、必ずnの倍数を含む」という
性質を使うの。証明は省くわ。

この性質を使うと、あの順番で計算することにより、
組合せの数が正しく求まるの。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#31

投稿記事 by こめかみ » 16年前

返信遅くなり申し訳ないです。



>>組木紙織さん
この掲示板に来てからのひそかな疑問が一つとけました。
見てるとこの掲示板の方々の名前も
プログラム関係に因んだ名前の方が多いように見受けられるので
学習しながら、

あの人のハンドルネームはこれか?
って感じでいつか気付くのを期待してたですが、
ん、
でも意味が解からないから結局関係無いですね。
由来とか興味はありますが、
何か有名なアルゴリズムの和訳か何かかと想像してます。
あるいは建築や製紙にプログラムを喩えて見ているとか。

組木でググッたら面白いものが見れました
日本の伝統工芸すごいですね、なんていうか、ラミエリーです。


糸偏が、なんというか、リーチがかかってて、もったいないっていうか、
ムズムズします。

組綵紙織さんとかだったらビンゴ!フィーバー!ばっよえ~ん!
ってな感じで気持ちいいのに。

無茶苦茶勝手な感想失礼しました。
本当に気分を害されたらごめんなさい。
そう思うのなら言わなきゃいいのですが、つい。。。


>>1:分子を素因数分解する。
>>2:分母を素因数分解しながら、約分をする。
>>3:素因数分解されて出てきた結果を出力する。 >>
>>
>>という流れになっています。

頑張って読み解こうとしてみましたが、
プログラム自体を把握することはついぞ出来ませんでした。
そこで流れの解説から想像するに、
とにかく出現する数字を素因数分解して、
同じ素数同士を纏めて計算したりしてるのかとか考えましたが(具体的なイメージは出来てません)。

そのアルゴリズムは、
初級者さんの簡潔なプログラムや
私のプログラムと比べてどのような個性を持っているのでしょうか。


例えば初級者さんのプログラムは約分を用いて組み合わせの計算が出す

"結果"

に必要最小限の記述で到達し出力するという、
プログラムを学習するものにとってのこの上ない

"鑑"

としての価値を見出すとします。

例えば、
私の作った分子分母の変数を全て確保しそれぞれを計算し出力するという、
単純に結果だけを追求するなら、
初級者さんのものと比べてこの上なく贅肉の多いプログラムも、

確保した変数に計算の過程が全て格納されている

ことに着目して、

約分の

"過程"

を全て出力するようにすれば、
たいちうさんや初級者さんのプログラムには無い個性ではないかと考え、
現在ビルドアップ中です、(何の役に立つかはまた別の話として、)

何の役に立つかは、まあ、無理矢理に価値を見出すなら、
数学で組み合わせを初めて習う中学生の補助教材みたいなものにでもなればいいかな、とか、
あるいは再帰を使わないとこんな長ったらしいプログラムの記述になるという

"悪い見本"

として世に放てば、
プログラムを習いたての人がこのソースコードを見た時、

「再帰かっこいい」

ってな感じで再帰の素晴らしさを認識する一つのきっかけぐらいには
なるんじゃないかとか考えてみました。


組木さんのコードはこのような個性を見出すとしたら
どのような価値を見出せるプログラムでしょうか。

本来なら自分で理解して自分一人で考える問題なのでしょうが、
いかんせん今の私には理解に時間がかかるので、考えるのを諦めたいところ、
なのですが、

なんとなく後ろ髪を引かれます。

その価値が興味深ければもう一度理解に取り組んでみたいと思うかもしれませんし、
今理解できなくても日を置いて理解しようとすると思います。

もしご面倒でなければ、考えて教えていただけないでしょうか、
我ながらめんどくさ過ぎる要望だと思うので、本当に気分が乗ったらでいいです。
ありがとうございました。




>>たいちうさん

価値についての話も踏まえて上の組木さんへの文章を書いてみました。
教育というのはハッタリが過ぎると後になって思い直しましたが、
教材くらいになれば嬉しいな、程度の考えでした、

相応しくないのなら、
"組み合わせの約分を全部表示させて遊ぶプログラム"と、
そのまんまな感じで管理人さんに提案してみようと思います。(掲載されてみたいのです)

あと、学習ってのはC言語についてのことではなく、
数ⅠAの一分野の本当に限定的な場面での使用を想定していました。
それでも無理がありますが。





>>初級者さん

丁寧な解説ありがとうございます。

よくわかりました。
コメントの無い上のコードだけではイマイチ意味を把握しきれてなかったのと(その説明で完璧にわかりました)、
matsさんの助言で既に完成形がイメージできてしまっていたこともあって、
無駄の少ない上のコードよりも、
自分なりに考えたプログラムの実装を優先させてしまいました。
つまり私の中ではあの回答こそが"簡単なコード"だったのです.
嘘をついてしまっていたようでごめんなさい。

でも自分で設定したハ-ドルを超えるのが楽しいのは本当です。
そういう意味で、
初級者さんのシンプルで読みやすいコードを如何にして書くか、というのも、
遡れば私と同じテーマなのではないでしょうか。

人が考えたコードを理解するだけじゃつまらなかったという理由もありました。
決して初級者さんのコードを否定する意図はありませんでしたが、
一抹でもそのように感じられている可能性がある様な気がしたのでお詫びさせていただきます。



>>自分にレス
>今日中にご意見が無ければ、
>配布の方向で検討させていただきます。

その日以内ってのは早すぎましたね。
自分の予定も予想外に一日食ってしまいましたし。

後一週間ぐらいは、遊び心を忍ばす余地が無いか考えて見たいので、
その間に、ご意見とかあったらどうぞ。

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#32

投稿記事 by たいちう » 16年前

> ってな感じで再帰の素晴らしさを認識する一つのきっかけぐらいには
> なるんじゃないかとか考えてみました。

combinationのプログラムを再帰で書くのも悪い例だと思いますよ。
階乗やフィボナッチ数と同じで、数学的な定義が再帰的だからといって、
プログラムを再帰で実装してはいけないという例。
理由は非常に効率が悪いから。

今時の環境ならば(4バイトの)int型で収まる範囲程度に限れば、
効率の低下が無視できる場合を多く、
数学的定義のまま実装することを利点と言うこともできるかもしれないけど。

身近な再帰の好例としては、フォルダの探索とかでしょうか。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#33

投稿記事 by こめかみ » 16年前

勉強になります。
ではこの問題はホントにただの再帰の練習問題なのですね。

再帰的な処理というのは効率の為に避けて通るべき場面が多々ある、と。
そのフォルダの探索というのは再帰以外では実装できない、
または再帰によって効率がベストになるということでしょうか。
一応、調べてみたのですが今の自分程度の見識では
見つけることが出来ませんでした。

もしかして上の初級者さんのコードが、
はからずも今回の課題の正答よりも効率が良いのではないかと思っているのですが。

処理速度の比べ方をどうやるのわからないので調べてませんが、
どうなのですかね?

組木紙織

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#34

投稿記事 by 組木紙織 » 16年前

私のハンドルネームについてのコメントは控えさせてもらいます。


>そのアルゴリズムは、
>初級者さんの簡潔なプログラムや
>私のプログラムと比べてどのような個性を持っているのでしょうか。


特にたいしたことはしていませんが、しいて言うなら
素数列を先に与えることによって、コンパイル時に計算可能な部分は先に計算をして、
実行時の計算量を減らすことが出来ることでしょうか。

#初級者さんのコードに太刀打ち出来るほどの速度は出ないと思いますが

それとは別に、
テクニックとして配列数を直接数字で与えてないところも見てほしいと思います。




たいちうさんへ

フィボナッチ数を再帰で実装しないほうがよいという意見には一部疑問があります。
フィボナッチ数は初期値を整数にすると全て整数の数列なので、整数型で結果がほしい場合があると思いますが、
数列の一般項を計算すると一般的には無理数(や虚数)を含んだ形になります。

一般項を色々いじくった結果、計算式から無理数(と虚数)を排除することが出来ましたが、
条件分岐を含む醜い計算式となり、全て整数型で計算可能なように式を変形すると計算途中で値が大きくなり
項数が多くなるとすぐにオーバーフローしそうな感じでした。

計算式の展開の複雑さ、計算量、計算時の有効桁数、を考えると
(それぞれに対して検証したわけではありませんが)
再帰の実装と再帰無しを比較すると必ずしも、再帰無しで実装したほうが有効だとは思えないです。


今回は一番よく知られている
a_1 = a_2 = 1
a_(n+2) = a_(n+1) + a_(n)

数列に関して一般項を展開して考えました。

必要があれば展開した式と無理数を排除した式を上げます。



>処理速度の比べ方をどうやるのわからないので調べてませんが、
理論的に計算する方法もありますが、実際にループで何回も連続で実行してみてその時間を比較すれば
わかります。

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#35

投稿記事 by たいちう » 16年前

> そのフォルダの探索というのは再帰以外では実装できない、
> または再帰によって効率がベストになるということでしょうか。

フォルダの構造が再帰的なものなので、再帰的なアルゴリズムと相性が良いですが、
他の方法で実装できますし、その中には効率的に遜色ないものもあると思います。
しかし再帰のプログラムが最もシンプルになるでしょう。

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#36

投稿記事 by たいちう » 16年前

組木紙織さん

フィボナッチ数を計算するプログラムの実装方法ですが、
再帰と同様に一般項も使うべきではないと思います。
("http://ja.wikipedia.org/wiki/フィボナッチ数列"にある、ルート5を含んだ式ですよね?)

使うべきも何も、学習と遊び以外の目的でフィボナッチ数列を必要とすることは
ほとんどないと思いますが、下記の例か、そのバリエーションが正解ではないでしょうか。
int Fibonacci(int n)
{
	int a = 0, b = 1, c;
	while (n--) {
		c = a + b;
		a = b;
		b = c;
	}
	return a;
}

組木紙織

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#37

投稿記事 by 組木紙織 » 16年前

>フィボナッチ数を計算するプログラムの実装方法ですが、
>再帰と同様に一般項も使うべきではないと思います。
>("http://ja.wikipedia.org/wiki/フィボナッチ数列"にある、ルート5を含んだ式ですよね?)

一般項の式はそれです。
(せっかくせっせと一般項を自分で求めたのに。。。)

私も最終的にはたいちうさんと同じところに行き着きました。
(寝て起きたら思いついたという落ちです。)


この数列を実際に利用する場面なんて思いつきませんが
フィボナッチ数列を一般項で出す利点を強いてあげると
定義に従って計算するとO(N)なのでO(1)ということぐらいですかね。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#38

投稿記事 by こめかみ » 16年前

遅くなりました。

>たいちうさん
>再帰のプログラムが最もシンプルになるでしょう。

ありがとうございました。
いつかフォルダの探索のプログラムを書く機会があったら、その言葉を思い出して実装に挑戦してみます。


>組木さん
>
>素数列を先に与えることによって、コンパイル時に計算可能な部分は先に計算をして、
>実行時の計算量を減らすことが出来ることでしょうか。
>
>#初級者さんのコードに太刀打ち出来るほどの速度は出ないと思いますが
>
>それとは別に、
>テクニックとして配列数を直接数字で与えてないところも見てほしいと思います。

やはり何をやっているのかわかりませんでしたが、
(というより解読する時間が取れないって言うか・・・言い訳ですね)
日を置いて解読してみようと思います。


>理論的に計算する方法もありますが、実際にループで何回も連続で実行してみてその時間を比較すれば
>わかります。

近くのスレッドで管理人さんが時間を計測するサンプルを作ってらしたので、
こっそり参考にしてやってみました。
タイムヘッダって時刻を取得するだけじゃないんですね、これは面白い。

条件をこんな感じに同じにして、
#include <stdio.h>
#include <time.h>

int combination(int n, int r)//※仮引数はint型でないといけない
{
	if(r==0||r==n)
		return (1);
	else if(r == 1);
		return(n);
	return (combination(n - 1, r - 1) + combination(n - 1, r));
}

int sub(void)
{
    int n, r;
    
    for (n = 0; n <= 19; n++) {
        for (r = 0; r <= n; r++) {
            printf("%dC%d=%3d ", n, r, combination(n, r));
        }
        putchar('\n');
    }
    return 0;
}
int main()
{
	int i;
	double c;
	clock_t t;
	t = clock();//時間を格納
	for(i=0;i<10000;i++){
		c = sub();
	}
	printf("%.3f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);
	return 0;
}
結果
,         "VC++",            "Cygwin",
,         "c",        "cpp",     "c",       "cpp"
"正答(再帰)", "105.890(秒",   105.797,    13.516,     15.421
"初級者さん",  "105.641",          106.031,        14.375,          14.031
"組木さん",      "コンパイル失敗",   124.406,       "コンパイル失敗", 30.922
"私",            "118.390",         "コンパイル失敗",20.156,         "コンパイル失敗"
初級者さんのほうが早いかと思ってたけど、意外とどっこいどっこいですね。

意外といえば組木さんのが私のより遅いなんて...
比較回数が足りないのかな。

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#39

投稿記事 by たいちう » 16年前

> 比較回数が足りないのかな。

むしろ比較の方法に問題がありそうです。
殆どの時間が計算結果の表示に費やされているのではないでしょうか。
気付いた点。

・VC++のバージョンを書きましょう。
・VC++はりリースモードでコンパイルした方が実行時間の比較には良いでしょう(※)。
また、VCからでなく、exeファイルから実行しましょう。
・複数回実行しましょう。特に2回目以降は速くなることがあるかもしれません。
・printf等の画面表示の関数は、かなり時間がかかります。
アルゴリズムの比較のためには、アルゴリズムと関係ない部分は
少ない方が良いので、sub()のprintf()とputchar()はなくしてしまいましょう。
・測定の手間を省くためと、測定方法が正しいことがみんなに分かるように、
1つの実行ファイルで、全てのアルゴリズムを順に試して結果をまとめて
表示するように作り変えてはいかがでしょうか。
腑に落ちない結果については他の環境で追試したり、比較方法に問題があれば
より適切な指摘も受けられると思います。

※リリースモードで画面表示を行わない場合、「どうせ計算したって誰も見やしないんだ」と、
コンパイラがズルをするかもしれません。
ズルをさせないために、sub()の中でcombination()の合計を計算し、
main()で表示するとかの工夫が必要かも。

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#40

投稿記事 by たいちう » 16年前

作り変えろとか言ってないで、自分でやればいいよね。
VC++2005 リリースモード C++でコンパイルして実行した結果。
(再帰の方に余分なセミコロンがありました。ご確認を)

再帰:0.040秒
再帰なし:0.000秒
#include <stdio.h>
#include <time.h>

int combination(int n, int r)//※仮引数はint型でないといけない
{
	if(r==0||r==n)
		return (1);

	else if(r == 1)
		return(n);

	return (combination(n - 1, r - 1) + combination(n - 1, r));
}

int combination2(int n, int r)
{
    int combi, i;
    for (combi = i = 1; i <= r; i++, n--) {
        combi *= n, combi /= i;
    }
    return combi;
}

int sub(void)
{
    int n, r, sum = 0;

	for (n = 0; n <= 19; n++) {
        for (r = 0; r <= n; r++) {
			sum += combination(n, r);
        }
   }
    return sum;
}

int sub2(void)
{
    int n, r, sum = 0;
	for (n = 0; n <= 19; n++) {
        for (r = 0; r <= n; r++) {
			sum += combination2(n, r);
        }
   }
    return sum;
}

int main()
{
	int i, sum;
	clock_t t;

	//---------------
	// 再帰

	t = clock();//時間を格納
	sum = 0;
	for(i=0;i<10000;i++){
		sum += sub();
	}
	printf("%d\n", sum);
	printf("%.3f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);

	//---------------
	// 初心者さんの

	t = clock();//時間を格納
	sum = 0;
	for(i=0;i<10000;i++){
		sum += sub();
	}
	printf("%d\n", sum);
	printf("%.3f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);

	// quit
	printf("end.\n");
	getchar();
	return 0;
}

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#41

投稿記事 by こめかみ » 16年前

>作り変えろとか言ってないで、自分でやればいいよね。

そんなことないです、むしろ答えよりやり方を教えてくださるほうが少なくとも私は好きです。
その答えが嫌だとか言うわけじゃないですが。
でも私と組木さんのぶんが無い様なので、まだ試す余地があるようですね。

土曜までスケジュールに追われて時間取れませんが、日曜に必ず時間作って挑戦します。


あ、セミコロンホントだ、直しときます。

あと、初心者さんではなく初級者さんですよ、私も見間違えてましたが。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#42

投稿記事 by こめかみ » 16年前

遅くなりました。

たいちうさんの比較コードが既にベストなのに、
私が新たに考える余地などあるはずも無いのでした。

リリースモードの計算速度に感動しました。
この前ひたすら計算待ちしてた私はなんだったんだw
VC++2008Express リリースモード Cでコンパイルして実行した結果。

1895815408
0.015秒    //再帰

1895815408
0.000秒    //初級者さんの再帰無し 速いっ! 速度比較の結果0秒ってどうなんだろw 
ホントは計測できるまでループ回数増やさなきゃいけないのかな。

1895815408
3.735秒    //私の 遅い... こんなもんですか。

end.

お次は
VC++2008Express リリースモード C++でコンパイルして実行した結果。

1895815408
0.015秒

1895815408
0.000秒
この上の二つはcソースの結果と同じなので省略

問題は組木さんのコード
-3204592    //この計算結果はいったいなんだろう...
15.344秒    //やっぱり遅いです。何回か実行させて試しましたが、同じような結果でした。
end.
むーん。。。?
私のよりは速いかと思っていたのですが。
組木さんのアルゴリズムは方法の一つとして学ばせていただくしかないのでしょうか、
うん、きっと理解すれば他に応用の効く場面があるに違いない。


ちなみにソースはたいちうさんのに追加する形でこんな感じです。

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

int combination(int n, int r)//※仮引数はint型でないといけない
{
	if(r==0||r==n)
		return (1);

	else if(r == 1)
		return(n);

	return (combination(n - 1, r - 1) + combination(n - 1, r));
}

int combination2(int n, int r)
{
    int combi, i;
    for (combi = i = 1; i <= r; i++, n--) {
        combi *= n, combi /= i;
    }
    return combi;
}
#pragma warning(disable : 4996)

enum Bunssu
{
	bunsi =1,bunbo=-1
};
const int sosuu[/url] ={2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97};

void debug(int table[sizeof(sosuu)/sizeof(int)])
{
	int i;
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		printf("%d: %d\n",sosuu,table);
	}

}

void primeFactor(int table[sizeof(sosuu)/sizeof(int)],int num, Bunssu flag)
{
	int i;
	if(num==0)
	{
		printf("error!\n");
		exit(1);
	}
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		while(num%sosuu==0)
		{
			table += flag;
			num = num/sosuu;
		}
	}
}

unsigned int calc(int table[sizeof(sosuu)/sizeof(int)])
{
	unsigned int result=1;
	int i;
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		if(table>0)
		{
			result *= table*sosuu*bunsi;	
		}
		
		//必要ないが念のため。
		if(table<0)
		{
			printf("error??\n");
			result /= table*sosuu[i]*bunbo;
		}
	}
	return result;
	
}
unsigned int combination3(int n, int r)
{
	int i,j;
	int result[sizeof(sosuu)/sizeof(int)];
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		result[i] =0;
	}
	
	for(i=0,j=r;i<r;i++,n--,j--)
	{
		primeFactor(result,n,bunsi);
		primeFactor(result,j,bunbo);
	}
	return calc(result);
	
}
int sub(void)
{
    int n, r, sum = 0;

	for (n = 0; n <= 19; n++) {
        for (r = 0; r <= n; r++) {
			sum += combination(n, r);
        }
   }
    return sum;
}

int sub2(void)
{
    int n, r, sum = 0;
	for (n = 0; n <= 19; n++) {
        for (r = 0; r <= n; r++) {
			sum += combination2(n, r);
        }
   }
    return sum;
}


int sub3(void)
{
    int n, r, sum = 0;
    
    for (n = 0; n <= 19; n++) {
        for (r = 0; r <= n; r++) {
			sum += combination3(n, r);
        }
    }
    return sum;
}


int main()
{
	int i, sum;
	clock_t t;

	//---------------
	// 再帰

	t = clock();//時間を格納
	sum = 0;
	for(i=0;i<10000;i++){
		sum += sub();
	}
	printf("%d\n", sum);
	printf("%.3f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);

	//---------------
	// 初心者さんの

	t = clock();//時間を格納
	sum = 0;
	for(i=0;i<10000;i++){
		sum += sub2();
	}
	printf("%d\n", sum);
	printf("%.3f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);

	//---------------
	// 組木さんまたは私

	t = clock();//時間を格納
	sum = 0;
	for(i=0;i<10000;i++){
		sum += sub3();
	}
	printf("%d\n", sum);
	printf("%.3f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);

	// quit
	printf("end.\n");
	getchar();
	return 0;
}




計算結果がおかしいということはどこかがおかしいのですよね。
コピペミスかna...ちょっと腑に落ちないけどもう諦めようかなぁ。
明日ちょっとがんばって無理だったら諦めます。

このスレッドでの投稿はこれで最後かもしれないですね。
長いことお付き合いいただきありがとうございました。

box

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#43

投稿記事 by box » 16年前

> 計算結果がおかしいということはどこかがおかしいのですよね。

どの方法を採っても、いつかはint型やlong型で扱える範囲を超えます。
そういう桁あふれが起きると、あふれた分はなかったものとして
計算し続けたり結果を出力したりします。
したがって、おかしな値を出力することとなります。

分子の掛け算を先に行なう方法だと、桁あふれが早く起きます。
掛け算・割り算を交互に行なう方法(初級者さんのような)だと、
桁あふれが起きるのが少しは遅くなります。
それだけのことです。

組木紙織

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#44

投稿記事 by 組木紙織 » 16年前

こめかみさんが遅いというので無駄な計算を省いて速度を上げてみました。
ついでにCでもコンパイル可能にして、さらにそれぞれの計算にコメントをいれて
流れがわかるようにしてみました。
#include <stdio.h>
#include <stdlib.h>

/*VCの警告の無視*/
#pragma warning(disable : 4996)

#define bunsi 1
#define bunbo -1

/***************************************************
<計算方法>
4C2を例にする
4C2=(4*3)/(2*1)
   =(2^2*3^1)/(2^1)
   =(2^1*3^1)/1
   =6
***************************************************/

/*19までの素数列。次の素数は23*/
const int sosuu[/url] ={2, 3, 5, 7, 11, 13, 17, 19};

/*
素因数分解と約分をする
flag =1の場合は素因数分解
flag =-1の場合は約分
*/
void primeFactor(int table[sizeof(sosuu)/sizeof(int)],int num, int flag)
{
	int i;
	/*nCrのnが0以下のとき*/
	if(num<=0)
	{
		printf("error!\n");
		exit(1);
	}
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		/*数値にそれ以上大きい素因数を含まないとき*/
		if(num<sosuu)
		{
			break;
		}
		/*数値にsosuuが因数として含まれるとき*/
		while(num%sosuu==0)
		{
			table += flag;
			num /= sosuu;
		}
	}
}

/*
素因数分解した結果を数値に戻す。
*/
unsigned int calc(int table[sizeof(sosuu)/sizeof(int)])
{
	unsigned int result=1;
	int i;
	
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		/*素因数分解配列に値が存在しているときのみ計算をする。*/
		if(table>0)
		{
			result *= table*sosuu;
		}else if(table<0) //必要ないが念のため
		{
			printf("error??\n");
			result /= table*sosuu[i];
		}
	}
	return result;
	
}
unsigned int combination(int n, int r)
{
	int i,j;
	int result[sizeof(sosuu)/sizeof(int)];
	/*素因数分解配列の初期化*/
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		result[i] =0;
	}


	/*nCr = nC(n-r)を用いて計算量を減らす*/
	if(n-r<r)
	{
		r=n-r;
	}
		
	/*素因数配列の計算*/
	for(i=0,j=r;i<r;i++,n--,j--)
	{
		/*分子の大きいほうから順番に素因数分解をする*/
		primeFactor(result,n,bunsi);
		/*分母の小さいほうから順番に約分をする*/
		primeFactor(result,j,bunbo);
	}
	
	/*素因数配列を数値として出力する*/
	return calc(result);
	
}

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#45

投稿記事 by こめかみ » 16年前

>>boxさん
>分子の掛け算を先に行なう方法だと、桁あふれが早く起きます。

21以下ということだったのでこれでいいかと思ったのですが。
そういうことではないかな?
何か根本的に勘違いしているかもです(私が)

>>組木さん

ひぎぃ(悲鳴)
なんか挑発してしまったみたいで申し訳無いです。


ありがたく拝見させていただきます。

おぉコメントまで入れて頂き、もうわかんないとかいってられませんね。超頑張ります。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#46

投稿記事 by こめかみ » 16年前

早速計ったら
2秒台でした。
流石です。


上のboxさんへの文章が後から見返したら噛み合って無かったです。
自分は何を言いたかったのか、
いやそもそもboxさんの発言をイマイチ把握しきれてないようです。
もうちょっと落ちついて発言します...すみません。

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#47

投稿記事 by たいちう » 16年前

combination3がどこまで計算できるのか調べるために、
main関数を変えてみる。
int main()
{
	int n, r, c1, c2, c3;
	for (n = 0; n <= 19; n++) {
		printf("n = %d\n", n);
		printf("             comb1      comb2      comb3\n");
		for (r = 0; r <= n; r++) {
			c1 = combination(n, r);
			c2 = combination2(n, r);
			c3 = combination3(n, r);
			printf("    r = %2d : %10d %10d %10d", r, c1, c2, c3);
			if (c1 != c2 || c2 != c3)
				printf(" !!!!\n");
			else
				printf("\n");
		}
		printf("\n");
	}

	// quit
	printf("end.\n");
	getchar();
	return 0;
}
combination3(8, 1)が既に違うじゃん。
8! = 40320なので、オーバーフローが原因じゃないと思うよ。
combination3をちゃんと読んでないけど。

Justy

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#48

投稿記事 by Justy » 16年前


>やっぱり遅いです。何回か実行させて試しましたが、同じような結果でした

 こめかみさんの No:23335のテストコードですが、このままの状態でVC9で
最適化をかけた場合ちょっと問題があるような気がします。


 うちの環境でビルドすると sub()と sub2()のループは

[color=#e0f0ff" size="-1" face="monospace]00401317 call clock
0040131C mov esi,eax
0040131E call sub
00401323 imul eax,eax,2710h
00401329 push eax
0040132A push offset string "%d\n"
0040132F call printf [/color]

 のようなコードが生成されます。
 このコードですと clock()が呼ばれた後 sub()は1回しか呼ばれていません。

 どういうことかというとコンパイラがコードを最適化した結果
sub() / sub2()を 1回だけコールして戻ってきた値を 10000倍して printfしています。

 反対に sub3()は

[color=#e0f0ff" size="-1" face="monospace]0040139B call clock
004013A0 mov dword ptr [esp+14h],eax
004013A4 xor ebp,ebp
004013A6 mov dword ptr [esp+10h],2710h
004013AE mov edi,edi
004013B0 xor ebx,ebx
004013B2 xor edi,edi
004013B4 xor esi,esi
004013B6 test edi,edi
004013B8 jl main+0C1h
004013BA lea ebx,[ebx]
004013C0 push esi
004013C1 push edi
004013C2 call combination3
004013C7 inc esi
004013C8 add esp,8
004013CB add ebx,eax
004013CD cmp esi,edi
004013CF jle main+0B0h
004013D1 inc edi
004013D2 cmp edi,13h
004013D5 jle main+0A4h
004013D7 add ebp,ebx
004013D9 sub dword ptr [esp+10h],1
004013DE jne main+0A0h
004013E0 push ebp
004013E1 push offset string "%d\n"
004013E6 call printf [/color]

 となっており、sub3()はインライン展開されて消滅していますが、combination3()は
きちんと 10000回ループして回数分コールされています。

 このあたりを直さないと、パフォーマンス測定としては平等さに欠けるかと。
(最適化のされやすさ込みでしたらいいのですが)

組木紙織

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#49

投稿記事 by 組木紙織 » 16年前

たいちうさんの指摘により、プログラムにミスがあったことが発覚したので、
次のように修正して下さい。

calc()の
result *= table*sosuu;
個々の部分を次のように置換してください。
for(j=0;j<table;j++){
result *= sosuu;			  
}

このままだとコンパイルエラーになるので、変数jをint型で宣言してください。

#累乗と乗算を間違えるなんてえらいミスをしていた

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#50

投稿記事 by こめかみ » 16年前

>>組木さん

直したら計算結果もバッチリでしたし、
速度も0.2秒台までになりました、
どんどん速くなっていく...




>>たいちうさん

比較の方法が勉強になります。
うーむ、一度見たこと無いと思いつかないですね。




>>Justyさん

うおっ機械語ですか。
add,sub,mov,jumpぐらいしかわかりませんが、
つまりまったくわかりませんが。

その機械語さえ読めれば、C言語側のどこを直せばいいのかわかるのですか?
sub1,sub2の関数がうまくループされてないということは
Justyさんの解説コミでかろうじてわかりましたが。

VCに生成された機械語を見る機能でもあるのでしょうか、
または外部ツール(デバッガーとか言う奴?)ですか。
使えるようになったほうが便利そうですが、今の私には敷居が高そうだなあ。


>(最適化のされやすさ込みでしたらいいのですが)

えーと、最適化というのはリリースモードでビルドすることですよね。

私のパフォーマンス測定は、
このスレッドではじめて実践したもの(というかほぼコピペ)なので、
複雑な意図はありません。


平等な測定を目指すには、
sub1,sub2をちゃんと10000回ループさせればいいのですか?

でも私には、どうやればsub1,sub2を直せるのか、
sub3のループは何故うまくいっているのかもよくわかりません。

良かったら直し方を教えて欲しいのですが、
機械語をろくに読めない私にはそもそも気付くことの出来ない問題っぽいですね。
理解できないかもしれないのですが、直し方を教えていただけないでしょうか。


火曜から土曜の平日はほとんど時間が取れないので、返信は遅くなってしまうかもしれません。
なんかスレッドがすごく伸びてて申し訳ないですがもうちょっとお世話になります、


私に理解できなそうな問題だとJustyさんが判断されたら、
その旨を示していただければ大人しく引き下がります。


組木さんのプログラムを修正後の比較プログラムはこの下に分けて投稿させていただきます。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#51

投稿記事 by こめかみ » 16年前

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

int combination(int n, int r)//※仮引数はint型でないといけない
{
	if(r==0||r==n)
		return (1);

	else if(r == 1)
		return(n);

	return (combination(n - 1, r - 1) + combination(n - 1, r));
}

int combination2(int n, int r)
{
    int combi, i;
    for (combi = i = 1; i <= r; i++, n--) {
        combi *= n, combi /= i;
    }
    return combi;
}


/*VCの警告の無視*/
#pragma warning(disable : 4996)

#define bunsi 1
#define bunbo -1

/***************************************************
<計算方法>
4C2を例にする
4C2=(4*3)/(2*1)
   =(2^2*3^1)/(2^1)
   =(2^1*3^1)/1
   =6
***************************************************/

/*19までの素数列。次の素数は23*/
const int sosuu[/url] ={2, 3, 5, 7, 11, 13, 17, 19};

/*
素因数分解と約分をする
flag =1の場合は素因数分解
flag =-1の場合は約分
*/
void primeFactor(int table[sizeof(sosuu)/sizeof(int)],int num, int flag)
{
	int i;
	/*nCrのnが0以下のとき*/
	if(num<=0)
	{
		printf("error!\n");
		exit(1);
	}
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		/*数値にそれ以上大きい素因数を含まないとき*/
		if(num<sosuu)
		{
			break;
		}
		/*数値にsosuuが因数として含まれるとき*/
		while(num%sosuu==0)
		{
			table += flag;
			num /= sosuu;
		}
	}
}

/*
素因数分解した結果を数値に戻す。
*/
unsigned int calc(int table[sizeof(sosuu)/sizeof(int)])
{
	unsigned int result=1;
	int i,j;
	
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		/*素因数分解配列に値が存在しているときのみ計算をする。*/
		if(table>0)
		{
			for(j=0;j<table;j++){
				result *= sosuu;			  
			}
		}else if(table<0) //必要ないが念のため
		{
			printf("error??\n");
			for(j=0;j<table;j++){
				result /= sosuu[i];			  
			}
		}
	}
	return result;
	
}
unsigned int combination3(int n, int r)
{
	int i,j;
	int result[sizeof(sosuu)/sizeof(int)];
	/*素因数分解配列の初期化*/
	for(i=0;i<sizeof(sosuu)/sizeof(int);i++)
	{
		result[i] =0;
	}


	/*nCr = nC(n-r)を用いて計算量を減らす*/
	if(n-r<r)
	{
		r=n-r;
	}
		
	/*素因数配列の計算*/
	for(i=0,j=r;i<r;i++,n--,j--)
	{
		/*分子の大きいほうから順番に素因数分解をする*/
		primeFactor(result,n,bunsi);
		/*分母の小さいほうから順番に約分をする*/
		primeFactor(result,j,bunbo);
	}
	
	/*素因数配列を数値として出力する*/
	return calc(result);
	
}
int sub(void)
{
    int n, r, sum = 0;

	for (n = 0; n <= 9; n++) {
        for (r = 0; r <= n; r++) {
			sum += combination(n, r);
        }
   }
    return sum;
}

int sub2(void)
{
    int n, r, sum = 0;
	for (n = 0; n <= 9; n++) {
        for (r = 0; r <= n; r++) {
			sum += combination2(n, r);
        }
   }
    return sum;
}


int sub3(void)
{
    int n, r, sum = 0;
    
    for (n = 0; n <= 9; n++) {
        for (r = 0; r <= n; r++) {
			sum += combination3(n, r);
        }
    }
    return sum;
}


int main()
{
	int i, sum;
	clock_t t;

	//---------------
	// 再帰

	t = clock();//時間を格納
	sum = 0;
	for(i=0;i<10000;i++){
		sum += sub();
	}
	printf("%d\n", sum);
	printf("%.5f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);

	//---------------
	// 初級者さんの

	t = clock();//時間を格納
	sum = 0;
	for(i=0;i<10000;i++){
		sum += sub2();
	}
	printf("%d\n", sum);
	printf("%.5f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);

	//---------------
	// 組木さん

	t = clock();//時間を格納
	sum = 0;
	for(i=0;i<10000;i++){
		sum += sub3();
	}
	printf("%d\n", sum);
	printf("%.5f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);

	// quit
	printf("end.\n");
	getchar();
	return 0;
}

Justy

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#52

投稿記事 by Justy » 16年前


>良かったら直し方を教えて欲しいのですが、

 そうですね。一番簡単でほぼ確実なのは
 
 sub1.c/cppに sub()を。
 sub2.c/cppに sub2()とcombination2()を。
 sub3.c/cppに sub3()とcombination3()/primeFactor()など

 というふうに3つの関数(sub/sub2/sub3)とそれぞれのサブ関数を
別の翻訳単位(c/cppファイル)に分けてしまうこと、でしょうか。

 同一ファイルに書くと、最適化の関係で各関数の中で何をしているのかを分析した結果、
今回のように無駄だと判断され処理を省かれたり、関数そのものが消滅(インライン化)されたり
してしまうことがあります。

 ソースを分けて外部結合にしてしまえば、コンパイラは基本的にソース単位でしか
解析・最適化をしないので、別のファイルにある関数内で何をしているかわからない以上、
省略することなく関数を呼び出しにいきます。

 というわけで、この方法で main関数のあるソースから sub/sub2/sub3をループで呼び出せば、
ほぼ確実にループ回数分呼ばれるはずです。



>平等な測定を目指すには、
>sub1,sub2をちゃんと10000回ループさせればいいのですか?

 普通に考えると、そう思います。
 さすがに1回しか呼ばれないものと、1万回呼ばれたものの時間の比較は
あまり意味がないですし・・・。



>えーと、最適化というのはリリースモードでビルドすることですよね。

 一応そうです。
(一応と付けているのは、リリースビルドだからといって必ずしも最適化がかかっている、
とは限らないから、です)



>VCに生成された機械語を見る機能でもあるのでしょうか、

 外部ツールにも勿論そういうツールはありますが、
VisualStudioにもその機能は備わっています。

 EE版にあるかどうかはわからないのですが、VisualStudioのオプションの
「デバッグ->全般」の中に「アドレスレベルのデバッグを有効にする」と
「ソースが内場合逆アセンブルの表示」という項目がありますので
両方にチェックを付けて下さい。

 これで実行中にブレイクポイントなどで実行を止めて、
ソースコードに対して右クリックすると「逆アセンブルの表示」という項目が
追加されます。

 あとはこれを選択すると該当するソースのアセンブリコードが表示されます。
(ちなみに、ステップ実行で通常は入れない関数の中に入ることもできるようになりますよ)


 実はもう1つアセンブリコードを見る方法がありまして、
コンパイラの設定で「C/C++ -> 出力ファイル」の中に「ASMリストの出力」
という項目がありますので、それを「なし」からそれ以外の適当なものに変更します。
 すぐ下の「ASMリストの場所」は $(IntDir)\としておいて下さい。

 これでビルドすると各ファイルをコンパイルするたびに .objファイルが作られるディレクトリと
同じディレクトリに .asmファイルが作られます。
 これをテキストエディタで開くとアセンブリコードを見ることができます。

たいちう

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#53

投稿記事 by たいちう » 16年前

> 比較の方法が勉強になります。
> うーむ、一度見たこと無いと思いつかないですね。

↓この部分でしょうか?

> if (c1 != c2 || c2 != c3)
>   printf(" !!!!\n");

c1 = c2 = c3 だったらOK、そうでなかったらNGとしてます。

OKの場合は、(c1 == c2 && c2 == c3)。
c1 == c3が省けることは分かりますよね?

NGの場合は、!(c1 == c2 && c2 == c3)。
ここで「http://ja.wikipedia.org/wiki/%E3%83%89% ... 5%E5%89%87」を摘要すると、
(c1 != c2 || c2 != c3)となります。
この法則の使い方は覚えておいて損は無いでしょう。

最後に、最終的なif文の条件を日本語に置き換えて、
おかしくないことを一応確認して完成です。
慣れれば殆ど考えずに書けますよ。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#54

投稿記事 by こめかみ » 16年前

>>組木さん

先に分母を素数に分解すると計算が速くなる理屈がやっと(たぶん)つかめました。
確実に割り切れる数だけで割っていけば、
割り切れるか判らない数で片っ端から割っていくよりも、
確かに早くなりますね!


>>たいちうさん

ド・モルガンの法則だとは意識してませんでしたが、
そのif分の使い方自体は、
ゲームプログラミングの館をはじめとする、
管理人さんの館シリーズ(綾辻行人みたい)のおかげで見知っていました。
ただコンソールアプリは
ゲームプログラミングにも増して不慣れなもので、
最終的なif文の条件を日本語に置き換える、
というのと、
出力の全パターンを比較する、というような。
"全出力"
を組み合わせるというのがとても新鮮に感じられたのでした。

なんかたいちうさんって数学お詳しそうですね、
数学用語の変数が英語だったり。プロはみんな読めて当たり前なのですかね。
道はけわしいです。気合入れねば。



>>Justyさん

 ご指導ありがとうございます。

 調べたら機械語とアセンブリコードは別物でしたね。
 認識が甘かったです。

C言語→(コンパイル)→アセンブリ語→(アセンブリ)→マシン語=実行ファイル
この一連の流れ=ビルド

 こんな感じですかね。コンパイルとビルドの違いがやっと判った気がします。
(VCのコマンドの違いは依然不明なのが口惜しいですが)


>(ちなみに、ステップ実行で通常は入れない関数の中に入ることもできるようになりますよ)

 これが今の私にはわかりませんでした。
 "関数の中に入る"とはなんでしょうか。
 定義や宣言に飛ぶのとも、
クイックウォッチで変数の中身を見るのとも違うようですが。

 使いこなせてなくてすみません。
 質問する側もある程度わかってないと解説もままならないですよね...



>同じディレクトリに .asmファイルが作られます。

 これは出来ました。
 アセンブリってなんか、見てるだけでかっこいいですね。
 意味は全然わかりませんが、なぜかワクワクします。
 C言語からアセンブリコードに上手く切り替えられたとき、
 ゲームで、狙ってバグッたフィールドに入ったような印象を受けました。
 なんだろ、裏技っぽさが、私の原風景のようなものなのかもしれません。


 VC9EE版では
「ASMリストの出力」→ 「アセンブリの出力」
、と
 ちょっと名前が違いました。ご参考までに。
「ASMリストの場所」は、そのまんま「ASMリストの場所」でした。


> ソースを分けて外部結合にしてしまえば、コンパイラは基本的にソース単位でしか
>解析・最適化をしないので、別のファイルにある関数内で何をしているかわからない以上、
>省略することなく関数を呼び出しにいきます。

 ありがとうございます。
 ファイル分割は今日はもう無理そうなので、土曜の夜にでも挑戦してみます。
きっと日曜には結果を報告します。


 VCEEでNo:23335のテストコードで
Justyさんと同じ辺りのアセンブリコード発見出来ました。(長いので詳細は分けて投稿します)

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#55

投稿記事 by こめかみ » 16年前

 Cのソースはまったく同じコードの筈なのに変数名(?)が微妙に違うもんですね。
 EE版とPRO(?)版の違いってことですかねー。

 なんかC言語のたったこれだけの記述が、
こんなややこしい記述に変換されていることが実感出来て感慨深いです。

// 再帰



t = clock();//時間を格納
0040126B mov ebp,dword ptr [__imp__clock (4020A0h)]
00401271 push esi
00401272 push edi
00401273 call ebp
00401275 mov edi,eax
00401277 call sub (4011C0h)

sum = 0;

for(i=0;i<10000;i++){
0040127C imul eax,eax,2710h

sum += sub();

}

printf("%d\n", sum);
00401282 mov esi,dword ptr [__imp__printf (4020A8h)]
00401288 push eax
00401289 push offset string "%d\n" (402170h)
0040128E call esi

printf("%.3f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);
00401290 call ebp
00401292 sub eax,edi
00401294 mov dword ptr [esp+40h],eax
00401298 fild dword ptr [esp+40h]
0040129C fdiv qword ptr [__real@408f400000000000 (402188h)]
004012A2 fstp qword ptr [esp]
004012A5 push offset string "%.3f\x95b\n" (402174h)
004012AA call esi



//---------------

// 初心者さんの



t = clock();//時間を格納
004012AC call ebp
004012AE mov edi,eax
004012B0 call sub2 (401220h)

sum = 0;

for(i=0;i<10000;i++){
004012B5 imul eax,eax,2710h

sum += sub2();

}

printf("%d\n", sum);
004012BB push eax
004012BC push offset string "%d\n" (402170h)
004012C1 call esi

printf("%.3f秒\n",(clock()-t)/(double)CLOCKS_PER_SEC);
004012C3 call ebp
004012C5 sub eax,edi
004012C7 mov dword ptr [esp+4Ch],eax
004012CB fild dword ptr [esp+4Ch]
004012CF add esp,0Ch
004012D2 fdiv qword ptr [__real@408f400000000000 (402188h)]
004012D8 fstp qword ptr [esp]
004012DB push offset string "%.3f\x95b\n" (402174h)
004012E0 call esi
004012E2 add esp,0Ch



//---------------

// 組木さんまたは私



t = clock();//時間を格納
004012E5 call ebp
004012E7 mov dword ptr [esp+3Ch],eax

sum = 0;
004012EB mov dword ptr [esp+34h],0
004012F3 mov dword ptr [esp+38h],2710h

for(i=0;i<10000;i++){

sum += sub3();
004012FB xor ebx,ebx
004012FD xor edi,edi
004012FF xor esi,esi
00401301 test edi,edi
00401303 jl main+0B5h (401315h)
00401305 mov eax,esi
00401307 mov ecx,edi
00401309 call combination3 (401110h)
0040130E inc esi
0040130F add ebx,eax
00401311 cmp esi,edi
00401313 jle main+0A5h (401305h)
00401315 inc edi
00401316 cmp edi,13h
00401319 jle main+9Fh (4012FFh)
0040131B add dword ptr [esp+34h],ebx
0040131F sub dword ptr [esp+38h],1
00401324 jne main+9Bh (4012FBh)

}

printf("%d\n", sum);
00401326 mov eax,dword ptr [esp+34h]
0040132A mov esi,dword ptr [__imp__printf (4020A8h)]
00401330 push eax
00401331 push offset string "%d\n" (402170h)
00401336 call esi


#Justyさんの文章、段落の始めに空白がありますね。
#国語の教科書のような綺麗な文章に気が引き締まりました。

Justy

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#56

投稿記事 by Justy » 16年前


>C言語→(コンパイル)→アセンブリ語→(アセンブリ)→マシン語=実行ファイル
>この一連の流れ=ビルド

 概ねそんな感じですが、「リンク」も仲間に入れてあげて下さい。



> "関数の中に入る"とはなんでしょうか。

 あ、ステップインのことです。
 アセンブリ表示ができると、C/C++のソースからではステップインできなかった
関数にも入れるようになります。



> 「ASMリストの出力」→ 「アセンブリの出力」
> ちょっと名前が違いました。ご参考までに。

 あ、そうですね。すみません。
 「アセンブリの出力」です。多分下の「ASMリストの場所」と混ざって
脳内で変換されてしまったみたいです(w



>Cのソースはまったく同じコードの筈なのに変数名(?)が微妙に違うもんですね。

 たしかにシンボル名が違いますね。
 多分ランタイムの違いでしょう。うちはマルチスレッド(/MT or /MTd)でビルドしていますが、
こめかみさんはマルチスレッドDLL(/MD or /MDd)にしているのではないでしょうか。



>同じ辺りのアセンブリコード発見出来ました

 やはり微妙な違いはありますが、概ね前回の指摘通りになっているようです。
(1つ目と2つ目は 10000倍、3つ目は10000回ループ)



>#Justyさんの文章、段落の始めに空白がありますね
>#国語の教科書のような綺麗な文章に気が引き締まりました。

 あれ? あれれ?
 他のスレも見てみましたが、入れているのは私だけ???

 自分はずっとメールも含めて文章を書く時は文章の先頭は1字下げています。
 これはもう習慣になってしまっていますが、普通はあんまり入れないんでしょうか。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#57

投稿記事 by こめかみ » 16年前


> 概ねそんな感じですが、「リンク」も仲間に入れてあげて下さい。

了解です。リンクリンク、と。



> アセンブリ表示ができると、C/C++のソースからではステップインできなかった
>関数にも入れるようになります。

ステップイン、及びステップ実行は全然使ったことの無い(または忘れてた)機能でしたが、
MSDNで、何とか把握しました。
プログラムの流れをカーソルで追って見てみたいと以前から思っていたので、
目から鱗です。

で、機能はわかったのですが、
アセンブリ表示が出来なかったときに、
"ステップインできない関数"というのが、
私なりに探してみたのですが、
わかりませんでした。

No:23335のコードに、入れない関数はもともと無かったのでしょうか。
入れない関数の条件などを教えていただければ、
それをヒントに探してみようと思います。
よろしくお願いします。



> 多分ランタイムの違いでしょう。うちはマルチスレッド(/MT or /MTd)でビルドしていますが、
>こめかみさんはマルチスレッドDLL(/MD or /MDd)にしているのではないでしょうか。

ランタイムは確かにそのとおりだったので、(なんでわかるのだろう・・・)
マルチスレッド(/MT or /MTd)に変えて、
リリースモードでソリューションをいったんクリーンしてから、
ビルドしなおしてアセンブリコードを見たのですが、
Justyさんの環境で生成されたような、
clockとpirntfの具体的な変数、関数名が出てこないですね。
うーん、あまり気にしないことにします。



> やはり微妙な違いはありますが、概ね前回の指摘通りになっているようです。
>(1つ目と2つ目は 10000倍、3つ目は10000回ループ)

具体的に読めてるんですか?すごいなぁ。
でも今回の件のおかげで、今後はアセンブリ読めない私にも、
アセンブリコードの行数を頼りに、
インライン展開されていないか推測する、
という作戦が使えそうですね。

というわけで龍神録の館のファイル分割を参考にしながら、
リリースビルドしてよく見てみたところ、
おそらく10000倍は直っていないようです。
問題の性質上プロジェクトをUPいたしますのでお手数ですが、
何かやり方がまずいのか、大きな勘違いをしてるかなどの、
問題点のご指摘をお願いできませんでしょうか。
ttp://cid-d72819af3901253a.skydrive.live.com/self.aspx/%e5%85%ac%e9%96%8b%e8%b3%aa%e5%95%8f%e3%83%95%e3%82%a9%e3%83%ab%e3%83%80/%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab%e5%88%86%e5%89%b2.zip
私が未熟なばっかりにご迷惑おかけします。
よろしくお願いします。



> 自分はずっとメールも含めて文章を書く時は文章の先頭は1字下げています。
> これはもう習慣になってしまっていますが、普通はあんまり入れないんでしょうか。

私も、義務教育で叩き込まれた感覚的にも、
文章作法的にも、字下げしたい性格な筈なのですが。
この掲示板の文書作法にいつの間にか習っていたのか、
もしくは一文字の空白が積み重ねるタイムロスを本能的に回避していたのか・・・
よくわかりませんw

最古参であろうJustyさんが今まで誰にも何も言われなかったのなら、
きっと私が気にしすぎなのですね。


#スモールタグ(?)が見やすかったので、真似してみましたが、
#なかなか面倒ですね、これ。書き慣れるしかないんですかね。

Justy

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#58

投稿記事 by Justy » 16年前


>ステップイン、及びステップ実行は全然使ったことの無い(または忘れてた)機能

 あー、そうでしたか。

 

>アセンブリ表示が出来なかったときに、
>"ステップインできない関数"というのが、

 んーと、例えば今回のソースで説明できなくもないのですが、解りやすくするために
[color=#e0f0ff" size="-1" face="monospace]
#include <windows.h>
int main()
{
Sleep(10);
return 0;
}
[/color]
 のコードで考えます。

 アドレスレベルのデバッグがオフになっていた場合、
右クリックメニューにも逆アセンブル表示は出てこないですし、Sleep()のところで
ステップインしてもそのまま何事も無かったかのように次の returnのところに
進んでしまいます。

 しかし、アドレスレベルのデバッグがオンになっていると逆アセンブル表示ができるので、
Sleep()のところまで進めたら逆アセンブル表示をします。
[color=#e0f0ff" size="-1" face="monospace]
Sleep(10);
->0042D69E mov esi,esp
0042D6A0 push 0Ah
0042D6A2 call dword ptr [__imp__Sleep@4 (497214h)]
[/color]
 するとこんな感じの表示になります。
 逆アセンブル表示中のステップ実行はアセンブリ1行1行のステップになりますので、
callのところまで進めて、ステップインすると・・・・Sleep()の中に入ることができます。

[color=#e0f0ff" size="-1" face="monospace]
->7C802442 mov edi,edi
7C802444 push ebp
7C802445 mov ebp,esp
7C802447 push 0
7C802449 push dword ptr [ebp+8]
7C80244C call 7C80239C
7C802451 pop ebp
7C802452 ret 4
[/color]
 まぁ、中に入ってもC/C++のソースではないんですけどね。



>No:23335のコードに、入れない関数はもともと無かったのでしょうか

 無くはないです。
 例えば、Debugビルドの方の return 0付近に cppソースの方にはないのですが、
コンパイラが自動的に埋め込んだチェックルーチンの呼び出しがあります。
[color=#e0f0ff" size="-1" face="monospace]
return 0;
00431BB4 xor eax,eax
}
00431BB6 pop edi
00431BB7 pop esi
00431BB8 pop ebx
00431BB9 add esp,0E8h
00431BBF cmp ebp,esp
00431BC1 call @ILT+3775(__RTC_CheckEsp) (42FEC4h)
[/color]
 この __RTC_CheckEsp()なんかは通常では入れないと思います。



>マルチスレッド(/MT or /MTd)に変えて、

 プロジェクトの方見てみました。
 デバッグビルドの設定はマルチスレッドデバッグ(MTd)になっていましたが、
リリースビルドの設定がマルチスレッドDLL(MD)になっていました。
 多分そのせいでしょう。



>リリースビルドしてよく見てみたところ、
>おそらく10000倍は直っていないようです。

 おっと。そうですね。
 これはちょっと懸念していたのですが、多分大丈夫だろうとスルーしてました。
 えーと、前回コンパイラは翻訳単位(c/cppファイル)毎に最適化すると
書きましたが、実はリンクが最適化してくれることがあります。

 それが、プロジェクトの設定「C/C++ -> 最適化」の中にある「プログラム全体の最適化」という設定です。
 これはリ翻訳単位間の最適化を行いリンカが行い、コードを生成する設定で、
現在リリースモードでこれが有効になっています。

 これが有効になっていると翻訳単位が別になっていても必ず同じ値を返す関数なら
10000倍するようなコードが生成されてしまいます。

 なので、これを「いいえ」にすれば、ループできちんと呼び出されます。



>私も、義務教育で叩き込まれた感覚的にも、
>文章作法的にも、字下げしたい性格な筈なのですが。

 あれからいろいろ見てみました。
 メール・掲示板では結構字下げしない人が多いですね。
 全く居ないわけではないのですが、少ないようです。
 
 ただ、さすがに印刷物やビジネス文書で字下げをしない人は全くいませんでた(w
 
 「メール 字下げ」で検索すると字下げに関する話がいろいろ出てきます。

段落のはじめの字下げをする?、しない? -OKWave
http://okwave.jp/qa591050.html

一字下げについて考える - BLOG STATION
http://blog.goo.ne.jp/kanimaster/e/c596 ... aa011d6728

アポロ漫録: 文章の整形 (2) - 段落と字下げ
http://taroturanai.spaces.live.com/blog/cnsy

 面白いですねぇ。



>スモールタグ(?)が見やすかったので

 テンプレートを用意してそこの間に書くだけなんですよ(w
 それでも面倒といえば面倒かもしれませんが。

組木紙織

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#59

投稿記事 by 組木紙織 » 16年前

字下げなどの、表示のルールはその場その場によって変わるので、
周りに合わせてあげればそれで十分だと思います。
(インデントのルールみたいに、気にする人は気にするんですよね。
 面白い。
)



>テンプレートを用意してそこの間に書くだけなんですよ(w
>それでも面倒といえば面倒かもしれませんが。

引用だけに使うんだったらツール作って通してあげれば面倒じゃないですね。
作るのが面倒かも知れませんが。

#アセンブラの話がここのとこ続いてたので気になって8086のアセンブラをはじめてみました。
#難しいというより、面倒です。

box

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#60

投稿記事 by box » 16年前

このトピックをしばらく見ていないうちに、
タイトルと全然違う内容になっているようです。

有意義な話ですので、続けてくださるのはよいのですが、
新たにトピックを立てられる方がいいようにも思います。

Justy

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#61

投稿記事 by Justy » 16年前


組木紙織さん
>引用だけに使うんだったらツール作って通してあげれば面倒じゃないですね。

 定型分を呼び出すようなマクロという形で、一応簡単なのは作ってあるんですよ。



boxさん
>タイトルと全然違う内容になっているようです

 たしかに。
 もうそれほど長くは続かないと思いますが、
予想外に続くようならそうした方が良さそうですね。

こめかみ

Re:数学でいう組み合わせの問題の解き方で、再帰ではなく約分を使って解く(実装する)には?

#62

投稿記事 by こめかみ » 16年前

すみません、遅くなりました。


>boxさん



すみませんもうちょっとで終わります。
ご迷惑おかけします。


>ツールについて


なにやらめくるめく自作ツールワールドの気配・・・!
興味のあるところですがまたスレッドが伸びてしまいそうですし、
その話題はまたの機会にお願いします。

とりあえずオペラのメモ機能がそれっぽく使えそうなのでそれで代用させていただきます。



>Justyさん



> 多分そのせいでしょう。

そのせいでした。
うまくいきました!

<証拠>

t = clock();//時間を格納
00401004 call clock (4014A4h)
00401009 mov ebx,eax
sum = 0;
0040100B xor esi,esi
0040100D mov edi,2710h

for(i=0;i<10000;i++){
sum += sub();
00401012 call sub (401150h)
00401017 add esi,eax
00401019 sub edi,1
0040101C jne main+12h (401012h)
}

printf("%d\n", sum);
0040101E push esi
0040101F push offset string "%d\n" (40E198h)
00401024 call printf (4013DFh)




ステップインについて

解りやすくするためのコードとNo:23335のコードともにアセンブリの関数に入ることが出来ました。
おかげさまでこの件は完全に納得出来ました。
ありがとうございました。


そしていよいよちゃんとした比較結果が出せました。

組木さんは逆転なるか!

1895815408
104.828秒
1895815408
0.375秒
1895815408
15.157秒
end.

一番上が再帰ですが...
15.157秒
再帰よりははるかに早いですね。

初級者さんのは流石の一秒未満
これはやりがいを追及するのも超納得です。

でもこれ組木さんのがNo:23335の改定前のを修正したものなので、

次は改定版で比較比較っと。

1895815408
104.812秒
1895815408
0.391秒
1895815408
15.140秒
end.

ありゃ
一回の比較の実行じゃ大して変わりませんでしたか。


最後にもう一度、皆さん長いことお付き合いいただきありがとうございました。



#あー、予想はしてたけど、
#時間経ちすぎてサンプルの館とかもうどうでもよくなっちゃったなぁ。
#やっぱ勢いのあるときに行動するのは大事ですね。
#ゲームだ、ゲームを作って掲載してもらおう。

閉鎖

“C言語何でも質問掲示板” へ戻る