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 - Nested

Flatten 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;
}
一人だけ問題を読み違えているようですが・・・このまま行きます。
本体は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;
}
もう少し縮めた。

sprintf版は2文字関数名が増えてるけどタブ・改行含めて145byte、
printf版は同じく126byte・・・かな?
 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
#include <stdio.h>
#include <string.h>

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

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


void conv_byte(QWORD n){
    int m,i=7;
    while(i--)(m=(n>>10*i)&1023)|!i&!n?printf("%d%c",m,"BKMGTPE"[i]):i?0:printf("B");
}

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

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

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;
}

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);
    }
}
 #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()

前出の再帰バージョン。 再帰のありがたみはあまりない。 しいて言えば、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'])

移植しました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#load "nums.cma";;
open Num
let conv =
  let base = (Int 1024) in
  let rec sub byte k bound = function
    | [] -> raise (Invalid_argument "over")
    | hd::tl -> 
        if byte </ bound then
          Printf.sprintf "%.1f%s" (float_of_num (byte // k)) hd
        else
          sub byte (k */ base) (bound */ base) tl
  in
    fun byte -> sub byte (Int 1) base ["b"; "k"; "M"; "G";"T"]
  ;;
(*
  conv (Int 1024);;
  conv (Big_int (Big_int.big_int_of_string "10000000000"));; 
*)

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])
}
1024 でなくて 1000 でいいんですね?

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

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

LL Golf の Lingrチャットによると 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]

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]

ワンライナーPythonの限界に挑戦中。91bytes (本体 80bytes).

1
python -c"s=raw_input();p=min(12,len(s)-1)/3*-3;print[s,s[:p]+'.'+s[p]+' kMGT'[-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;
};

もっと純粋関数言語っぽくしてみました。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