M.Hiroi's Home Page

F# Programming

お気楽 F# プログラミング超入門

[ PrevPage | F# | NextPage ]

ファイル入出力

今回は F# のファイル入出力について説明します。プログラムの典型的な動作は、外部から入力されたデータを処理し、その結果を外部へ出力することです。外部とのインターフェースはいろいろありますが、基本となるのがファイル入出力です。近代的なプログラミング言語では、ファイルとの入出力を行うためのデータ型が用意されています。.NET では「ストリーム (stream)」を使います。

stream は「流れ」や「小川」という意味ですが、プログラミングの世界では、ファイルとプログラムの間でやりとりされるデータの流れ、という意味で使われているようです。ストリームはファイルと一対一に対応していて、ファイルからデータを入力する場合は、ストリームを経由してデータが渡されます。逆に、ファイルへデータを出力するときも、ストリームを経由して行われます。.NET にはストリームを表すクラスが複数用意されていて、それらのクラスは F# から利用することができます。

●標準入出力

通常のファイルは、ストリームを生成しないとアクセスすることはできません。ただし、標準入出力は F# の起動時にストリームが自動的に生成されるので、簡単に利用することができます。一般に、キーボードからの入力を「標準入力」、画面への出力を「標準出力」といいます。標準入出力に対応するチャネルは大域変数に格納されています。下表に変数名を示します。

表 : 標準入出力
変数名ファイル
stdin 標準入力
stdout 標準出力
stderr 標準エラー出力

REPL で stdin, stdout, stderr の値を表示させてみましょう。

> stdin;;
val it: System.IO.TextReader =
  System.IO.SyncTextReader {KeyAvailable = false;}

> stdout;;
val it: System.IO.TextWriter =
  System.IO.TextWriter+SyncTextWriter {Encoding = System.Text.ConsoleEncoding;
                                       FormatProvider = ja-JP;
                                       NewLine = "
";}

> stderr;;
val it: System.IO.TextWriter =
  System.IO.TextWriter+SyncTextWriter {Encoding = System.Text.ConsoleEncoding;
                                       FormatProvider = ja-JP;
                                       NewLine = "
";}

TextReader と TextWriter はモジュール System.IO に定義されている抽象クラスです。変数の型は TextReader と TextWriter ですが、実際には TextReader や TextWriter を継承したクラスのインスタンスがセットされています。

テキストデータの入出力処理は標準入出力を使うと簡単です。データの読み込みは以下のメソッドを使うと簡単です。

stream.Read() => int, 1 文字読み込み
stream.ReadLine() => string, 1 行読み込み
stream.ReadToEnd() => string, 最後まで読み込む

ファイルの終了 (EOF, end of file) を検出した場合、Read() は -1 を返し、ReadLine() は null を返します。

データの書き込みは、標準出力であれば printf や printfn がありますが、この他にも以下の関数やメソッドがあります。

fprintf stream format ...
fprintfn stream format ...
stream.Write ...
stream.WriteLine ...
System.Console.Write ...     // stdout.Write と同じ
System.Console.WriteLine ... // stdout.WriteLine と同じ

簡単な例を示しましょう。

> stdin.ReadLine();;
hello, world!
val it: string = "hello, world!"

> stdout.WriteLine("hello, world!");;
hello, world!
val it: unit = ()

> System.Console.WriteLine("hello, world!");;
hello, world!
val it: unit = ()

> fprintfn stdout "hello, world!";;
hello, world!
val it: unit = ()

hello, world! と入力してリターンキーを押すと、ReadLine() は入力データを文字列にして返します。このとき、改行文字が取り除かれることに注意してください。

それでは簡単な例題として、入力をそのままエコーバックする関数 echo を作ってみましょう。プログラムは次のようになります。

リスト : エコーバック

let rec echo () =
  let a = stdin.ReadLine()
  if a <> null then (
    stdout.WriteLine(a)
    echo ()
  ) else ()

標準入力 stdin から ReadLine() で 1 行読み込み、それを変数 a にセットします。a が null でなければデータを読み込むことができました。stdout.WriteLine() で標準出力へ出力します。ReadLine() は改行を取り除くので、WriteLine() で改行を付け加えます。あとは echo を再帰呼び出しするだけです。

簡単な実行例を示します。

> let rec echo () =
-   let a = stdin.ReadLine()
-   if a <> null then (
-     stdout.WriteLine(a)
-     echo ()
-   ) else ();;
val echo: unit -> unit

> echo();;
hello    <-- 入力
hello
world    <-- 入力
world
foo bar baz     <-- 入力
foo bar baz
val it: unit = ()      <-- CTRL-D の入力で終了

Unix 系 OS の場合、echo を終了するには CTRL-D を入力してください。

●テキストファイルのオープンとクローズ

ファイルにアクセスする場合、次の 3 つの操作が基本になります。

  1. アクセスするファイルをオープンする
  2. 入出力関数やメソッドを使ってファイルを読み書きする。
  3. ファイルをクローズする。

「ファイルをオープンする」とは、アクセスするファイルを指定して、それと一対一に対応するストリームを生成することです。入出力関数やメソッドは、生成したストリームを経由してファイルにアクセスします。

F# の場合、テキストファイルをオープンするにはモジュール System.IO に定義されているクラス StreamReader と StreamWriter を使います。StreamReader は TextReader を、StreamWriter は TextWriter を継承しています。

new StreamReader("ファイル名") => stream
new StreamWriter("ファイル名") => stream

ファイル名は文字列で指定し、ファイル名のパス区切り記号にはスラッシュ ( / ) を使います。Windows でパス区切り記号に \ を使う場合、@quoted string ("..." の前に @ を付けた文字列) を使うと便利です。@" ... " の中はエスケープシーケンスの処理が無効になります。

"C:\\Foo\\Bar\\Baz\\oops.txt" => @"C:\Foo\Bar\Baz\oops.txt"

ストリームを生成するときは use バインドまたは using 関数を使うと便利です。

1. use 変数名 = インスタンスの生成
2. using( インスタンスの生成 ) ラムダ式

use は let と同様に変数に値を束縛 (バインド) しますが、変数がスコープの範囲から外れたとき、オープンしたファイルを自動的にクローズします。using の場合、生成されたインスタンスはラムダ式の引数に渡されます。そして、ラムダ式の実行が終了するとき、オープンしたファイルをクローズします。もちろん、メソッド Close() を使って明示的にファイルをクローズすることもできます。

簡単な使用例を示します。

> open System.IO;;
> let a = new StreamWriter("test.txt");;
val a: StreamWriter

> List.iter (fun (x: string) -> a.WriteLine(x)) ["foo"; "bar"; "baz"; "oops"];;
val it: unit = ()

> a.Close();;
val it: unit = ()

> let b = new StreamReader("test.txt");;
val b: StreamReader

> b.ReadLine();;
val it: string = "foo"

> b.ReadLine();;
val it: string = "bar"

> b.ReadLine();;
val it: string = "baz"

> b.ReadLine();;
val it: string = "oops"

> b.ReadLine();;
val it: string = <null>

> b.Close();;
val it: unit = ()

テキストデータをファイルに書き込むには、ファイルを StreamWriter でオープンします。このとき、注意事項が一つあります。既に同じ名前のファイルが存在している場合は、そのファイルの長さを 0 に切り詰めてからデータを書き込みます。既存のファイルは内容が破壊されることに注意してください。

●テキストファイルの表示

それでは簡単な例題として、ファイルの内容を画面へ出力する関数 cat を作ってみましょう。プログラムは次のようになります。

リスト : ファイルの表示

open System.IO

let cat (filename: string) =
  use fin = new StreamReader(filename)
  let rec cat_sub () =
    let c = fin.Read()
    if c <> -1 then (
      printf "%c" (char c)
      cat_sub ()
    ) else ()
  cat_sub ()

関数 cat の引数 filename はファイル名を表す文字列です。failename をオープンして入力ストリームを変数 fin にセットします。ファイルの表示は局所関数 cat_sub で行います。Read() で 1 文字読み込み、それを printfn "%c" で標準出力へ出力します。%c は文字を出力する書式です。c は int なので (char c) で文字型に変換しています。あとは cat_sub を再帰呼び出しするだけです。

Read() が -1 を返したら EOF に到達したので、cat_sub の実行を終了します。関数 cat の実行も終了するので、変数 fin のインスタンスが破棄されて、オープンしたファイルがクローズされます。

ところで、cat は再帰呼び出しを使いましたが、繰り返しでも簡単にプログラムを作ることができます。次のリストを見てください。

リスト : ファイルの表示 (2)

let cat1 (filename: string) =
  use fin = new StreamReader(filename)
  let mutable c = 0
  while c <> -1 do
    c <- fin.Read()
    if c <> -1 then printf "%c" (char c)

mutable な変数 c を用意し、Read() の返り値を c にセットします。c が -1 でなければ c を出力して、while ループで処理を繰り返します。とても簡単ですね。なお、Read() と printfn "%c" のかわりに ReadLine() と WriteLine() を使って行単位で入出力を行っても同じようにプログラムを作ることができます。

●バイナリファイルの入出力

バイナリファイルの入出力は System.IO に定義されているクラス FileStream を使います。FileStream はバイト単位で入出力を行う基本的なクラスです。FileStream のスーパークラスは Stream という抽象クラスです。

new FileStream(filename: string, mode: FileMode) => stream

引数 mode にはファイルのアクセスモードを指定します。FileMode の値を以下に示します。

一般的な使い方では、データの読み込みに Open を、書き込みに Create を指定すればいいでしょう。FileStream の用意されている基本的なアクセスメソッドを以下に示します。

1. ReadByte() => int, 1 バイト読み込む (EOF に到達した場合は -1 を返す)
2. Read(buff: byte[], offset: int, cnt: int) => int
   cnt バイト読み込んで、buff + offset 以降に格納する。返り値は読み込んだバイト数
3. WriteByte(byte c) => unit, 1 バイト書き込む
4. void Write(buff: byte[], offset: int, cnt: int) => unit
   buff + offset からデータを cnt バイト書き込む

簡単な使用例を示します。

> let c = new FileStream("test.dat", FileMode.Create);;
val c: FileStream

> List.iter (fun (x: byte) -> c.WriteByte(x)) [1uy .. 8uy];;
val it: unit = ()

> c.Close();;
val it: unit = ()

> let d = new FileStream("test.dat", FileMode.Open);;
val d: FileStream

> d.ReadByte();;
val it: int = 1

> d.ReadByte();;
val it: int = 2

> d.ReadByte();;
val it: int = 3

> d.ReadByte();;
val it: int = 4

> d.ReadByte();;
val it: int = 5

> d.ReadByte();;
val it: int = 6

> d.ReadByte();;
val it: int = 7

> d.ReadByte();;
val it: int = 8

> d.ReadByte();;
val it: int = -1

> d.Close();;
val it: unit = ()

●ファイルのコピー

簡単な例題として、ファイルをコピーする関数 cp を作ってみましょう。次のリストを見てください。

リスト : ファイルのコピー

open System.IO

let cp (src: string) (dst: string) =
  use fin = new FileStream(src, FileMode.Open)
  use fout = new FileStream(dst, FileMode.Create)
  let mutable c = 0
  while c <> -1 do
    c <- fin.ReadByte()
    if c <> -1 then fout.WriteByte((byte c))

引数 src がコピー元のファイル名、dst がコピー先のファイル名です。src はアクセスモード Open で、dst は Create でオープンします。次に、mutable な変数 c を用意して、while ループの中で ReadByte() で読み込んだデータを c にセットします。c が -1 でなければ WriteByte() で c を出力します。c は int なので、byte c で c を byte にキャストしています。あとは EOF に到達するまで、fin からデータを読み込み、それを fout へ書き込むだけです。

Read() と Write() を使う場合は次のようになります。

リスト : ファイルのコピー (2)

open System.IO

let cp1 (src: string) (dst: string) =
  use fin = new FileStream(src, FileMode.Open)
  use fout = new FileStream(dst, FileMode.Create)
  let buff = Array.create 256 0uy
  let mutable eof = false
  while not eof do
    let c = fin.Read(buff, 0, 256)
    fout.Write(buff, 0, c)
    if c < 256 then eof <- true

Array.create で byte 配列を生成して変数 buff にセットします。それから、ファイルの終了を表す mutable な変数 eof を用意して false に初期化します。eof が false の間は while ループを繰り返します。ループの中では Read() で fin から 256 バイトのデータを読み込んで buff にセットします。Read() は実際に読み込んだバイト数を返すので、それが 256 よりも小さい場合はファイルを最後まで読み込んだことになります。eof を true に書き換えて、while ループを終了します。

●BinayReader と BinaryWriter

バイト以外の単位でバイナリファイルの入出力を行う場合はクラス BinaryReader と BinaryWriter を使うと便利です。

new BinaryReader(Stream input) => stream
new BinaryWriter(Stream output) => stream

通常、コンストラクタの引数には FileStream で生成したストリームを渡します。BiaryReader と BinaryWriter には様々な型でデータを読み書きするメソッドが用意されています。詳細は .NET のリファレンスをお読みください。

簡単な使用例を示します。

> let a = new FileStream("rand.dat", FileMode.Create);;
val a: FileStream

> let b = new BinaryWriter(a);;
val b: BinaryWriter

> open System;;
> let c = new Random();;
val c: Random

> for i = 1 to 8 do
-   let x = c.NextDouble()
-   b.Write(x);;
val it: unit = ()

> b.Close();;   // a もクローズされる
val it: unit = ()

> let d = new FileStream("rand.dat", FileMode.Open);;
val d: FileStream

> let e = new BinaryReader(d);;
val e: BinaryReader

> e.ReadDouble();;
val it: float = 0.4376040322

> e.ReadDouble();;
val it: float = 0.4769175733

> e.ReadDouble();;
val it: float = 0.2004353027

> e.ReadDouble();;
val it: float = 0.4157152674

> e.ReadDouble();;
val it: float = 0.4928314161

> e.ReadDouble();;
val it: float = 0.9473991958

> e.ReadDouble();;
val it: float = 0.6436219041

> e.ReadDouble();;
val it: float = 0.8567656831

> e.Close();;   // d もクローズされる
val it: unit = ()

●コマンドライン引数の取得

F# の場合、コマンドライン引数はモジュール System の関数 Environment.GetCommandLineArgs() で取得することができます。返り値は string の配列です。先頭要素が実行ファイル、それ以降にコマンド引数がセットされます。

簡単な例を示しましょう。

リスト : コマンドライン引数 (cmdline/Program.fs)

open System

printfn "%A" (Environment.GetCommandLineArgs())
$ cd cmdline
$ dotnet run foo bar baz
[|"/home/mhiroi/...略.../cmdline.dll"; "foo"; "bar"; "baz"|]

なお、dotnet fsi でスクリプトファイルとして実行すると、先頭要素が REPL のパス、2 番目の要素がスクリプトファイル名、それ以降にコマンドライン引数がセットされます。

$ cp Program.fs cmdline.fsx
$ dotnet fsi cmdline.fsx foo bar baz
[|"/home/mhiroi/...略.../fsi.dll"; "cmdline.fsx"; "foo"; "bar"; "baz"|]

プログラムの実行方法によってコマンドライン引数の位置が異なることに注意してください。

C# やC言語のように、コマンドライン引数をメイン関数 (C# は Main, C言語は main) の引数として受け取ることもできます。F# のメイン関数は属性 [<EntryPoint>] で指定します。cmdline/Program.fs を次のように修正します。

リスト : エントリーポイントの指定

open System

[<EntryPoint>]
let main args =
  printfn "%A" (Environment.GetCommandLineArgs())
  printfn "%A" args
  0
$ dotnet run foo bar baz
[|"/home/mhiroi/...略.../cmdline.dll"; "foo"; "bar"; "baz"|]
[|"foo"; "bar"; "baz"|]

関数名は main 以外でもかまいません。main の引数 args にコマンドライン引数を格納した string[] が渡されます。main の返り値は終了コードを表す整数値です。main の型は string[] -> int となります。この場合、実行ファイル名は args から削除されることに注意してください。

それでは簡単な例題として、コマンドラインからファイル名を取得して、そのファイルの内容を表示するプログラムを作りましょう。プロジェクト名は cat としました。

リスト : ファイルの内容を表示する (cat/Program.fs)

open System.IO

let rec cat (fin: TextReader) =
  let ls = fin.ReadLine()
  if ls = null then ()
  else (
    stdout.WriteLine(ls)
    cat fin
  )

[<EntryPoint>]
let main args =
  if Array.length args = 0 then
    cat stdin
  else
    for file in args do
      printfn "=> %s <=" file
      new StreamReader(file) |> cat
  0

関数 cat は引数にストリーム (TextReader) を受け取ります。今回のプログラムでは、ファイル名が指定されていないときは標準入力 stdin からデータを読み込みます。stdin の型は TextReader なので、cat の型は TextReader にしておくと都合がいいです。

あとは、関数 main で args の長さをチェックし、0 ならば cat stdin を呼び出します。そうでなければ、for ループで args からファイル名を順番に取り出し、StreamReader でストリームを生成して cat に渡します。StreamReader は TextReader のサブクラスなので、自動的にアップキャストが行われます。

それでは実際に試してみましょう。

$ cd cat
$ cat test1.txt
foo
bar
baz
oops
$ cat test2.txt
Foo
Bar
Baz
Oops!
Hello, world
$ dotnet run < test1.txt
foo
bar
baz
oops
$ dotnet run test1.txt
=> test1.txt <=
foo
bar
baz
oops
$ dotnet run test1.txt test2.txt
=> test1.txt <=
foo
bar
baz
oops
=> test2.txt <=
Foo
Bar
Baz
Oops!
Hello, world

正常に動作しているようですね。F# で簡単なコマンドラインツールを作ってみるのも面白いと思います。

●問題

次の関数を定義してください。

  1. テキストファイルの先頭 10 行を表示する head_file filename
  2. テキストファイルの最後 10 行を表示する tail_file filename
  3. 2 つのテキストファイルを行単位で連結する paste_file file1 file2
  4. 2 つのファイルをバイト単位で比較する cmp_file file1 file2
  5. ファイルのエントロピーを計算する entoropy filename
    各記号 \(a_i\) の出現確率 \(P(a_i)\) がわかると、次の式でエントロピー H を求めることができます。
    \( H = - \displaystyle \sum_{i} P(a_i) \log_2 {P(a_i)} \quad (bit) \)

エントロピーについては拙作のページ Algorithms with Python シャノン・ファノ符号とハフマン符号 をお読みくださいませ。













●解答1

リスト : ファイルの先頭 10 行を表示する

let head_file (filename: string) =
  use fin = new StreamReader(filename)
  let rec head_file_sub i =
    if i < 10 then (
      let ls = fin.ReadLine()
      if ls <> null then (
        stdout.WriteLine(ls)
        head_file_sub (i + 1)
      )
    )
  head_file_sub 0

StreamReader で引数 filename をリードオープンします。あとは局所関数 head_file_sub で 10 行読み込んで、WriteLine で出力します。途中でファイルの終了を検出した場合、ReadLine() は null を返すので、head_file_sub の再帰呼び出しを終了します。

●解答2

リスト : ファイルの末尾 10 行を表示する

let tail_file (filename: string) =
  use fin = new StreamReader(filename)
  let rec tail_file_sub buff =
    let xs = fin.ReadLine()
    if xs = null then
      buff
    else
      tail_file_sub ((if List.length buff >= 10 then List.tail buff else buff) @ [xs])
  List.iter (fun (x: string) -> stdout.WriteLine(x)) (tail_file_sub [])

読み込んだ直近の 10 行を局所関数 tail_file_sub の引数 buff のリストに保持します。buff の長さが 10 に満たない場合、buff の末尾に読み込んだ行 [xs] を連結します。buff の長さが 10 の場合は、List.tail で先頭要素を取り除いてから [xs] を連結します。ファイルの終了を検出したら buff を返します。最後に List.iter で tail_file_sub の返り値から 1 行ずつ取り出して WriteLine で出力します。

なお、このプログラムはリストの連結に @ (append) を使っているので、効率はよくありません。興味のある方はプログラムを改良してみてください。

●解答3

リスト : ファイルを行単位で連結する

let input_one_line (fin: StreamReader) =
  let ls = fin.ReadLine()
  if ls = null then None
  else Some ls

let rec flush_file (fin: StreamReader) =
  match input_one_line fin with
    None -> ()
  | Some x -> printfn "%s" x; flush_file fin

let paste_file (file1: string) (file2: string) =
  use fin1 = new StreamReader(file1)
  use fin2 = new StreamReader(file2)
  let rec paste_sub () =
    let buff1 = input_one_line fin1
    let buff2 = input_one_line fin2
    match (buff1, buff2) with
      (None, None) -> ()
    | (Some x, None) -> printfn "%s" x; flush_file fin1
    | (None, Some x) -> printfn "%s" x; flush_file fin2
    | (Some x, Some y) -> printfn "%s%s" x y; paste_sub ()
  paste_sub ()

最初に、引数 file1 と file2 を StreamReader でオープンします。次に関数 input_one_line で 1 行読み込み、変数 buff1 と buff2 にセットします。input_one_line は fin から 1 行読み込み、それを Some に包んで返します。ファイルの終了を検出した場合は None を返します。

次に、match で buff1 と buff2 をパターンマッチングします。どちらも None であれば unit を返します。(Some x, None) とマッチングした場合、printfn で x を出力し、flush_file で fin1 を出力します。逆に、(None, Some x) とマッチングした場合は fin2 を出力します。(Some x, Some y) とマッチングした場合、printfn で x と y を出力してから paste_sub を再帰呼び出しします。

●解答4

リスト : ファイルの比較

open System.IO

let cmp_file (file1: string) (file2: string) =
  use s1 = new FileStream(file1, FileMode.Open)
  use s2 = new FileStream(file2, FileMode.Open)
  let mutable c1 = 0
  let mutable c2 = 0
  let mutable cnt = 0
  while c1 = c2 && c1 <> -1 do
    c1 <- s1.ReadByte()
    c2 <- s2.ReadByte()
    cnt <- cnt + 1
  if c1 = c2 then None
  else Some (cnt, c1, c2)

最初に FileStream で file1 と file2 をリードオープンして、ストリームを変数 s1, s2 にセットします。mutable な変数 c1, c2 はストリーム s1, s2 から読み込んだデータをセットし、変数 cnt で読み込んだバイト数をカウントします。これが位置になります。

あとは while ループで s1 と s2 からデータを読み込み、c1 と c2 にセットします。c1 と c2 が異なるか、c1 と c2 が等しくても c1 が EOF (-1) ならば while ループを終了します。そのあと c1 と c2 の値を比較して、等しければ None を返します。異なる場合は Some(cnt, c1, c2) を返します。

●解答5

リスト : ファイルのエントロピーを求める

open System.IO

let make_frequency (filename: string) =
  use fin = new FileStream(filename, FileMode.Open)
  let freq = Array.create 256 0
  let mutable c = 0
  while c <> -1 do
    c <- fin.ReadByte()
    if c <> -1 then freq.[c] <- freq.[c] + 1
  freq

let entoropy (filename: string) =
  let freq = make_frequency filename
  let sum = Array.fold (+) 0 freq
  let e = - (Array.fold
              (fun a x -> if x > 0.0 then a + (x * (log x / log 2.0)) else a)
              0.0
              (Array.map (fun x -> (float x) / (float sum)) freq))
  in (sum, e)

関数 make_frequency で記号 (0 - 255) の出現頻度表を作成します。ファイル filename を FileStream でリードオープンし、ReadByte() で 1 バイトずつ読み込みます。あとは記号 c に対して freq.[c] の値を +1 するだけです。関数 entoropy は各記号の出現確率 p を Array.map で求め、エントロピー e を Array.fold で計算します。

それでは、実際に Canterbury Corpus で配布されているテストデータ The Canterbury Corpus のエントロピーを求めてみましょう。

リスト : entoropy のテスト

let test_entoropy () =
  let files = ["alice29.txt"; "asyoulik.txt"; "cp.html"; "fields.c"; "grammar.lsp";
               "kennedy.xls"; "lcet10.txt"; "plrabn12.txt"; "ptt5"; "sum"; "xargs.1"]
  List.iter
    (fun name -> let (s, e) = entoropy name
                 printfn "%14s %8d  %f  %6.0f" name s e ((float s) * e / 8.0))
    files

関数 printfn で結果を出力します。モジュール Printf にはC言語の標準ライブラリ関数 printf に相当する書式出力関数が定義されています。機能はC言語の printf とほぼ同じです。詳細は F# のリファレンスマニュアルをお読みください。

> test_entoropy ();;
   alice29.txt   152089  4.567680   86837
  asyoulik.txt   125179  4.808116   75234
       cp.html    24603  5.229137   16082
      fields.c    11150  5.007698    6979
   grammar.lsp     3721  4.632268    2155
   kennedy.xls  1029744  3.573471  459970
    lcet10.txt   426754  4.669118  249071
  plrabn12.txt   481861  4.531363  272936
          ptt5   513216  1.210176   77635
           sum    38240  5.328990   25473
       xargs.1     4227  4.898432    2588
val it: unit = ()

各列の項目はファイル名、ファイルサイズ、エントロピー、下限値です。ファイルサイズ * エントロピー / 8 で圧縮の下限値を計算することができます。ただし、この結果は無記憶情報源モデルの場合であり、モデル化によってエントロピーの値は異なることに注意してください。


Copyright (C) 2022 Makoto Hiroi
All rights reserved.

[ PrevPage | F# | NextPage ]