プロが教えるわが家の防犯対策術!

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void swap(char p[], char q[]);
void get(char bufG[],char **p_str);

typedef struct {
int number;
char *class_type;
char* name;
char *subject;
int num_akaten;
} my;
my *data;
int main(int argc, char* argv[])
{
FILE *fp;
int field = 0, line = 0;
char buf[1000];
char bufG[1111];
char *str;

int m;
int line2 = 0;

if((fp=fopen("test3.csv","r"))==NULL){
printf("ファイルが開けません");
}

while(fgets(buf, 1000, fp) != NULL){
line2++;
}
fclose(fp);
printf("%d\n", line2);
if((fp=fopen("test3.csv","r"))==NULL){
printf("ファイルが開けません");
}

data = (my *)malloc(sizeof(my) * line2);

while(fgets(buf,1000,fp) != NULL){
str = buf;
while(*str != '\0'){
get(bufG,&str);
switch(field){
case 0:
data[line].number = atoi(bufG);

break;
case 1:
data[line].class_type = (char *)malloc(strlen(bufG) +1);
strcpy(data[line].class_type, bufG);
break;
case 2:
data[line].name = (char *)malloc(strlen(bufG) + 1);
strcpy(data[line].name, bufG);
break;
case 3:
data[line].subject =(char *)malloc(strlen(bufG) + 1);
strcpy(data[line].subject, bufG);
break;
case 4:
data[line].num_akaten = atoi(bufG);
}
str++;
field++;
}
line++;
field = 0;
}



fclose(fp);
for(m = 0; m < line; m++){
printf("%d\n", data[m].number);
printf("%s\n", data[m].class_type);
printf("%s\n", data[m].name);
printf("%s\n", data[m].subject);
printf("%d\n", data[m].num_akaten);
}
return 0;

}

void get(char bufG[],char **p_str) {
int i;
char *str;
str = *p_str;
for(i = 0; *str != ',' && *str != '\0' ; i++){
if(*str == '\n'){
bufG[i] = '\0';
}
else{
bufG[i] = *str;
}
str++;
}
bufG[i] = '\0';
*p_str = str;
return ;
}

前問題の解答例を提示してくださった方のサンプルをコピペさせてもらって成績の項目を追加しています。本文ではget関数の型はchar*型で文字列を返していますが今回の件では余り関係ないのでこのままで。

ファイルの中身は
1,犬,ボルト,国語,2
2,猫,山田,数学,1
3,犬,鈴木,英語,2
4,犬,居合,国語,1
5,猫,伊藤,数学,2
6,猫,斎藤,数学,1
のような感じになってます。
クラスの名前は今までアルファベットできたがこちらにしました。
名前自体に意味はありません。クラス名を文字列にしただけです。

やりたいソートの条件は赤点の多い順が優先でクラスと好きな教科が
同じものをソートしたい。優先順位は赤点→クラスとが一致→教科が一致
なので
1,犬,ボルト,国語,2
3,犬,鈴木,数学,2
5,猫,伊藤,数学,2
2,猫,山田,数学,1
6,猫,斎藤,数学,1
4,犬,居合,国語,1

このようなファイルに並び替えされます。
今試してるのは↑のソースで各値を入れ終わった後に

for(p = 0; p < line; p++){
for(q = p + 1; q < line; q++){
if(data[p].num_akaten < data[q].num_akaten){
swap(&p,&q);//swapはp行とq行の全項目を入れ替える関数
  }
}
}

こうするととりあえず赤点数の順にはなります
これからさらに上記のような並びにしたいです。(クラスと教科が同じ順)
ここからどうすればいいでしょうか?
出来れば同じループ内で処理したいです。
私が考えていたのがp行のクラスとq行のクラスが同じ場合
p+1行目のクラスがp行目のクラスと異なっているなら
p+1行目とq行目を交換するというのを試していましたが
うまくいかないのでこの方法はダメなのでしょう。


他にもっとよい方法がある といった場合教えていただけると助かります。

A 回答 (4件)

以下のようにしてください。


---------------------
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void swap(char p[], char q[]);
void get(char bufG[],char **p_str);
intcomp_rtn(const void *p1,const void *p2);//これを追加

typedef struct {
int number;
char *class_type;
char* name;
char *subject;
int num_akaten;
} my;
my *data;
int main(int argc, char* argv[])
{
FILE *fp;
int field = 0, line = 0;
char buf[1000];
char bufG[1111];
char *str;

int m;
int line2 = 0;

if((fp=fopen("test3.csv","r"))==NULL){
printf("ファイルが開けません");
}

while(fgets(buf, 1000, fp) != NULL){
line2++;
}
fclose(fp);
printf("%d\n", line2);
if((fp=fopen("test3.csv","r"))==NULL){
printf("ファイルが開けません");
}

data = (my *)malloc(sizeof(my) * line2);

while(fgets(buf,1000,fp) != NULL){
str = buf;
while(*str != '\0'){
get(bufG,&str);
switch(field){
case 0:
data[line].number = atoi(bufG);

break;
case 1:
data[line].class_type = (char *)malloc(strlen(bufG) +1);
strcpy(data[line].class_type, bufG);
break;
case 2:
data[line].name = (char *)malloc(strlen(bufG) + 1);
strcpy(data[line].name, bufG);
break;
case 3:
data[line].subject =(char *)malloc(strlen(bufG) + 1);
strcpy(data[line].subject, bufG);
break;
case 4:
data[line].num_akaten = atoi(bufG);
}
str++;
field++;
}
line++;
field = 0;
}



fclose(fp);
qsort(data,line,sizeof(my),comp_rtn);//ここを追加
//ソート結果を見やすくするために表示方法を変更(項目毎に改行しない。)
for(m = 0; m < line; m++){
printf("%d ", data[m].number);
printf("%s ", data[m].class_type);
printf("%s ", data[m].name);
printf("%s ", data[m].subject);
printf("%d\n", data[m].num_akaten);
}
return 0;

}

void get(char bufG[],char **p_str) {
int i;
char *str;
str = *p_str;
for(i = 0; *str != ',' && *str != '\0' ; i++){
if(*str == '\n'){
bufG[i] = '\0';
}
else{
bufG[i] = *str;
}
str++;
}
bufG[i] = '\0';
*p_str = str;
return ;
}
//この関数を追加
intcomp_rtn(const void *p1,const void *p2){
intret;
my *data1 = (my*)p1;//void*型で受けた変数を本来の型に戻す
my *data2 = (my*)p2;//同上
//赤点の多い順でソート(降順)
//降順の為data2-data1とする。(昇順ならdata1-data2)
ret = data2->num_akaten - data1->num_akaten;
//赤点の数が同じなら(ret==0)、次のソート、違うならretの値でreturn
if (ret != 0) return ret;
//クラスでソート(昇順)
ret = strcmp(data1->class_type,data2->class_type);
//クラスが同じなら(ret==0)、次のソート、違うならretの値でreturn
if (ret != 0) return ret;
//好きな教科でソート(昇順)
ret = strcmp(data1->subject,data2->subject);
//比較結果でrturn(結果が同じでも次の比較がないので戻るだけ)
return ret;
}

---------------------
実行結果は以下の通り
ソート前のデータ
1,犬,ボルト,国語,2
2,猫,山田,数学,1
3,犬,鈴木,英語,2
4,犬,居合,国語,1
5,猫,伊藤,数学,2
6,猫,斎藤,数学,1

実行結果
6
3 犬 鈴木 英語 2
1 犬 ボルト 国語 2
5 猫 伊藤 数学 2
4 犬 居合 国語 1
6 猫 斎藤 数学 1
2 猫 山田 数学 1

尚、犬、猫の順番が逆転してますが
シフトJISの漢字コードは
犬=8CA2 猫=944Cの為、犬が前にきます。(クラス:昇順のため)
windows-xp,visual-c++(2008)で確認済み
    • good
    • 0
この回答へのお礼

できました。ありがとうございました。

お礼日時:2009/07/13 09:26

(過去の「質問」、「回答」への「補足」を読むと、う~ん、いろいろとムズイものがあるかと・・)



   http://okwave.jp/qa5108906.html 配列を返す
   http://okwave.jp/qa5108691.html 関数化
   http://okwave.jp/qa5094929.html ポインタ(追加質問)
   http://okwave.jp/qa5092628.html ポインタ
   http://okwave.jp/qa5092003.html strtok
   http://okwave.jp/qa5087561.html メモリ2(ポインタ編)
   http://okwave.jp/qa5087031.html メモリ
   http://okwave.jp/qa5084764.html 文字入れ替え
   http://okwave.jp/qa5084141.html 配列

  ずっとスルーしてきたけど、今回で終了かな、と思い投稿します。
  それにしても、頑固ですね、初回とほぼ同じだ。

 (「メモリ2(ポインタ編)」の int check(int a[100], int n); が気がかり)
++++++++++++++++++++++++++++++++++++++++++++++
>クラスと好きな教科が同じものをソートしたい。
>クラスとが一致→教科が一致

 「同じもの」、「一致」は、「ソートの条件」になりえません。「検索」が妥当かと・・。

 てか、そもそも、

 ・「get関数の型はchar*型で文字列を返しています」の必要があるの?。呼び出し元定義の「引数」でいいのでは。
 ・「切り出し」は、項目順に処理すればよく、switch() 文で、今何項目目の処理?なんて、自問する必要ないのでは。
 ・構造体のメンバ「まで」ポインタにする必要有るの?。
 ・同じファイルを2度も開く必要有るの?。

   プログラム的に★★★超みっともない、と思うのは年寄りだけかな?。
   少なくとも、事前にエディタで行数を「確認」し、コマンドラインで(ファイル名と共に)それを用いた方がカッコイイ。
++++++++++++++++++++++++++++++++++++++++++++++
「リスト構造」を用いたソースを投稿します。

 ☆関数 Kiridashi() の説明。

 ・対象項目が、いくつ目のカンマの後ろにあるか、を指定(第3引数で)。
 ・頭のカンマの次の文字(◆)から、尻のカンマ(▲)を一旦 '\0' にして、そこまでコピー(第1引数に)。
 ・呼び出し元で定義した実体(第1引数)を、呼び出し元で使用(■)。

 ☆関数 Sort() の説明。

 ・優先順位は、赤点 → クラス → 教科 としています。
 ・1回のソートで済むよう、ソートキーは「赤点 + クラス + 教科」の3桁の数字(●)にしています。
 ・グローバル変数の格納順を変えることで、「クラスや教科」の優先順位を変えることが可能です。

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

#define CLS 5
#define KYK 8

typedef struct _cell{// ファイル仕様確認の必要有り
 int number;
 int num_akaten;
 char class_type[ 8 ];
 char name[ 16 ];
 char subject[ 8 ];
 struct _cell *next;
}MY;

MY sHead;

char cgClass[ CLS ][ 4 ] = { "", "猫", "犬", "B", "A" }; // 右:ソート優先
char cgKyuka[ KYK ][ 8 ] = { "", "算数", "数学", "国語", "英語", "美術", "音楽", "社会" }; // 〃

void Kiridashi( char cWork[], char cBuff[], int iShitei )
{
 int iCnt = 0, i, iHead = 0;

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

  if( '\n' == cBuff[ i ] ) cBuff[ i ] = ','; // レコード終端
  if( '\0' == cBuff[ i ] ) cBuff[ i ] = ','; // レコード/ファイル終端?

  if( ',' == cBuff[ i ] ){

   iCnt++;

   if( iShitei == iCnt ) iHead = i + 1; // ◆

   if( ( 1 + iShitei ) == iCnt ){ // ▲

    cBuff[ i ] = '\0'; // ▲

    strcpy( cWork, &cBuff[ iHead ] );

    cBuff[ i ] = ','; // 復元

    break;
   }
  }
 }
}
void Print_Student( void )
{
 MY *p;

 for( p = sHead.next; NULL != p; p = p->next ){

  printf( "%d,", p->number );
  printf( "%s,", p->class_type );
  printf( "%s,", p->name );
  printf( "%s,", p->subject );
  printf( "%d\n", p->num_akaten );
 }
 printf( "\n" );
}
int GetClassCode( MY *w )
{
 int i;

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

  if( 0 == strcmp( w->class_type, cgClass[ i ] ) ){

   return( i );
  }
 }
 return( 0 );
}
int GetKyoukaCode( MY *w )
{
 int i;

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

  if( 0 == strcmp( w->subject, cgKyuka[ i ] ) ){

   return( i );
  }
 }
 return( 0 );
}
void Sort( void )
{
 MY *p, *q, tmp;
 int iSortKey, iSortMax;
 long lTmp;

 for( p = sHead.next; NULL != p; p = p->next ){

  iSortMax = p->num_akaten * 100 + GetClassCode( p ) * 10 + GetKyoukaCode( p );// ●

  for( q = p->next; NULL != q; q = q->next ){

   iSortKey = q->num_akaten * 100 + GetClassCode( q ) * 10 + GetKyoukaCode( q );// ●

   if( iSortKey < iSortMax ) continue;// 「降順」

   tmp = *p; // 構造体の入れ換え
   *p = *q;
   *q = tmp;

   lTmp = (long)p->next; // 「リスト」の並び?変更不可、戻す
   p->next = q->next;
   q->next = (struct _cell *)lTmp;

   iSortMax = iSortKey;
  }
 }
}
void SetList( char cBuff[] )
{
 MY *p, *q, *new_p;
 char cWork[ 32 ];// 項目中の最も長いものに余裕をつけて

 p = sHead.next;
 q = &sHead;

 while( NULL != p ){ // 2つ目以降

  q = p;
  p = p->next;
 }
 new_p = (MY *)malloc( sizeof( MY ) );// 1人分のメモリ確保

 if( NULL == new_p ){

  fprintf( stderr, "メモリ確保失敗!!\n" );

  exit( 1 );
 }
 Kiridashi( cWork, cBuff, 0 ); new_p->number = atoi( cWork ); // ■
 Kiridashi( cWork, cBuff, 1 ); strcpy( new_p->class_type, cWork );
 Kiridashi( cWork, cBuff, 2 ); strcpy( new_p->name, cWork );
 Kiridashi( cWork, cBuff, 3 ); strcpy( new_p->subject, cWork );
 Kiridashi( cWork, cBuff, 4 ); new_p->num_akaten = atoi( cWork );

 new_p->next = NULL;

 q->next = new_p;
}
void AllDeleteList( void )
{
 MY *p;

 while( NULL != sHead.next ){

  p = sHead.next;

  sHead.next = p->next;

  free( p );
 }
}
int main( void )
{
 FILE *fp;
 char cBuff[ 256 ];

 sHead.next = NULL;

 if( NULL == ( fp = fopen( "test3.csv", "r" ) ) ){

  printf( "ファイルが開けません" );

  return( -1 );
 }
 while( NULL != fgets( cBuff, 256, fp ) ){

  SetList( cBuff );// メモリ確保と「切り出し」格納
 }
 fclose( fp );

 Print_Student();

 Sort();

 Print_Student();

 AllDeleteList(); // メモリ解放:ソース「提出」ならば、これがないだけで「赤点」。

 return( 0 );
}
注:インデントに全角空白を用いています。コピペ後、タブに一括変換して下さい。

この回答への補足

まぁchar*型で返すことにこだわることもないんですけど
一応こちらが私がイメージした方なんですよ。
voidでパラメータで渡す方でできる というのはわかります。
けどもしも私がイメージした方でできなければ混乱してしまう為
こちらでもできることを確認したかったのです。

構造体のポインタの方はメモリの仕組みを理解する為に配列で
定義していたメンバをポインタに変えました。

同じファイルを2回開くのは最初の構造体のメモリをとるときに余分なメモリをとらないようにする為です。何行あるかなんてわかりませんし。
無くなったら補充ってやり方もいいんですけどこちらのほうが
しっくりきたので

補足日時:2009/07/13 00:28
    • good
    • 0

>やりたいソートの条件は赤点の多い順が優先でクラスと好きな教科が


>同じものをソートしたい。優先順位は赤点→クラスとが一致→教科が一致なので

1.項目名は先頭から順番に何になりますか?
出席番号、クラス、氏名、好きな教科、赤点の数
でよいですか?
2.ソートの順番は赤点の数(降順)->クラス(昇順)->好きな教科(昇順)で良いでしょうか
3.qsort(ソートを行う標準関数)をこのような使用すると非常に簡単にできますが、ソートを行うのは、自前で作るのが条件ですか?
qsortを使用してよいなら、回答できます。

この回答への補足

1,項目名の順は不変です。行単位で全て入れ替え ですので。
2,で、大丈夫です。
3,そういうのはないです。qsortを使ってもらっても大丈夫です。

※都合により1日弱ネット見れませんでした。返答遅れました

補足日時:2009/07/12 11:39
    • good
    • 0

最初に赤点数の順にソートしてから、さらにクラスでソートして…というのは無駄です。

この課題の場合は、一度のソートで済むはずです。

2つのmy構造体があったとき、その大小(ソートしたときにどちらが先になるか)は一意に決まるのですよね? であれば、2つのmy構造体の大小を判定する関数を作ってください。strcmp()のように、大小に応じて負数, 0, 正数のいずれかを返すようにしておけばいいでしょう。
その大小判定関数を仮にmycmpという名前にしたとすると、下記のような感じになると思います。

int mycmp(my *x, my *y)
{
 if (xの赤点数 < yの赤点数) {
  return -1; // xのほうが小さい
 } else if (xの赤点数 > yの赤点数) {
  return 1; // xのほうが大きい
 } else { // 赤点数は同じ
  if (xのクラス < yのクラス) {
   return -1; // xのほうが小さい
  } else if (xのクラス > yのクラス) {
   return 1; // xのほうが大きい
  } else { // クラスも同じ
   // 以下、優先度順に他の要素で比較して-1か1を返す
   // 全項目が一致した場合は 0 を返す
  }
 }
}

あとは、mycmpの結果を使ってソートするだけです。
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!