2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【新約聖書】PowerShellスクリプトで実装する、マタイによる福音書のhtmlを85%のサイズに圧縮するアルゴリズム

Last updated at Posted at 2019-03-02

やったこと

圧縮アルゴリズムどころか、自分でソートすら書けないパソコンの大先生が
PowerShellスクリプトで圧縮アルゴリズムを実装しました。

テストとして使用した文書はこちらです。htmlをutf-8で保存しました。
http://bible.salterrae.net/kougo/html/matthew.html

結果的に 190,720 バイト => 162,672 バイトと元のサイズの85%になりました。
ど素人が「減らせた」というだけでもすごくないですか?それができるPowerShellすごい。

エクスプローラで「送る」⇒「圧縮」したら 53,578 バイトになりました。28%です。
一瞬で28%に圧縮されるなんて……奇跡としか思えません。

圧縮アルゴリズムの詳細

  • 4バイトずつ読み、頻出の値トップ128ランキングを作成
  • 先頭に頻出値を128個書く
  • トップ128に入る値は 0~0x7F の1バイトに置き換え
  • トップ128に入らない値は 0x80, 4バイト値 という計5バイトに置き換え
  • 4で割り切れないデータ余りの数だけ末尾に 0x81, 1バイト値 に置き換え

こちらがスクリプトです。

function ToMS {
    param ([string]$FullName)
    return [System.IO.MemoryStream]::new([System.IO.File]::ReadAllBytes($FullName))
}

function Zip {
    param ([System.IO.Stream]$Stream)

    $br = [System.IO.BinaryReader]::new($Stream)

    $yokuDeruInt32Top128 = 1..($Stream.Length / 4) | % { $br.ReadInt32() } | group | sort Count -Descending | select -First 128 | % { [int]$_.Name }
    
    $dic = @{}
    0..($yokuDeruInt32Top128.Count - 1) | % { $dic[$yokuDeruInt32Top128[$_]] = $_}

    $ms = [System.IO.MemoryStream]::new()
    $bw = [System.IO.BinaryWriter]::new($ms)

    $yokuDeruInt32Top128 | % { $bw.Write([int]$_) }
    $ms.Position = 128 * 4 #128個なくてもここまで進める

    $Stream.Position = 0
    while ($Stream.Length - $Stream.Position -ge 4) {
        $i = $br.ReadInt32()
        if ($dic.ContainsKey($i)) {
            $bw.Write([byte]$dic[$i])
        }
        else {
            $bw.Write([byte]0x80)
            $bw.Write([int]$i)
        }
    }

    while ($Stream.Length - $Stream.Position -ge 1) {
        $bw.Write([byte]0x81)
        $ms.WriteByte($Stream.ReadByte())
    }

    $ms.Position = 0
    return $ms
}

function UnZip {
    param([System.IO.Stream]$Stream)

    $br = [System.IO.BinaryReader]::new($Stream)

    $dic = @{}
    0..127 | % { $dic[$_] = $br.ReadInt32() }

    $ms = [System.IO.MemoryStream]::new()
    $bw = [System.IO.BinaryWriter]::new($ms)

    while ($Stream.Length - $Stream.Position -gt 0) {
        $b = [int]$br.ReadByte()
        if ($b -eq 0x80) {
            $bw.Write([int]$br.ReadInt32())
        }
        elseif ($b -eq 0x81) {
            $bw.Write([byte]$br.ReadByte())
        }
        else {
            $bw.Write([int]$dic[$b])
        }
    }

    $ms.Position = 0
    return $ms
}

コードを使うコマンドライン

# 圧縮してファイルに保存
> . .\zip.ps1; $ms = ToMS($pwd.Path+"\matthew.html"); $zipped = Zip($ms); [IO.File]::WriteAllBytes($pwd.Path+"\zipped", $zipped.ToArray())

# 伸長してファイルに保存
> . .\zip.ps1; $ms = ToMS($pwd.Path+"\zipped"); $unzipped = Unzip($ms); [IO.File]::WriteAllBytes($pwd.Path+"\unzipped", $unzipped.ToArray())

反省

頻出はE3 81 9F E3「た●」やE3 80 81 E3「、●」というような
3バイトの文字 + 何かの先頭1バイト のようになっていました。

utf-8の日本語が含まれた文書を
4バイトで区切るのは効率がよくないかもしれません。

Zip が 10秒
UnZip が 40ミリ秒
でした。

> Get-WmiObject Win32_Processor


Caption           : Intel64 Family 6 Model 158 Stepping 9
DeviceID          : CPU0
Manufacturer      : GenuineIntel
MaxClockSpeed     : 2501
Name              : Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz
SocketDesignation : U3E1

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.17763.316
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17763.316
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

ほかにPowerShellで圧縮する方法

DeflateStream を使います。これはgzip圧縮できます。結果は16%です。

> $fs=[System.IO.File]::OpenRead($pwd.Path+"\matthew.html");$ms=[System.IO.MemoryStream]::new();$ds=[System.IO.Compression.DeflateStream]::new($ms,[System.IO.Compression.CompressionMode]::Compress);$fs.CopyTo($ds);$ms.Length/$fs.Length;$ds,$ms,$fs|%{$_.Close()}

Compress-Archive コマンドレット

圧縮する方法というか.zipファイルに圧縮する方法です。
出力ファイル名を.zipとしなければエラーになってしまいます。たとえば.appxはNGです。

Compress-Archive : .appx はサポートされるアーカイブ ファイル形式ではありません。サポートされるアーカイブ ファイル形式は
、.zip のみです。


アルゴリズムは圧縮率・圧縮伸長の速度・CPU使用率・メモリ使用量などのバランスで選ぶべきです。
PowerShellで圧縮するならDeflateStreamでよろしいかと思います。

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?