- ベストアンサー
文字列
お世話になります。最近文字列を勉強し始めたのですが、文字列になるとてんでダメになってしまいます・・・。お聞きしたいことは2つあります。 1.テキストファイルから英単語を読み込み、それらをアルファベット順に並べ、新たにテキストファイルに書き込むというものです。例えば、 january February march April Apple と読み込んだなら、 Apple April February january march と書き込みたいのです。文字列を配列に格納し、一文字一文字比較していきたいのですが、文字だとどのように比較して順番を変えればよいのでしょうか。数字の比較なら簡単に出来るんですが、文字だとわからなくなってしまいます。 2.これも同じく文字なのですが、テキストファイルから英単語、あるいは英語のフレーズを2つ読み込み、その2つの英語がアナグラムかどうかを判定するものです。 アナグラム→http://d.hatena.ne.jp/keyword/%A5%A2%A5ʥ%B0%A5%E9%A5%E0 例えば、「O, Draconian devil!」と「Leonardo da Vinci!」を読み込んでアナグラムか判定します。ちなみにこれはアナグラムです。これも一文字一文字比較していくと思うのですが、どのように比較すれればよいのかがわかりません。どなたか宜しくお願い致します。
- みんなの回答 (10)
- 専門家の回答
質問者が選んだベストアンサー
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> int isanagram1(char *str1, char *str2) { int c1[256] = {0}, c2[256] = {0}, i; while(*str1) c1[tolower(*str1 ++)] ++; while(*str2) c2[tolower(*str2 ++)] ++; for(i = 0; i < 256; i ++){ if(isalpha(i) && (c1[i] != c2[i])) return 0; } return 1; } void selectalpha(char *str) { char *c0, *c1; for(c0 = c1 = str; *c0 = tolower(*c1); c1 ++) c0 += (isalpha(*c0) != 0); } void count(char *str, int *c) { char *al = "abcdefghijklmnopqrstuvwxyz", *p; selectalpha(str); while(*str){ if((p = strchr(al, *str ++)) != NULL) c[p - al] ++; } } int isanagram2(char *str1, char *str2) { int c1[26] = {0}, c2[26] = {0}, i; count(str1, c1); count(str2, c2); for(i = 0; i < 26; i ++){ if(c1[i] != c2[i]) return 0; } return 1; } int compc(const void *c1, const void *c2) { return *(char *)c1 - *(char *)c2; } int isanagram3(char *str1, char *str2) { selectalpha(str1); selectalpha(str2); qsort(str1, strlen(str1), sizeof(char), compc); qsort(str2, strlen(str2), sizeof(char), compc); return !strcmp(str1, str2); } int main(void) { char str1[32] = "O, Draconian devil!"; char str2[32] = "Leonardo da Vinci!"; char *result[2] = {"Not Anatram", "Anagram"}; puts(str1); puts(str2); printf("%s\n", result[isanagram1(str1, str2)]); puts(str1); puts(str2); printf("%s\n", result[isanagram2(str1, str2)]); puts(str1); puts(str2); printf("%s\n", result[isanagram3(str1, str2)]); puts(str1); puts(str2); return 0; }
その他の回答 (9)
- yama5140
- ベストアンサー率54% (136/250)
No4, 5 です。 >1つ目のプログラムは出来ました。皆様ありがとうございました! よかったですね、「ソート」は一度習得すれば、あとは応用ですから。 ------------------------------------------------- >No.3 様が以前ご指摘されたように、まずO, Draconian devil!を >昇順に並べ替え、次にLeonardo da Vinci!を同じく昇順に並べ替 >えようとしたのですが・・(投稿者一部修正) ★No4 で記しましたように、この方法では難しいと思います。 >それと、比較はcase-insensitiveなので大文字小文字は区別しません。 ★このことの処理を、「昇順に並べ替え」る前にしないといけませんね。 ------------------------------------------------- >1つの文字列を昇順にする方法が見つかりません。 ★以降に示します(「丸投げ」返球ですが、この方法では本来の目的が叶えられないことを示すため、ソース全文を投稿します)。 #include <stdio.h> #include <string.h> void Sort32( char cWork[] ) { int i, j, iLen; char cDummy; iLen = strlen( cWork ); for( i = 0; i < iLen; i++ ){ for( j = i; j < iLen; j++ ){ if( cWork[i] < cWork[j] ) continue; cDummy = cWork[i]; cWork[i] = cWork[j]; cWork[j] = cDummy; } } } void main() { char cDevil[32] = "O, Draconian devil!"; char cVinci[32] = "Leonardo da Vinci!"; Sort32( cDevil ); Sort32( cVinci ); printf( "%s\n", cDevil ); printf( "%s\n", cVinci ); } 注:インデントに全角空白を用いています(タブに一括変換して下さい)。 ☆実行すると、 「__!,DOaacdeiilnnorv」 「__!LVaacddeiinnoor」 となります。 (投稿表示のため、半角スペースの代わりに _ を使用) No.3 さんの >並べ替えた文字列を、先頭から1文字ずつ比べていきます。 ★↑難しいどころか、できないですよね。 ★「並べ替え」方式でなく、「26文字種の使用数をカウント」方式を お勧めします( case-insensitive 対応)。 これですと、例のように文字列長が異なる場合にも使えます。
お礼
文字列をそれぞれ大文字に変換してソートし比較するようなプログラムを作りました。 しかしどちらか一方の文字列に!や,が含まれているとやはり比較してもアナグラムと判断できませんね・・・。 大文字変換の際、配列が!や,に当たると、その配列自体を削除してしまえば、と考えたのですが、その方法がわかりませんでした・・・。 yama5140様がご指摘したように、使用数をカウントする方法で考えたいと思います。本当にありがとうございました!
- asuncion
- ベストアンサー率33% (2127/6289)
> この方法を調べても、2つ以上の文字列を昇順にする方法(私の1つ目の質問のような)は沢山見つかるのですが、 > 1つの文字列を昇順にする方法が見つかりません。 ソートなどのアルゴリズムについて解説している書籍で 勉強なさることを強くおすすめします。
お礼
近いうちに探してみたいと思います。ありがとうございました。
- asuncion
- ベストアンサー率33% (2127/6289)
> fgetsはファイルを読み込むための関数でしたよね fgets()の第3引数にstdinを指定すれば、標準入力(例:キーボード)からの 入力を受け取れます。
- asuncion
- ベストアンサー率33% (2127/6289)
> 「O, Draconian devil」のようにスペースを入れると、cDevilにO,が、cVinciにDraconianが入ってしまい、セグメンテーションエラーを起こしてしまいました。 > 私のこのやり方はまずかったでしょうか・・・? scnaf()を使うと、スペースを受け取った時点で、例えばcDevilへの 入力を終了したものとみなします。 別の関数、例えばfgets()あたりを使ってみてはいかがでしょうか。 > 例えば単純に、erosとroseを入力してもアナグラムではないと表示されます。 どういったコードでですか? > と、Count26が2つあるからでしょうか。 何をしたときに矛盾と出たのでしょうか? 行なったこととエラーメッセージを正確に教えてください。 なお、どの関数を何回呼び出しても全く問題ありません。
補足
メンテ終わりましたね^^ 初めの質問の所で誤りがありました。 アナグラムかどうか判断するプログラムは、テキストファイルから読み込むのではなく、キーボードから打ち込みます。 それと、比較はcase-insensitiveなので大文字小文字は区別しません。すみませんでした。 1つ目のプログラムは出来ました。皆様ありがとうございました! >asuncion様 キーボードからの入力でscanfを使用したのですが、ご指摘されたようにスペースを使うと正しく入力されないですね。 fgetsはファイルを読み込むための関数でしたよね。これは私の誤りでした。申し訳ありません。 スペースを使っても正しく入力できる関数はありますでしょうか。 eros、roseを試したコードはyama5140様のコードで試させて頂きました。 以下、yama5140様のコードです。 char cDevil[32], cVinci[32]; int iCnt[26] = { 0 }, iCode, i; for( i = 0; i < 32; i++ ){ // cDevil if( 0x00 == cDevil[i] ) break; // 文字列終わり if( 0 == isalpha( cDevil[i] ) ) continue; // 「英文字」判定 iCode = toupper( cDevil[i] ) - 0x41; // 大文字化(◆) iCnt[ iCode ]++; // 加算 } for( i = 0; i < 32; i++ ){ // cVinci if( 0x00 == cVinci[i] ) break; if( 0 == isalpha( cVinci[i] ) ) continue; iCode = toupper( cVinci[i] ) - 0x41; iCnt[ iCode ]--; // 減算 } for( i = 0; i < 26; i++ ){ // アナグラム判定 if( 0 == iCnt[i] ) continue; printf( "アナグラムではありません\n" ); break; } asuncion様が以前ご指摘されたように、まずO, Draconian devil!を昇順に並べ替え、次にLeonardo da Vinci!を同じく昇順に並べ替えようとしたのですが、 この方法を調べても、2つ以上の文字列を昇順にする方法(私の1つ目の質問のような)は沢山見つかるのですが、1つの文字列を昇順にする方法が見つかりません。 良い方法はありますでしょうか。
- yama5140
- ベストアンサー率54% (136/250)
No4 です。 >私は配列をs[256]と1つだけ用意し、 >テキストファイルから読み込んでいるのですが テキストファイルが、 january February march April Apple のようになっていると s[256] には最後の Apple だけが入り、 それ以前の行(レコード)のデータは上書きされてしまいます。 (まさか連結なんかしてないよね?←複雑そう) ★次のように、格納してからでないと・・・。 char cBuf[256], cStore[GYO][256]; while( NULL != fgets( cBuf, 256, fp ) ){ strcpy( cStore[iLine++], cBuf ); // 格納 } ・ (ソート) if( 0 < strcmp( cStore[i], cStore[j] ) ){ (入れ替え(別の配列に待避などで)) } (出力) ☆ソートについては、検索すればいっぱい出てきます。 -------------------------------------------------- ★前回の回答で、関数化できる部分がありました。 ・ Count26( cDevil, iCnt, 1 ); // 関数呼び出し Count26( cVinci, iCnt, -1 ); ・ } void Count26( char cWork[], int iCnt[], int iAdd ) { int i, iCode; for( i = 0; i < 32; i++ ){ if( 0x00 == cWork[i] ) break; // 文字列終わり if( 0 == isalpha( cWork[i] ) ) continue; // 「英文字」判定 iCode = toupper( cWork[i] ) - 0x41; // 大文字化にして iCnt[ iCode ] += iAdd; // 加減 } }
補足
ご返答ありがとうございます! まず2つ目で質問です。 とりあえずキーボードから文字を入力し試しました。 printf("文字を入力\n"); scanf("%s", cDevil); scanf("%s", cVinci); そしてyama5140さんのおっしゃるようにプログラムを動かしてみたのですが、エラーが起きてしまいました。 「O, Draconian devil」のようにスペースを入れると、cDevilにO,が、cVinciにDraconianが入ってしまい、セグメンテーションエラーを起こしてしまいました。 私のこのやり方はまずかったでしょうか・・・? 2、スペースを入れなくても、どの文字を入力してもアナグラムではないと表示されてしまいます。 例えば単純に、erosとroseを入力してもアナグラムではないと表示されます。 3、関数ですが、矛盾と出てしまいました。 Count26( cDevil, iCnt, 1 ); Count26( cVinci, iCnt, -1 ); と、Count26が2つあるからでしょうか。
- yama5140
- ベストアンサー率54% (136/250)
★1.は文字列群を昇順にソートし、ソート結果をファイル出力。 ・ソートでは strcmp() を用いる(戻り値、正負で大小判定)。 ---------------------------------------------- ☆質問者様の2.における例文字列、 「O, Draconian devil!」と 「Leonardo da Vinci!」を見ると、 , が一方のみに使われ、文字列長が異なる。 (プロポーショナルでの見かけの文字列長調整?)。 ことから、2つの文字列内をソートし、結果どうしを「比較」する方法では難しいと思います。 ★大文字・小文字を同じとして、26文字種の使用数をカウントし、 双方の使用数が同じかどうかで判定すれば、と思います。 a と A の使用数が iCnt[0] に入ります(◆)。 char cDevil[32], cVinci[32]; int iCnt[26] = { 0 }, iCode, i; for( i = 0; i < 32; i++ ){ // cDevil if( 0x00 == cDevil[i] ) break; // 文字列終わり if( 0 == isalpha( cDevil[i] ) ) continue; // 「英文字」判定 iCode = toupper( cDevil[i] ) - 0x41; // 大文字化(◆) iCnt[ iCode ]++; // 加算 } for( i = 0; i < 32; i++ ){ // cVinci if( 0x00 == cVinci[i] ) break; if( 0 == isalpha( cVinci[i] ) ) continue; iCode = toupper( cVinci[i] ) - 0x41; iCnt[ iCode ]--; // 減算 } for( i = 0; i < 26; i++ ){ // アナグラム判定 if( 0 == iCnt[i] ) continue; printf( "アナグラムではありません\n" ); break; } 注:インデントに全角空白を用いています。
補足
皆様ご回答本当にありがとうございます! 皆様のいうstrcmp関数を始めて知りました。全然勉強不足です。頑張ります! さて、1つ目ですが、strcmpの使い方を調べ数時間粘ったのですが、躓いてしまいました。 strcmpを使う場合、2つの配列が必要となりますよね。 しかしこの場合、私は配列をs[256]と1つだけ用意し、テキストファイルから読み込んでいるのですが、これだと比較をどうすればいいのかがわかりません。 配列を2つ用意しようとすると、今度はどうテキストファイルから文字を読み込み2つの配列に格納すればいいのかがわからなく躓いてしまいました。 これを解決する方法はありますでしょうか?
- asuncion
- ベストアンサー率33% (2127/6289)
> 「O, Draconian devil!」と「Leonardo da Vinci!」 双方の文字列をそれぞれ昇順または降順に並べ替えます。 並べ替えた文字列を、先頭から1文字ずつ比べていきます。 途中に食い違いがあればアナグラムではありません。 最後まで同じであればアナグラムです。
- Tasuke22
- ベストアンサー率33% (1799/5383)
文字列の比較の仕方ですね。 strcmp などの関数を使うと大小関係が得られます。 a<b<・・・<z です。アスキーコードだったら a=31 b=32 となっています。 御自分で比較する場合は、文字として比較します。 str1[0] < str2[0] といった具合ですが、文字列の数だけ行う必要が あります。
- yaemon_2006
- ベストアンサー率22% (50/220)
1、strcmp 2、それぞれの文字数を比較
お礼
悩んでいた所が一気に解決されました^^驚きました! 本当に感謝感謝です!ありがとうございます!!