challenge LL Golf Hole 7 - バイト数を読みやすくする

与えられたバイト数を読みやすくしてください。読みやすくとは、いわゆる human readable な表記とします(詳しくはサンプルのコードを参考にしてください)。

与えるバイト数についてはリテラルで与える、標準入力で与える、引数で与えるなどは自由とします。

余力のあるものはこのプログラムを短くしてください。

※ LL Future実行委員の高野光弘です。この出題は LL Future公式の出題であり、優れたものについてはLL Golfのセッションでご紹介させていただくかもしれません。ご理解の上、ご投稿ください。また、LL Futureのチケットは現在も発売中です。よろしければ、メインイベントの方にもぜひご参加ください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
b = gets.to_i
if b < 10**3
    puts b
elsif b < 10**6
    puts "%.1fk" % (b.to_f/10**3)
elsif b < 10**9
    puts "%.1fM" % (b.to_f/10**6)
elsif b < 10**12
    puts "%.1fG" % (b.to_f/10**9)
else
    puts "%.1fT" % (b.to_f/10**12)
end

Posted feedbacks - Flatten

Nested Hidden
一応YiBまで対応(処理系が対応してればだけど.
変換対象のバイト数はスタイルシートパラメタnで与えます。
449bytes, 実質443bytes
1
2
3
4
5
6
7
8
<transform version="2.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:y="y">
<output method="text"/><param name="n"/>
<variable name="u" select="('B','KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB')"/>
<template match="/"><value-of select="y:f($n,1)"/></template>
<function name="y:f"><param name="m"/><param name="i"/>
<sequence select="if($m&lt;1024)then($m,$u[$i])else
y:f(round-half-to-even($m div 1024,2),$i+1)"/>
</function></transform>

#7312と同じ考えでC++で。入力は標準入力。
133bytes。実質132bytes
1
2
3
#include <iostream>
char*p=" \bKiMiGiTiPiEiZiYi";
main(){double n;std::cin>>n;for(;1024<n;p+=2)n/=1024;std::cout<<n<<*p<<p[1]<<"B";}

標準入力から読み込み。
Yまで対応しています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using System;
class P {
    static void Main() {
        double n = double.Parse(Console.ReadLine());
        string s = " kMGTPEZY";
        int c = 0, l = 1000;
        for (; n >= l; c++)
            n /= l;
        Console.WriteLine("{0:f1}{1}", n, s[c]);
    }
}

CPANモジュールNumber::Bytes::Humanを使ってone-liner。入力は標準入力

perl -MNumber::Bytes::Human=format_bytes -nlE'say format_bytes$_’

66bytesかな

-p を使ってもう1byte削れた. おまけでv5.10じゃなくても大丈夫に

perl -MNumber::Bytes::Human=format_bytes -ple'$_=format_bytes$_’

とりあえず、縮めてないです。
 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
#include <stdio.h>

#define QWORD unsigned long long
#define DWORD unsigned long
#define WORD  unsigned short
#define BYTE  char

int put_unit(char * p,int n,char u){
    if(n) return sprintf(p,"%d%c",n,u);
    if(u=='B') return sprintf(p,"B");
    return 0;
}

void conv_byte(char *p,QWORD num){
    int i;
    if(num==0) sprintf(p++,"0");
    for(i=6;i>=0;i--)
        p+=put_unit(p,(num>>(10*i))&0x3ff,"BKMGTPE"[i]);
}

void conv_put(char *buf,QWORD n){
    conv_byte(buf,n);
    printf("%Ld=%s\n",n,buf);    
}
int main(){
    char buf[64];
    
    conv_put(buf,0);
    conv_put(buf,(BYTE)-1);
    conv_put(buf,(WORD)-1);
    conv_put(buf,(DWORD)-1);
    conv_put(buf,(QWORD)-1);
    
    return 0;
}

Squeak Smalltalk で。

1
2
3
4
5
6
7
| byte |
byte := 123456789012345.
^(#('' k M G T P E Z) inject: byte into: [:result :unit |
    result < 1024 ifTrue: [^result asString, unit].
    result / 1024 roundTo: 0.1]) asString, 'Y'

"=> '112.3T' "

お題のSample Codeを参考に書いてみました。byte数は標準入力から与えます。111byte。

1
2
$i=int(((($n=<>)=~tr/0-9/0-9/)-1)/3);
printf(($i?"%.1f":"%d")."%s\n",$n/10**($i*3),('',qw(k M G T P E Z Y))[$i])

1000で割る方で書いてみました。 タブと改行を除いて 166B

1
2
3
4
5
6
7
8
class P{
    public static void main(String[]a){
        double d=Double.parseDouble(a[0]);
        int i=0,l=1000;
        for(;d>=l;d/=l,i++);
        System.out.printf("%.1f%s",d," kMGTPEZY".charAt(i));
    }
}

投稿直後に縮むことに気付いたので再投稿。 これで 164B

1
2
3
4
5
6
7
8
class P{
    public static void main(String[]a){
        double d=Double.parseDouble(a[0]);
        int i=0,l=1000;
        for(;d>=l;d/=l,i++);
        System.out.printf("%.1f"+" kMGTPEZY".charAt(i),d);
    }
}

一人だけ問題を読み違えているようですが・・・このまま行きます。
本体は155Byte
 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
#include <stdio.h>
#include <string.h>

#define QWORD unsigned long long
#define DWORD unsigned long
#define WORD  unsigned short
#define BYTE  char

void conv_byte(char *p,QWORD n){
    int m,i=7;strcpy(p,"0B");
    if(n)while(i--)p+=(m=(n>>10*i)&1023)?sprintf(p,"%d%c",m,"BKMGTPE"[i]):i?0:sprintf(p,"B");
}

void conv_put(char *buf,QWORD n){
    conv_byte(buf,n);
    printf("%Ld=%s\n",n,buf);    
}

int main(){
    char buf[64];
    
    conv_put(buf,(QWORD)-1);
    conv_put(buf,(DWORD)-1);
    conv_put(buf,(WORD)-1);
    conv_put(buf,(BYTE)-1);
    conv_put(buf,0);

    conv_put(buf,(QWORD)1024*1024*1024*1024*1024*1024);
    conv_put(buf,(QWORD)1024*1024*1024*1024*1024);
    conv_put(buf,(QWORD)1024*1024*1024*1024);
    conv_put(buf,(QWORD)1024*1024*1024);
    conv_put(buf,(QWORD)1024*1024);
    conv_put(buf,(QWORD)1024);
    conv_put(buf,(QWORD)1);

    return 0;
}

 #7320をお手本にJavaScriptで。( toFixed()メソッドがちょっと嫌な感じですが。)
1
2
3
4
(function (d) {
  for (var i = 0; d >= 1024; d /= 1024, i++);
  alert(d.toFixed(2) + ' kMGTPEZY'.charAt(i));
})(123456789012345);

あんまり納得のいく出来ではない。forの中が汚い。

 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
def readable(byte, base=1024):
  '''
  >>> readable(0)
  '0'
  >>> readable(100)
  '100'
  >>> readable(1024)
  '1.0k'
  >>> readable(1900*1024)
  '1.9M'
  >>> readable(1900*1024*1024)
  '1.9G'
  >>> readable(1900*(1024**7))
  '1.9Y'
  '''
  for c in ['','k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']:
    d, m = divmod(byte, base)
    if not d:
      if c:
        return '%.1f%s'%(float(exp)/base, c)
      else:
        return str(m)
    else:
      exp = byte
      byte = d
  return 'Not supported'


if __name__ ==  '__main__':
  import doctest
  doctest.testmod()

大差でこちらだと思う。 **を一杯計算するが・・・。

System Message: WARNING/2 (<string>, line 1); backlink

Inline strong start-string without end-string.
 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
def readable(byte, base=1024):
  '''
  >>> readable(0)
  '0'
  >>> readable(100)
  '100'
  >>> readable(1024)
  '1.0k'
  >>> readable(1900*1024)
  '1.9M'
  >>> readable(19000*1024)
  '18.6M'
  >>> readable(190000*1024)
  '185.5M'
  >>> readable(1900*1024*1024)
  '1.9G'
  >>> readable(1900*(1024**7))
  '1.9Y'
  '''
  for i, c in enumerate(['','k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']):

    if byte < base **(i+1):
      if c:
        return '%.1f%s'%(float(byte)/base**i, c)
      else:
        return str(byte)

  return 'Not supported'


if __name__ ==  '__main__':
  import doctest
  doctest.testmod()

R的にはループを使わない方向で。

1
2
3
4
5
6
bytes.pretty <- function(n){
  u <- c('', 'k', 'M', 'G', 'T')
  r <- 1024^(1:length(u)-1)
  i <- which.min(abs(512-n/r))
  sprintf("%.1f%s", n/r[i], u[i])
}

前出の再帰バージョン。 再帰のありがたみはあまりない。 しいて言えば、Yでかけないやつが来たときもreadableではないが数字列を返す点か。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def readable(byte, base=1024):
  def sub(byte, k, bound, unit):
    if byte < bound:
      if unit and unit[0]:
        return '%.1f%s'%(float(byte)/k, unit[0])
      else:
        return str(byte)
    else:
      return sub(byte, k*base, bound *base, unit[1:])
  return sub(byte, 1, base, ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'])

1024 でなくて 1000 でいいんですね?

データは標準入力から。
サンプルプログラムのように k=1000 で。
>echo 123|jconsole unit1.ijs
123

>echo 1234|jconsole unit1.ijs
1.2k

>echo 123456789|jconsole unit1.ijs
123.5M

>echo 12345678901234567|jconsole unit1.ijs
12345.7T
1
exit wd;(a<1e3){a;~(0j1":a%10^3*b),(b=.((a=.x:".}:1!:1[3)>10^3*i.5)i:1){' kMGT'

raw_input()を使って書いてみた。86bytes。

1
s=raw_input();p=-min(4,(len(s)-1)/3)*3;print[s,s[:p]+'.'+s[p:1+p]+'kMGT'[-1-p/3]][p<0]

またクラス作るパターンでやりました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env gosh
(define-class <b> ()
  ((b :init-keyword :b :accessor b-b)))

(define-method write-object ((b <b>) port)
  (let* ((j (b-b b))
         (k 1000.0)
         (l '(byte k M G T P E Z Y))
         (m (min (truncate->exact (/ (log j) (log k)))
                 (- (length l) 1))))
    (if (< j k)
        (display j)
        (display (/ (truncate (* (/ j (expt k m)) 10)) 10)))
    (print (list-ref l m))))

(define (main args)
  (display "input: ")(flush)
  (print (make <b> :b (x->number(read)))))

81B。

1
2
3
4
5
6
/^..\?.\?$/b
s/$/.0kMGT/
:a
s/\(.\)\(.\)..\...\(.\)/\1.\2\3/
ta
s/\(\...\).*/\1/

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
#include <iostream>

template <bool b, class T, class S> struct Choice { typedef T Result; };
template <class T, class S> struct Choice<false, T, S> { typedef S Result; };

struct None
{
    enum { symbol = ' ' };
    static double scaled(long long n) throw() 
      { return static_cast<double>(n); }
};
struct Kilo
{
    enum { symbol = 'K' };
    static double scaled(long long n) throw() 
      { return static_cast<double>(n) / 1000LL; }
};
struct Mega
{
    enum { symbol = 'M' };
    static double scaled(long long n) throw() 
      { return static_cast<double>(n) / 1000000LL; }
};
struct Giga
{
    enum { symbol = 'G' };
    static double scaled(long long n) throw() 
      { return static_cast<double>(n) / 1000000000LL; }
};
struct Tera
{
    enum { symbol = 'T' };
    static double scaled(long long n) throw() 
      { return static_cast<double>(n) / 1000000000000LL; }
};

template <long long n>
struct SIPrefix
{
    typedef
        typename Choice< n <          1000LL, None,
        typename Choice< n <       1000000LL, Kilo,
        typename Choice< n <    1000000000LL, Mega,
        typename Choice< n < 1000000000000LL, Giga,
                                              Tera
      >::Result>::Result>::Result>::Result  Result;
};

template <long long n>
struct HumanReadable
{
private:
    typedef typename SIPrefix<n>::Result prefix;
    friend std::ostream& operator << (std::ostream& os, struct HumanReadable<n>)
      {
        os << prefix::scaled(n) << static_cast<char>(prefix::symbol);
      }
};

int main()
{
    std::cout << HumanReadable<123456789012345LL>() << std::endl;
    return 0;
};

1000だと文字の長さや正規表現を使った解が作りやすいけど、「バイト数」というお題を考えると1024でなければ意味がないし・・・。

この違いは結構大きいのではないかと思います。


もっと純粋関数言語っぽくしてみました。g++だとこんなすさまじいのが通る。

 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
#include <iostream>

template <bool b, class T, class S> struct Choice { typedef T Result; };
template <class T, class S> struct Choice<false, T, S> { typedef S Result; };

struct None {enum{ symbol = ' ', base =             1LL };};
struct Kilo {enum{ symbol = 'K', base =          1000LL };};
struct Mega {enum{ symbol = 'M', base =       1000000LL };};
struct Giga {enum{ symbol = 'G', base =    1000000000LL };}; 
struct Tera {enum{ symbol = 'T', base = 1000000000000LL };}; 
template <long long n> struct SIPrefix
{
    typedef
        typename Choice< n < Kilo::base,     None,
        typename Choice< n < Mega::base,     Kilo,
        typename Choice< n < Giga::base,     Mega,
        typename Choice< n < Tera::base,     Giga,
                                             Tera
      >::Result>::Result>::Result>::Result  Result;
};

template <long long n> struct HumanReadable
{
private:
    typedef typename SIPrefix<n>::Result prefix;
    friend std::ostream& operator << (std::ostream& os, struct HumanReadable<n>)
      {
        return os << static_cast<double>(n)/prefix::base << static_cast<char>(prefix::symbol);
      }
};

int main()
{
    std::cout << HumanReadable<123456789012345LL>() << std::endl;
    return 0;
};

Pまで対応して正味74byte。 他の人と同じやり方。

1
ruby -e'n=gets.to_i;k=0;b=1024;while(n>b&&k<5);n/=b;k+=1;end;print n," KMGTP"[k,1]'

4bytes削った。82bytes

1
s=raw_input();p=-min(4,(len(s)-1)/3)*3;print[s,s[:p]+'.'+s[p]+'kMGT'[-1-p/3]][p<0]

小数点以下の桁数を固定で表示する標準的な方法が分かりません…。これだと、「10000000」が「10.MB」になってしまう。

1
2
3
4
5
6
7
8
(use srfi-42)

(print (let ((b 1000)
              (x (x->integer (read))))
          (last-ec x (:parallel (:do ((y x)) (>= y b) ((/. y b)))
                                   (: u "KMGTPEZY"))
            (format "~,,,,3a~a" (/. y b) u)))
        'B)

問題を見たときに多くの人が考えたであろうネタ回答。

1K=1024、引数→標準出力。出題であえて「K」でなく「k」としているのはスルーしてみます。

1
2
3
dd if=/dev/zero of=.$$ bs=$1 count=1 2>/dev/null
ls -lh .$$|cut -d\  -f 5
rm .$$

あー、ログインし忘れたorz


sparse fileに対応しているファイルシステムであれば、こんな感じにしてみるとネタ感は少なくなると思います。
コードはちょっと汚くなりますが・・・。

$ time bash bytes.sh 5000000000000
4.6T

real    0m0.437s
user    0m0.259s
sys     0m0.214s
1
2
3
dd if=/dev/zero of=.$$ bs=1 seek=$(($1-1)) count=1 2>/dev/null
ls -lh .$$|cut -d\  -f 5
rm .$$

1
2
3
4
(let ((kibi 1024))
  (do ((n (read) (/. n kibi))
       (u '(|| k M G T) (cdr u)))
      ((< n kibi) (format #t "~A~AB~%" n (car u)) 0)))

余分なゼロを出さないように、つまり、 % ./a.out 1073741824 1G と、してみました。サンプルコードだと1G0M0K0Bになります。 再起を使っています。 64bit longlongしか考えないので、 ”E" までの単位にしか対応していません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

void bytes(unsigned long long l, char *suf, int zs)
{
        unsigned u l & 0x3FF;
        if(l>1023)
                bytes(l>>10, suf+1,1);
        if(u>0|| !zs && l==0) 
       printf("%d%c",u,*suf)
}

int
main(int argc , char *argv[])
{
        int i;
        unsigned long long l;
        for(i=1;i<argc;i++) {
                l=strtoll(argv[i],NULL,10);