固定長データ
Posted feedbacks - Nested
Flatten Hidden投稿速さ重視で
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | using System;
using System.Collections.Generic;
using System.IO;
struct MenuList
{
public string Day;
public string Firstbread;
public string Lunch;
public string Dinner;
}
struct Record
{
public string FirstName;
public string SecondName;
public string Sex;
public string Age;
public string Year;
public string Month;
public List<MenuList> Menu;
}
class Program
{
static void Main(string[] args)
{
List<Record> meibo = new List<Record>();
//テストデータ作成
using (StreamWriter sw = new StreamWriter(@"C:\testdata.txt"))
{
for (int i = 0; i < 500; i++)
{
sw.Write("{0, -12}", "Yamada");
sw.Write("{0, -12}", "Tarou");
sw.Write("{0, 1}", "M");
sw.Write("{0, 3}", "26");
sw.Write("{0, 4}", "2008");
sw.Write("{0, 2}", "03");
for (int j = 1; j <= 31; j++)
{
sw.Write("{0:00}", j);
sw.Write("{0,-500}", "Yamada");
sw.Write("{0,-500}", "Yamada");
sw.Write("{0,-500}", "Yamada");
}
}
sw.Dispose();
}
using (StreamReader sr = new StreamReader(@"C:\testdata.txt"))
{
Record temp;
while (!sr.EndOfStream)
{
temp = new Record();
char[] buffer;
//性
buffer = new char[12];
sr.Read(buffer, 0, buffer.Length);
temp.FirstName = string.Format("{0:s12}", new string(buffer));
//名
buffer = new char[12];
sr.Read(buffer, 0, buffer.Length);
temp.SecondName = string.Format("{0:s12}", new string(buffer));
//性別
buffer = new char[1];
sr.Read(buffer, 0, buffer.Length);
temp.Sex = string.Format("{0:s1}", new string(buffer));
//年齢
buffer = new char[3];
sr.Read(buffer, 0, buffer.Length);
temp.Age = string.Format("{0,3:s3}", new string(buffer));
//年
buffer = new char[4];
sr.Read(buffer, 0, buffer.Length);
temp.Year = string.Format("{0:s4}", new string(buffer));
//月
buffer = new char[2];
sr.Read(buffer, 0, buffer.Length);
temp.Month = string.Format("{0:s2}", new string(buffer));
//ループ
temp.Menu = new List<MenuList>();
for (int i = 1; i <= 31; i++)
{
MenuList temp2 = new MenuList();
//日
buffer = new char[2];
sr.Read(buffer, 0, buffer.Length);
temp2.Day = string.Format("{0:s2}", new string(buffer));
//朝食
buffer = new char[500];
sr.Read(buffer, 0, buffer.Length);
temp2.Firstbread = string.Format("{0:s500}", new string(buffer));
//昼食
buffer = new char[500];
sr.Read(buffer, 0, buffer.Length);
temp2.Lunch = string.Format("{0:s500}", new string(buffer));
//夕食
buffer = new char[500];
sr.Read(buffer, 0, buffer.Length);
temp2.Dinner = string.Format("{0:s500}", new string(buffer));
temp.Menu.Add(temp2);
}
meibo.Add(temp);
}
foreach (Record result in meibo)
{
Console.WriteLine("FirstName : {0}", result.FirstName);
Console.WriteLine("SecondName : {0}", result.SecondName);
Console.WriteLine("Sex : {0}", result.Sex);
Console.WriteLine("Age : {0}", result.Age);
Console.WriteLine("Year : {0}", result.Year);
Console.WriteLine("Month : {0}", result.Month);
foreach (MenuList result2 in result.Menu)
{
Console.WriteLine("Day : {0}", result2.Day);
Console.WriteLine("Firstbread : {0}", result2.Firstbread);
Console.WriteLine("Lunch : {0}", result2.Lunch);
Console.WriteLine("Dinner : {0}", result2.Dinner);
}
}
sr.Dispose();
}
}
}
|
Squeak Smalltalk で。
1 2 3 4 5 6 7 8 9 10 11 12 13 | | stream data |
stream := FileStream fileNamed: 'data.txt'.
data := OrderedCollection new.
500 timesRepeat: [
| record |
record := OrderedCollection new.
#(12 12 1 3 4 2) do: [:size | record add: (stream next: size) withBlanksTrimmed].
1 to: 31 do: [:date |
record add: (stream next: 2).
3 timesRepeat: [record add: (stream next: 500) withBlanksTrimmed]].
data add: record].
stream close.
data inspect
|
しかしC系の人間にしかわからないバウンダリの痛み。
このデータ仕様わざとやってるでしょ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | #include <stdio.h>
#include <stdlib.h>
typedef struct tagDailyMenu {
char date[2];
char breakfast[500];
char lunch[500];
char dinnar[500];
} DailyMenu;
typedef struct tagPerson {
char family[12];
char name[12];
char sex;
char age[3];
char year[4];
char month[3];
} Person;
typedef struct tagRecord {
Person person;
DailyMenu days[31];
} Record;
int main(void)
{
FILE *fp;
char *filename = "data.dat";
int readlen = 0;
Record rec;
fp = fopen(filename, "r");
if (fp == NULL) {
printf("ファイルオープン失敗\n");
return 1;
}
memset(&rec, 0x00, sizeof(Record));
while ((readlen = fread(&rec.person, 34, 1, fp)) == 1) {
int readlen2 = 0;
int day = 0;
for (day = 0; day < 31; day++) {
int readlen2 = fread(&rec.days[day], 1502, 1, fp);
if (readlen2 < 1) {
printf("ファイルがこわれています。\n");
break;
}
}
if (day < 31) {
break;
}
printf("name: [%.12s %.12s] %.4s/%.2s [%.2s/%.10s/%.10s/%.10s]\n",
rec.person.family, rec.person.name,
rec.person.year, rec.person.month,
rec.days[0].date, rec.days[30].breakfast,
rec.days[30].lunch, rec.days[30].dinnar
);
memset(&rec, 0x00, sizeof(Record));
}
fclose(fp);
return 0;
}
|
バウンダリとは、実行効率のためCのコンパイラが自動的に構造体のサイズを4バイトの整数倍にしてしまうことです。
日毎の部分の本当のサイズは1502バイトですが、構造体のサイズは1504バイトになってしまい、シンプルに書くことができませんでした。
本当はfreadのサイズ指定で数値ではなくsizeofを使いたかった。
なおRecordとPersonを分離しているのは、最初、日ごとのデータが31個固定ではなく可変数だと思い込んでいたためです。
すみません。バウンダリ関係なかった! Personのmonthの桁数が3になっているのに気づかず混乱してしまった。 全部charだとバウンダリ関係ないんだっけ。 落ち着け俺。
今回の問題とは関係ないですが、一応知ってるとは思いますが、知らない人のために。 #pragmaなのでコンパイラ依存ですが、4byte境界を変更することができます。 次のような構造体をsizeofすると、5になります。 また、pragma packを取り除くと、8になります。 #BMPのローダーを書くときにお世話になったなぁ。 #頭の'BM'って文字列のせいで、全部が2byteずつずれて・・・。
see: Visual C++でアラインメントを扱う3つの機能、#pragma pack, __declspec(align(#)), __alignof演算子についてのメモ
1 2 3 4 5 6 7 | #pragma pack(push,1)
typedef struct s
{
char ch1;
int i1;
} s;
#pragma pack(pop)
|
最近はあまり意識しなくなってしまいましたが 固定長のデータを構造体で読み取る場合には 共用体を併用したほうが安全です。 そのままでは、ビックエンディアンのときにおかしくなってしまうと思うので。。。 無名構造体とか使用するともう少し楽にアクセスできた気がするけど 普段しようしないので忘れてしまいました^^;;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | union {
char stream [35];
struct tagDATA
{
char family[12];
char name[12];
char sex;
char age[3];
char year[4];
char month[3];
} data;
} head;
read_size = read(fd, head.stream, sizeof(head.stream));
|
やっつけですがunpackを使った例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #!/usr/bin/perl
use strict;
use warnings;
my $dat;
if (@ARGV) {
open $dat, shift or die;
} else {
$dat = \*STDIN;
}
my $bytes;
my @records;
for (1..500) {
read $dat, $bytes, 12 + 12 + 1 + 3 + 4 + 2;
my ($lname, $fname, $sex, $age, $year, $month) = unpack "A12A12A1A3A4A2", $bytes;
$lname =~ s/\s*$//;
$fname =~ s/\s*$//;
my %record = (last_name => $lname,
first_name => $fname,
sex => $sex,
age => $age + 0,
year => $year + 0,
month => $month + 0,
menues => []);
for (1..31) {
read $dat, $bytes, 2 + 500 + 500 + 500;
my ($day, $breakfast, $lunch, $dinner) = unpack "A2A500A500A500", $bytes;
$breakfast =~ s/^\s*|\s*$//;
$lunch =~ s/^\s*|\s*$//;
$dinner =~ s/^\s*|\s*$//;
push @{$record{menues}}, {day => $day + 0,
breakfast => $breakfast,
lunch => $lunch,
dinner => $dinner};
}
push @records, \%record;
}
#テスト出力
use Data::Dumper;
print Dumper @records;
|
ついでにテストデータ生成スクリプトを貼っておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/usr/bin/perl
use strict;
use warnings;
my @lnames = map { sprintf "%-12s", $_ } qw/Foo Bar Baz Jim Bob Beth Cathy/;
my @fnames =
map { sprintf "%-12s", $_ } qw/Hoge Fuga Piyo Jacson Page McDonald Hamond/;
my @sexes = qw/F M U/;
my @ages = map { sprintf "%03d", $_ } 0 .. 999;
my @dishes =
map { sprintf "%-500s", $_ }
qw/BaconEgg Coffee HamEgg BoiledEgg EggInBasket Udon Steak
SunnySideUp Onigiri Ramen/;
sub randomint { int rand shift }
for ( 1 .. 500 ) {
print $lnames[ randomint( scalar @fnames ) ],
$fnames[ randomint( scalar @lnames ) ],
$sexes[ randomint( scalar @sexes ) ], $ages[ randomint( scalar @ages ) ],
"200803";
for my $day ( 1 .. 31 ) {
$day = sprintf "%02d", $day;
print $day, $dishes[ randomint( scalar @dishes ) ],
$dishes[ randomint( scalar @dishes ) ],
$dishes[ randomint( scalar @dishes ) ];
}
}
|
とりあえず、こんな感じで。 ちゃんとやるなら、ReadとWriteの処理はRecordとセットのクラスを用意するかも。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Sample170 {
private static final String encoding = "MS932";
private static final String FILE_NAME = "record.dat";
public static void main(String[] args) {
try {
createTestData(FILE_NAME);
List<Record> records = readData(FILE_NAME);
System.out.println(records.size());
} catch (IOException e) {
e.printStackTrace();
}
}
private static List<Record> readData(String fileName) throws IOException {
Reader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), encoding));
List<Record> result = new ArrayList<Record>();
char[] smallBuf = new char[12];
char[] menuBuf = new char[500];
while (true) {
Record record = new Record();
if (reader.read(smallBuf) < 0) break;
record.familyName = String.valueOf(smallBuf).trim();
if (reader.read(smallBuf) < 0) break;
record.firstName = String.valueOf(smallBuf).trim();
if (reader.read(smallBuf, 0 , 1) < 0) break;
record.sex = Record.Sex.valueOf(String.valueOf(smallBuf[0]));
if (reader.read(smallBuf, 0 , 3) < 0) break;
record.age = Integer.parseInt(String.valueOf(smallBuf, 0, 3).trim());
if (reader.read(smallBuf, 0 , 4) < 0) break;
int year = Integer.parseInt(String.valueOf(smallBuf, 0, 4).trim());
if (reader.read(smallBuf, 0 , 2) < 0) break;
int month = Integer.parseInt(String.valueOf(smallBuf, 0, 2).trim());
for (int index = 1; index <= 31; index++) {
MenuData menu = new MenuData();
menu.year = year;
menu.month = month;
if (reader.read(smallBuf, 0 , 2) < 0) break;
menu.day = Integer.parseInt(String.valueOf(smallBuf, 0, 2).trim());
if (reader.read(menuBuf) < 0) break;
menu.breakfast = String.valueOf(menuBuf).trim();
if (reader.read(menuBuf) < 0) break;
menu.lunch = String.valueOf(menuBuf).trim();
if (reader.read(menuBuf) < 0) break;
menu.dinner = String.valueOf(menuBuf).trim();
}
result.add(record);
}
return result;
} finally {
if (reader != null) {
reader.close();
}
}
}
private static void createTestData(String fileName) throws IOException {
Writer writer = null;
try {
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), encoding));
Random random = new Random();
Record.Sex[] sexValues = Record.Sex.values();
for (int index = 0; index < 500; index++) {
writer.write(String.format("%-12s", "FamilyName"));
writer.write(String.format("%-12s", "FirstName"));
writer.write(sexValues[random.nextInt(sexValues.length)].toString());
writer.write(String.format("%3d", random.nextInt(120)));
writer.write("2008");
writer.write("03");
for (int day = 1; day <= 31; day++) {
writer.write(String.format("%02d", day));
writer.write(String.format("%-500s", "breakfast menu."));
writer.write(String.format("%-500s", "lunch menu."));
writer.write(String.format("%-500s", "dinner menu."));
}
}
} finally {
if (writer != null) {
writer.close();
}
}
}
}
class Record {
public enum Sex {
F, M, U,
}
public String familyName;
public String firstName;
public Sex sex;
public int age;
public final List<MenuData> menuList = new ArrayList<MenuData>();
}
class MenuData {
public int year;
public int month;
public int day;
public String breakfast;
public String lunch;
public String dinner;
}
|
エラー処理をはしょればこんなかんじ? あと、31日固定として。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <stdio.h>
#include <stdlib.h>
typedef struct menu_ {
char date[2];
char breakfast[500];
char lunch[500];
char supper[500];
} menu_t;
typedef struct fixed_ {
char last_name[12];
char first_name[12];
char gender;
char age[3];
char year[4];
char month[3];
menu_t menus[31];
} fixed_t;
int main(int argc, char **argv) {
FILE *fp = fopen(argv[1], "r");
fixed_t fixed[500];
int i;
for (i=0; i<500; i++) {
fread(&fixed[i], sizeof(fixed_t), 1, fp);
}
return 0;
}
|
匿名で書いてしまいました。mootohです。
構造体に属する変数が詰まっている保証はないので、例えば4バイトアラインを基本とする処理系だったら、monthとmenusの間に1バイト使われない領域が入ってしまい、入力データのサイズと構造体のsizeofが一致しないかと。 もちろんこのコンパイラにこのオプション付ければ動く、というのはあるかもしれませんが、それはそれでタグを付けていただきたいところです。
#6098書いた者ですが、これ嘘?charのみを使ってるとアライメントされない?
自分の理解不足に嘆いて、サンプルを作ってみました。
<結果>*VC2005で確認
sizeof A = 8
sizeof B = 5
sizeof C = 16
sizeof D = 16
sizeof E = 10
sizeof F = 24
いまいち釈然としなかったですが、参考ページの「配列にしたときにバイト境界をまたがないように、最小限のパディングが入れられる」という説明を読んでやっと納得できた気がします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include <stdio.h>
struct A {
char c[2]; /* |c c| | */
int n; /* |n n n n| */
};
struct B {
char c[2]; /* |c c|d d| */
char d[3]; /* |d| | */
};
struct C { /* AとBの後ろに詰め物 */
struct A a;
struct B b;
};
struct D { /* AとBの後ろに詰め物 */
struct B b;
struct A a;
};
struct E { /* こいつは10バイトにみっちり詰まる */
struct B b1;
struct B b2;
};
struct F {
char c[3]; /* |c c c| | */
short s; /* |s s| | */
int n; /* |n n n n| */
short t; /* |t t|d d| */
char d[3]; /* |d| | */
char *p; /* |p p p p| */
};
int main(void) {
printf("sizeof A = %d\n", sizeof(struct A));
printf("sizeof B = %d\n", sizeof(struct B));
printf("sizeof C = %d\n", sizeof(struct C));
printf("sizeof D = %d\n", sizeof(struct D));
printf("sizeof E = %d\n", sizeof(struct E));
printf("sizeof F = %d\n", sizeof(struct F));
return 0;
}
|
わざわざありがとうございます。理解が曖昧なまま投稿してはいけませんねorz
またもや参考サイトのリンクに失敗。。。 もしかしてページタイトル必須なのか? つまらないことでレスを伸ばしてすみません。。。
see: 構造体のサイズとアライメント
COBOLで書くべきお題がついにきましたね(笑
読み込み処理はfromStringです。テストデータ作成用にtoStringを作成しています。
matchというのが予約語なので、「match」というメソッドが素直に呼び出せないという・・・
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import java.io._
import java.util.Scanner
import java.util.regex._
import scala.io._
object Meal {
def fromString(text:String) = {
val scanner = new Scanner(text)
scanner.findInLine("^(.{12})(.{12})(.{1})(.{3})(.{4})(.{2})
(.{2})(.{500})(.{500})(.{500})$")
val data = classOf[Scanner].getMethod("match",null).
invoke(scanner,null). asInstanceOf[MatchResult].group _:int=>String
new Meal( data(1).trim, data(2).trim, data(3)(0), data(4).trim.toInt,
data(5).trim.toInt, data(6).trim.toInt, data(7).trim.toInt,
data(8), data(9), data(10))
}
}
class Meal
(var lastName:String, var firstName:String, var sex:char,
var age:int, var year:int, var month:int, var day:int,
var breakfast:String, var lunch:String, var dinner:String) {
def format(template:String, arg:Any*) = {
String.format(template, arg.map(_.asInstanceOf[AnyRef]).toArray)
}
override def toString = {
format("%-12s%-12s%c%3d%04d%02d%02d%-500s%-500s%-500s\n",
lastName, firstName, sex, age, year, month, day,
breakfast, lunch, dinner)
}
}
Source.fromFile("test.txt").getLines.take(500) foreach Meal.fromString
|
メモリ上の構造≠ファイル上の構造の前提で書きました。 予想以上にstreamの扱いに苦戦。streamと固定長データは相性が良くない…というより、わたしがstreamを使い切れていないというのが正解かも。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | #include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <iterator>
struct Menu
{
std::string date;
std::string breakfast;
std::string lunch;
std::string dinner;
};
typedef std::vector<Menu> Menus;
struct Record
{
public:
std::string lastName;
std::string firstName;
std::string sex;
std::string age;
std::string year;
std::string month;
Menus menus;
};
std::istream& operator >> (std::istream& in, Menu& menu)
{
char s[500];
in.read(s, 2); menu.date = std::string(s, 2);
in.read(s, 500); menu.breakfast = std::string(s, 500);
in.read(s, 500); menu.lunch = std::string(s, 500);
in.read(s, 500); menu.dinner = std::string(s, 500);
return in;
}
std::istream& operator >> (std::istream& in, Record& record)
{
char s[500];
in.read(s, 12); record.lastName = std::string(s, 12);
in.read(s, 12); record.firstName = std::string(s, 12);
in.read(s, 1); record.sex = std::string(s, 1);
in.read(s, 3); record.age = std::string(s, 3 |





Mymelo #6060() Rating5/7=0.71
固定長のデータが記載されたファイルを読み込むプログラムを作成してください。読み込んだデータは、複数の値を格納できるデータ型に格納してください。
ファイルには、すべて ascii 文字で以下のデータが格納されています。デリミタはなく、固定長で格納されています。レコードとレコードのあいだも改行はありません。
以上の形式のデータ500人分を読みこんで、データを複数の値を格納できるデータ型に格納してください。データに大して何か処理を行う必要はなく、すぐに破棄してかまいません。
この問題は、このようなファイルをどのように扱うかを知りたくて作成しました。
[ reply ]