if-else文とswitch-case文の代わりに配列を使用

2943 ワード

テーブル駆動法(Table-Driven Approach)は,複雑なif-elseやswitch-caseの論理的判断の代わりに,テーブル内で情報を検索する.これは設計のテクニックで、多くの場合を応用することができて、プログラムの性能を高めることができるだけではなくて、コードの量を大幅に減らすことができて、コードを効率的で優雅になります.次に、この方法の利点を一例を用いて示す.
指定した月の日数を取得するために関数を書く必要がある場合は、次のコード・セグメントを書く可能性があります.
//      ,      1,    0
int IsLeapYear(unsigned int year)
{
	if ((0 == year % 4 && 0 != year % 100) || (0 == year % 400))
		return 1;
	return 0;
}

//  year month    
unsigned int GetMonthDays(unsigned int year, unsigned int month)
{
	unsigned int days = 0;
	if (1 == month) {days = 31;}
	else if (2 == month)
	{
		if (IsLeapYear(year)) {days = 29;}
		else {days = 28;}
	}
	else if (3 == month) {days = 31;}
	else if (4 == month) {days = 30;} 
	else if (5 == month) {days = 31;} 
	else if (6 == month) {days = 30;} 
	else if (7 == month) {days = 31;} 
	else if (8 == month) {days = 31;} 
	else if (9 == month) {days = 30;} 
	else if (10 == month) {days = 31;} 
	else if (11 == month) {days = 30;} 
	else if (12 == month) {days = 31;} 

	return days;
}

上のコードは複数のif-else文を通じて、現在の月の総日数を取得します.コードははっきりしていますが、少し冗長で重複しているように見えます.また、具体的な日数を得るには、何度も比較する必要があります.月が経つにつれて、比較回数が多くなります.
コンパイラはswitch-caseを実装する際に、比較のパフォーマンスを最適化するためにホップテーブルを使用します.次に、上記のコードのif-else文をswitch-caseの実装に変更します.
//  year month    
unsigned int GetMonthDays(unsigned int year, unsigned int month)
{
	int days = 0;
	switch(month)
	{
		case 1: days = 31; break;
		case 2: 
		{
			if (IsLeapYear(year)) {days = 29;}
			else {days = 28;}
		}break;
		case 3: days = 31; break;
		case 4: days = 30; break;
		case 5: days = 31; break;
		case 6: days = 30; break;
		case 7: days = 31; break;
		case 8: days = 31; break;
		case 9: days = 30; break;
		case 10: days = 31; break;
		case 11: days = 30; break;
		case 12: days = 31; break;
		default: break;
	}

	return days;
}

上のswitch-case実装については,効率がどんなに向上しても,単純にコードを見るとif-elseとあまり変わらないような気がしますが,コードは少し冗長で繰り返しても簡潔ではありません.他のシーンに適用すると、caseブランチが多くなり、caseブランチのコード量が大きくなるにつれて、コードのメンテナンスが難しくなります.実際には、ブランチがますます多くなると、caseブランチごとの論理は実際には同じであり、その中のデータが変わっただけであることがわかります.ブランチを付けるときにうっかり論理のコードを修正してしまうと、エラーが発生しやすくなります.
この場合、テーブル駆動法により、変化するデータと変化しない論理を容易に分離することができ、コードは以下の通りである.
//      ,      1,    0
int IsLeapYear(unsigned int year)
{
	if ((0 == year % 4 && 0 != year % 100) || (0 == year % 400))
		return 1;
	return 0;
}

static unsigned int monthdays[2][12] = 
{
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
}

//  year month    
unsigned int GetMonthDays(unsigned int year, unsigned int month)
{
	return monthdyas[IsYeapYear(year)][month - 1];
}

今回のコードは十分きれいで、コード量が少なくなっただけでなく、配列アドレスのオフセット操作によってデータを迅速に得ることができ、比較論理を大幅に必要とせず、より効率的になりました.しかし、このような表駆動法はすべての場合に直接使用できるものではなく、判断するデータが連続していなければ、1、5、16、222のように、別の変換が必要であり、例えば1つのmapで元のデータを連続した数字に変換することで、表駆動法を使用することができる.また,他のより高度なデータ構造を用いて実現することも可能であり,配列に限定される必要はなく,時間を交換するために空間を用いることが肝心である.