web-dev-qa-db-ja.com

Rubyでかなりのファイルサイズ?

私は、バイトを表す整数を「きれいな」形式の文字列に変換するメソッドを作成しようとしています。

ここに私の半分働いた試みがあります:

class Integer
  def to_filesize
    {
      'B'  => 1024,
      'KB' => 1024 * 1024,
      'MB' => 1024 * 1024 * 1024,
      'GB' => 1024 * 1024 * 1024 * 1024,
      'TB' => 1024 * 1024 * 1024 * 1024 * 1024
    }.each_pair { |e, s| return "#{s / self}#{e}" if self < s }
  end
end

何が悪いのですか?

21
TeamBlast

Filesize gemはどうですか?バイト(および他の形式)からきれいに出力された値に変換できるようです:

例:

Filesize.from("12502343 B").pretty      # => "11.92 MiB"

http://rubygems.org/gems/filesize

27
David P

Rails-標準のRails数値ヘルパーはどうですか?

http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_human_size

number_to_human_size(number, options = {})

28
Wile E.

私は@Davidに同意します。おそらく既存のソリューションを使用するのが最善ですが、あなたが間違っていることについてのあなたの質問に答えることです:

  1. 主なエラーは、逆にではなくsselfで除算することです。
  2. 本当に前のsで割りたいので、sを1024で割ります。
  3. 整数演算を行うと混乱する結果になるので、浮動小数点に変換します。
  4. おそらく答えを丸めます。

そう:

class Integer
  def to_filesize
    {
      'B'  => 1024,
      'KB' => 1024 * 1024,
      'MB' => 1024 * 1024 * 1024,
      'GB' => 1024 * 1024 * 1024 * 1024,
      'TB' => 1024 * 1024 * 1024 * 1024 * 1024
    }.each_pair { |e, s| return "#{(self.to_f / (s / 1024)).round(2)}#{e}" if self < s }
  end
end

次のことができます。

 1.to_filesize 
#=> "1.0B" 
 1020.to_filesize 
#=> "1020.0B" 
 1024.to_filesize 
#=> "1.0KB" 
 1048576.to_filesize 
#=> "1.0MB" 

繰り返しますが、実際にそれを行うことはお勧めしませんが、バグを修正する価値はあります。

15

これは私の解決策です:

def filesize(size)
  units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'Pib', 'EiB']

  return '0.0 B' if size == 0
  exp = (Math.log(size) / Math.log(1024)).to_i
  exp += 1 if (size.to_f / 1024 ** exp >= 1024 - 0.05)
  exp = 6 if exp > 6 

  '%.1f %s' % [size.to_f / 1024 ** exp, units[exp]]
end

他のソリューションと比較して、よりシンプルで効率的で、より適切な出力を生成します。

フォーマット

両方とも to_filesizeおよびto_human大きな数字に問題があります。 format_mbには、たとえば「1 MiB」が「1024 KiB」と見なされる奇妙なケースがあります。

    Origin:       filesize    to_filesize      format_mb       to_human
       0 B:          0.0 B           0.0B            0 b         0.00 B
       1 B:          1.0 B           1.0B            1 b         1.00 B
      10 B:         10.0 B          10.0B           10 b        10.00 B
    1000 B:       1000.0 B        1000.0B         1000 b      1000.00 B
     1 KiB:        1.0 KiB          1.0KB         1024 b        1.00 KB
   1.5 KiB:        1.5 KiB          1.5KB       1536.0 b        1.50 KB
    10 KiB:       10.0 KiB         10.0KB      10.000 kb       10.00 KB
   100 KiB:      100.0 KiB        100.0KB     100.000 kb      100.00 KB
  1000 KiB:     1000.0 KiB       1000.0KB    1000.000 kb     1000.00 KB
     1 MiB:        1.0 MiB          1.0MB    1024.000 kb        1.00 MB
     1 Gib:        1.0 GiB          1.0GB    1024.000 mb        1.00 GB
     1 TiB:        1.0 TiB          1.0TB    1024.000 gb        1.00 TB
     1 PiB:        1.0 Pib          ERROR    1024.000 tb        1.00 PB
     1 EiB:        1.0 EiB          ERROR    1024.000 pb        1.00 EB
     1 ZiB:     1024.0 EiB          ERROR    1024.000 eb          ERROR
     1 YiB:  1048576.0 EiB          ERROR 1048576.000 eb          ERROR

パフォーマンス

また、それは最高のパフォーマンスを持っています。

                      user     system      total        real
filesize:         2.740000   0.000000   2.740000 (  2.747873)
to_filesize:      3.560000   0.000000   3.560000 (  3.557808)
format_mb:        2.950000   0.000000   2.950000 (  2.949930)
to_human:         5.770000   0.000000   5.770000 (  5.783925)

私は各実装を現実的な乱数ジェネレータでテストしました:

def numbers
  Enumerator.new do |enum|
    1000000.times do
      exp = Rand(5)
      num = Rand(1024 ** exp)
      enum.yield num
    end
  end
end
8
FelipeC

Integerにメソッドを追加することでポイントを獲得できますが、これはよりFile固有のように思えるので、Fileを変更することをお勧めします。たとえば、.prettysize()というメソッドをFileに追加します。

しかし、これは反復を使用し、浮動小数点としての1バイトの出力を回避する代替ソリューションです:-)

def format_mb(size)
  conv = [ 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb' ];
  scale = 1024;

  ndx=1
  if( size < 2*(scale**ndx)  ) then
    return "#{(size)} #{conv[ndx-1]}"
  end
  size=size.to_f
  [2,3,4,5,6,7].each do |ndx|
    if( size < 2*(scale**ndx)  ) then
      return "#{'%.3f' % (size/(scale**(ndx-1)))} #{conv[ndx-1]}"
    end
  end
  ndx=7
  return "#{'%.3f' % (size/(scale**(ndx-1)))} #{conv[ndx-1]}"
end
2
ChuckCottrill

@Darshan Computingのソリューションは、ここでは部分的です。ハッシュキーの順序が保証されていないため、このアプローチは確実に機能しません。これを修正するには、to_filesizeメソッド内で次のようにします。

 conv={
      1024=>'B',
      1024*1024=>'KB',
      ...
 }
 conv.keys.sort.each { |s|
     next if self >= s
     e=conv[s]
     return "#{(self.to_f / (s / 1024)).round(2)}#{e}" if self < s }
 }

これが、Float内で同様のメソッドを実行した結果です。

 class Float
   def to_human
     conv={
       1024=>'B',
       1024*1024=>'KB',
       1024*1024*1024=>'MB',
       1024*1024*1024*1024=>'GB',
       1024*1024*1024*1024*1024=>'TB',
       1024*1024*1024*1024*1024*1024=>'PB',
       1024*1024*1024*1024*1024*1024*1024=>'EB'
     }
     conv.keys.sort.each { |mult|
        next if self >= mult
        suffix=conv[mult]
        return "%.2f %s" % [ self / (mult / 1024), suffix ]
     }
   end
 end
1
Steeve McCauley