🧩今日の学び
・件数を数える処理で不正レコードを除外しても、元のファイルからそのレコードが消えるわけではない。
・SORTによって不正レコードの位置が移動すると、値を取り出す順番までずれるため、2回目の読み込みにも同じ有効性判定が必要になる。
・複数の処理で同じデータを扱う場合は、各処理の入口で同じ基準を使い、有効なレコードだけを数えて利用することが重要である。
係長)で、気づいただろ?
なるお)気づくもんかい!
係)さすがにイラッとする時間だぞ。
な)あい…
係)手を使って、中央値出してみろ。
な)って、出てますよ?
係)出せって言ってんだよ。
な)あい…
手計算すると中央値は1650になる
データがこうで…
2025-12-31hoshiimo 02500 2026-01-01gyudon2 00900 2026-01-01curry 01500 2026-01-02gyudon2 00900 2026-01-03hoshiimo 02500 2026-01-04udontenpura 01800
ソートするとこうでしょ?
00900 00900 01500 01800 02500 02500
…てか、干し芋高すぎじゃね?
んで偶数だから、3個目と4つ目の平均だと。
(1500 + 1800)/ 2 = (答え)
…がふッ!
係)どうだ、01200だったか?
な)…。
そうなる可能性も捨てきれないというか。この世の物理法則を考えると、極小な確率で01200になるとかならないとか。
係)ならないだろ!
な)あい…
係)最初に気づけ!
なんで、そうなったかわかるか?
昨日も言ったとおり、改行がうまくいってないから7行目が出現してしまったが故に、計算が狂ったんだよ。
な)ふむむむ…
COUNTは6なのに中央値がまだ合わない
な)え、でも、カウントを6に直しましたよね?それに中央値1200ですよ?
係)ん?
な)1200だから、 900と1500の平均じゃないす?
ってことは、2つ目と3つ目の金額の平均になってないです?
ってことは、7行目じゃなくって、1行目になにか出たんじゃないですか?
だって、3つ目と4つ目の金額を拾ってくるってコードなのに、2つ目と3つ目の金額を拾ってきてるわけで、そうなると、1行目になにか出たってことじゃないんです?
係)あ…、う、うーん…
な)あは!係長もわからないっすか!
係)喜ぶな!くそ!
うーん…どういうことだこれ…
な)オラ、ワクワクすっぞ!
係)わくわくすんな!
ちょっとアイスでも食って待ってろ。
な)おー初めての優しさ!
係)なんでだよ!
いいから話しかけるな。
な)ほーい
係)(ブツブツ)
・・・
はぁ…。
な)わからなかったすか?
係)なんでわからない方になるんだよ!
といっても、うーん、お前に細かく説明してもな…
な)信頼されてる!?
係)今のどこに信頼の要素があるんだよ…
原因はGnuCOBOLのCOB_LS_SPLITだった
簡単に言うと、GnuCOBOLのバージョン違いによる結果の違いだな。
な)ほ?
係)お前がアイスを2つ食ってる間に、色々やってみたんだが…
な)見てた!?
係)GnuCOBOLの現バージョンの仕様、COB_LS_SPLITというランタイム…機能によるものだな。
これは
「レコード長より長い行を読んだらどうするか」を決めるもので、
・機能をオンだと、余分なところは次の行に分割
・機能をオフだと、余分なところは切り捨てる
という違いになる。
な)別に問題ないんじゃ?
係)そうだ。本来ならな。
ただ、今回のGnuCOBOL 3.2環境で確認したところ、ファイル終端の状態が重なって、末尾に余計な1バイトが現れたようなんだ。
(→レコードの最後に01800に0が追加されて、018000となった)
その結果、1レコードとして定義した長さを、1バイトだけ超えてしまった
(→その018000の最後0のが行送りされた)
COB_LS_SPLITは、レコード長を超えた部分を次のレコードへ分割するか、その場で切り捨てるかを決める設定だからな、設定していないと分割となるので、余分な行をつくりだしてしまったということだろう。
な)えー!それ大問題じゃ!?
係)ま、そういうこともあるだろうな。
な)「ぐぬ〜」には優しい!?
係)メインフレームの厳格さを求められているわけでもないしな。
こここまで無料で使わせてもらってんだ、そのぐらいは見なかったことにしてもいいいいだろ。
な)ほむぅ…ほんと、内弁慶っすね…
係)お前にだけだ!
ったく、このまま進めるぞ。
前回のコードがこれで終わってるな。
IDENTIFICATION DIVISION.
PROGRAM-ID. NARUO.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT INPUT-FILE
ASSIGN TO "naruo2.txt"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT SORT-FILE
ASSIGN TO "sortwork.tmp".
SELECT OUTPUT-FILE
ASSIGN TO "naruo2_sorted.txt"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD INPUT-FILE.
01 INPUT-RECORD.
05 INPUT-SALE-DATE PIC X(10).
05 INPUT-ITEM-NAME PIC X(20).
05 INPUT-AMOUNT PIC 9(5).
SD SORT-FILE.
01 SORT-RECORD.
05 SORT-SALE-DATE PIC X(10).
05 SORT-ITEM-NAME PIC X(20).
05 SORT-AMOUNT PIC 9(5).
FD OUTPUT-FILE.
01 OUTPUT-RECORD.
05 OUTPUT-SALE-DATE PIC X(10).
05 OUTPUT-ITEM-NAME PIC X(20).
05 OUTPUT-AMOUNT PIC 9(5).
WORKING-STORAGE SECTION.
01 EOF-FLAG PIC X VALUE "N".
01 COUNT-LOOPING PIC 9(2) VALUE 0.
01 READ-COUNT PIC 9(2) VALUE 0.
01 LOW-POS PIC 9(2) VALUE 0.
01 HIGH-POS PIC 9(2) VALUE 0.
01 LOW-VAL PIC 9(5) VALUE 0.
01 HIGH-VAL PIC 9(5) VALUE 0.
01 MEDIAN-VAL PIC 9(5)V9 VALUE 0.
PROCEDURE DIVISION.
SORT SORT-FILE
ON ASCENDING KEY SORT-AMOUNT
USING INPUT-FILE
GIVING OUTPUT-FILE.
OPEN INPUT OUTPUT-FILE.
PERFORM UNTIL EOF-FLAG = "Y"
READ OUTPUT-FILE
AT END
MOVE "Y" TO EOF-FLAG
NOT AT END
IF OUTPUT-AMOUNT IS NUMERIC
AND OUTPUT-AMOUNT > 0
ADD 1 TO COUNT-LOOPING
END-IF
END-READ
END-PERFORM.
CLOSE OUTPUT-FILE.
COMPUTE LOW-POS = COUNT-LOOPING / 2.
COMPUTE HIGH-POS = LOW-POS + 1.
OPEN INPUT OUTPUT-FILE.
MOVE "N" TO EOF-FLAG.
PERFORM UNTIL EOF-FLAG = "Y"
READ OUTPUT-FILE
AT END
MOVE "Y" TO EOF-FLAG
NOT AT END
ADD 1 TO READ-COUNT
IF READ-COUNT = LOW-POS
MOVE OUTPUT-AMOUNT TO LOW-VAL
END-IF
IF READ-COUNT = HIGH-POS
MOVE OUTPUT-AMOUNT TO HIGH-VAL
END-IF
END-READ
END-PERFORM.
CLOSE OUTPUT-FILE.
COMPUTE MEDIAN-VAL = (LOW-VAL + HIGH-VAL) / 2.
DISPLAY "COUNT=" COUNT-LOOPING
" LOW-POS=" LOW-POS " HIGH-POS=" HIGH-POS.
DISPLAY "LOW-VAL=" LOW-VAL " HIGH-VAL=" HIGH-VAL.
DISPLAY "MEDIAN IS " MEDIAN-VAL.
STOP RUN.
1回目で除外してもゴミは消えていない
一回目のループでIS NUMERICを使って選別しただろ?
それは2回目のループでも必要だということになる。
なんでだと思う?
な)… ゴミがあるから?
係)そういうことだな。
1回目で弾いたのは数えるのをやめただけだ。ファイルからゴミが消えたわけじゃない。
2回目も同じファイルを読むんだぞ。同じゴミが、同じ場所に、同じようにいる。
だから、2回目のループでも必要となるんだ。
ゴミがなかったとしても、大勢に影響がなく、ゴミがあったら止められるわけだからな。
な)ほへー、係長って気遣いできる人だったんすね。
ちょっと見直しました。
係)どういう意味だよ!
2回目のループにも同じ選別を入れる
係)いいから、今の話をもとに直してみろ!
な)ほーい。
考え方は1回目と変わんないから、単純に追加ってことでいいわよね
OPEN INPUT OUTPUT-FILE.
MOVE "N" TO EOF-FLAG.
PERFORM UNTIL EOF-FLAG = "Y"
READ OUTPUT-FILE
AT END
MOVE "Y" TO EOF-FLAG
NOT AT END
IF OUTPUT-AMOUNT IS NUMERIC ←ここ
AND OUTPUT-AMOUNT > 0 ←ここ
ADD 1 TO READ-COUNT
IF READ-COUNT = LOW-POS
MOVE OUTPUT-AMOUNT TO LOW-VAL
END-IF
IF READ-COUNT = HIGH-POS
MOVE OUTPUT-AMOUNT TO HIGH-VAL
END-IF
END-IF
END-READ
END-PERFORM.
係)実行してみろ。
な)ほいさー!
IDENTIFICATION DIVISION.
PROGRAM-ID. NARUO.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT INPUT-FILE
ASSIGN TO "naruo2.txt"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT SORT-FILE
ASSIGN TO "sortwork.tmp".
SELECT OUTPUT-FILE
ASSIGN TO "naruo2_sorted.txt"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD INPUT-FILE.
01 INPUT-RECORD.
05 INPUT-SALE-DATE PIC X(10).
05 INPUT-ITEM-NAME PIC X(20).
05 INPUT-AMOUNT PIC 9(5).
SD SORT-FILE.
01 SORT-RECORD.
05 SORT-SALE-DATE PIC X(10).
05 SORT-ITEM-NAME PIC X(20).
05 SORT-AMOUNT PIC 9(5).
FD OUTPUT-FILE.
01 OUTPUT-RECORD.
05 OUTPUT-SALE-DATE PIC X(10).
05 OUTPUT-ITEM-NAME PIC X(20).
05 OUTPUT-AMOUNT PIC 9(5).
WORKING-STORAGE SECTION.
01 EOF-FLAG PIC X VALUE "N".
01 COUNT-LOOPING PIC 9(2) VALUE 0.
01 READ-COUNT PIC 9(2) VALUE 0.
01 LOW-POS PIC 9(2) VALUE 0.
01 HIGH-POS PIC 9(2) VALUE 0.
01 LOW-VAL PIC 9(5) VALUE 0.
01 HIGH-VAL PIC 9(5) VALUE 0.
01 MEDIAN-VAL PIC 9(5)V9 VALUE 0.
PROCEDURE DIVISION.
SORT SORT-FILE
ON ASCENDING KEY SORT-AMOUNT
USING INPUT-FILE
GIVING OUTPUT-FILE.
OPEN INPUT OUTPUT-FILE.
PERFORM UNTIL EOF-FLAG = "Y"
READ OUTPUT-FILE
AT END
MOVE "Y" TO EOF-FLAG
NOT AT END
IF OUTPUT-AMOUNT IS NUMERIC
AND OUTPUT-AMOUNT > 0
ADD 1 TO COUNT-LOOPING
END-IF
END-READ
END-PERFORM.
CLOSE OUTPUT-FILE.
COMPUTE LOW-POS = COUNT-LOOPING / 2.
COMPUTE HIGH-POS = LOW-POS + 1.
OPEN INPUT OUTPUT-FILE.
MOVE "N" TO EOF-FLAG.
PERFORM UNTIL EOF-FLAG = "Y"
READ OUTPUT-FILE
AT END
MOVE "Y" TO EOF-FLAG
NOT AT END
IF OUTPUT-AMOUNT IS NUMERIC
AND OUTPUT-AMOUNT > 0
ADD 1 TO READ-COUNT
IF READ-COUNT = LOW-POS
MOVE OUTPUT-AMOUNT TO LOW-VAL
END-IF
IF READ-COUNT = HIGH-POS
MOVE OUTPUT-AMOUNT TO HIGH-VAL
END-IF
END-IF
END-READ
END-PERFORM.
CLOSE OUTPUT-FILE.
COMPUTE MEDIAN-VAL = (LOW-VAL + HIGH-VAL) / 2.
DISPLAY "COUNT=" COUNT-LOOPING
" LOW-POS=" LOW-POS " HIGH-POS=" HIGH-POS.
DISPLAY "LOW-VAL=" LOW-VAL " HIGH-VAL=" HIGH-VAL.
DISPLAY "MEDIAN IS " MEDIAN-VAL.
STOP RUN.
COUNT=06 LOW-POS=03 HIGH-POS=04 LOW-VAL=01500 HIGH-VAL=01800 MEDIAN IS 01650.0
OH!メルシー!
係)だから、どういうことだよ…
最後に生まれたゴミはソートされて先頭へ移動した
な)でも、なんで1行目に変なのが出現したんすか?
係長は7行目に追加したとか言ってませんでした?
係)ぐっ!?
な)係長の見込み違い?やっちゃいました?メルシーの呪い?
係)いいか、7件目に出たのは合ってる。だからCOUNTが7になったんだよ!
しかしな、並べ替えたら先頭に来るってとこまで見てなかった。生まれたのは最後、座ったのは先頭ってことだ。
お前のデブデータに呆れて見落としただけだ!
な)ほーほー
係)ま、まぁ、先にソートファイルを確認していればわかったとはいえるが…(ボソ)
おむすび
な)え、なにか言いました?
係)なにも言ってない!ほら帰るぞ!片付けろ!
な)やったー、監禁解除!
係)人聞きの悪い…
な)鳥貴族行きます?
係)…ま、いいか。
な)やた!
(当然続く)
係長のワンポイント
不正データは「一度無視すれば消える」ものじゃない。
本当は、同じファイルを読むたびに、同じ条件で選別し直す必要がある。
よくある誤解は、1回目のループで除外したから後の処理も安全だと思うことだが、ゴミは同じ場所に残っている。
しかもSORTすると、その不正レコードが別の位置へ移動し、中央の値までずらすことがある。
だから件数を数える処理と値を取り出す処理の両方に、同じ有効性判定を入れる。
COBOLは一度の判定で安心する言語ではなく、処理の入口ごとにデータを確かめる言語だ。


コメント