challenge printfの自作

printf関数を自作してください。
printfの説明は不要だと思います。とりあえずWikiPediaのリンクをはっておきます。

実際にはsprintf関数を作ってください。
注意事項
  • 標準でついているprintf系関数の使用禁止
  • 標準でついているライブラリ以外の使用禁止
  • 引数・返り値等の仕様はできるだけ似せればよい

可変長引数など、言語によっては難しい/不可能な仕様もありますが、いろいろ工夫して本物に近づくようにしてみてください。
1
2
3
4
5
6
7
#include <string.h>

// なにもフォーマットしてない
int mysprintf(char *str, const char *format, ... ){
    strcpy(str, format);
    return strlen(str);
}

Posted feedbacks - Flatten

Nested Hidden
場合わけが多くなるのでなかなかきれいにまとまりませんが、仕事ではこういうベタな仕様をえいやっと書くことも多い気がするので、書いとけば参考になるかなと。

さぼったところ:
- 浮動小数点数はeEfFgGを区別せず。いい加減です。
- 変換指定子 aApnはサポートせず。
- 長さ修飾子はSchemeはサポートせず(Schemeでは意味がないかな)
- n$による引数の並べかえはサポートせず。

後はそれなりにサポートしているつもりです。
gosh> (sprintf "|%d|" 123)
"|123|"
gosh> (sprintf "|%10d|" 123)
"|       123|"
gosh> (sprintf "|%-10d|" 123)
"|123       |"
gosh> (sprintf "|%10.5d|" 123)
"|     00123|"
gosh> (sprintf "|%+10.5d|" 123)
"|    +00123|"
gosh> (sprintf "|%+10d|" 123)
"|      +123|"
gosh> (sprintf "|%+10o|" 123)
"|      +173|"
gosh> (sprintf "|%10o|" 123)
"|       173|"
gosh> (sprintf "|%#10o|" 123)
"|      0173|"
gosh> (sprintf "|%10x|" 123)
"|        7b|"
gosh> (sprintf "|%10X|" 123)
"|        7B|"
gosh> (sprintf "|%#10x|" 123)
"|      0x7b|"
gosh> (sprintf "|%f|" 3.14)
"|3.14|"
gosh> (sprintf "|%.6f|" (sqrt 2))
"|1.414214|"
gosh> (sprintf "|%10.6f|" (sqrt 2))
"|  1.414214|"
gosh> (sprintf "|%10.6f|" (- (sqrt 2)))
"| -1.414214|"
gosh> (sprintf "|%*.*f|" 10 6 (log 10))
"|  2.302585|"
gosh> (sprintf "|%f|" 10e100)
"|1.0e101|"
gosh> (sprintf "|%f|" 1e100)
"|1.0e100|"
gosh> (sprintf "|%.6f|" 1e100)
"|1.000000e100|"
gosh> (sprintf "|%s|" "abc")
"|abc|"
gosh> (sprintf "|%10s|" "abc")
"|       abc|"
gosh> (sprintf "|%-10s|" "abc")
"|abc       |"
gosh> (sprintf "|%*s|" 10 "abc")
"|       abc|"
gosh> (sprintf "|%c|" #\z)
"|z|"
gosh> (sprintf "|%%|")
"|%|"
 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
(use gauche.sequence)
(use util.match)
(use text.tree)
(use srfi-1)

(define (sprintf fmt . args)
  (define (get-var-arg name v args)
    (if v
      (cond [(string->number v) => (cut values <> args)]
            [(equal? v "*")     (values (car args) (cdr args))]
            [else (error #`",name needs to be a number or '*', but got:" v)])
      (values #f args)))
  (define (pick-arg conv args)
    (if (equal? conv "%") (values #f args) (car+cdr args)))
  (define (rec fmt args)
    (rxmatch-case fmt
      [#/(.*)%([-+ #0]*)(\d+|\*)?(?:\.(\d*|\*))?([diouxXeEfFgGaAcspn%])(.*)/
       (_ before flags width prec conv after)
       (receive (width args) (get-var-arg 'width width args)
         (receive (prec args) (get-var-arg 'prec prec args)
           (receive (arg args) (pick-arg conv args)
             `(,before
               ,(fill width flags (dispatch arg prec flags conv))
               ,(rec after args)))))]
      [else fmt]))
  (define (dispatch arg prec flags conv)
    (case (ref conv 0)
      [(#\d #\i #\u) (int arg prec flags 10 conv)]
      [(#\o)         (int arg prec flags 8 conv)]
      [(#\x #\X)     (int arg prec flags 16 conv)]
      [(#\e #\E #\f #\F #\g #\G) (real arg prec flags conv)]
      [(#\c)         (if (char? arg)
                       (string arg)
                       (error "char required for %c conversion:" arg))]
      [(#\s)         (if (string? arg)
                       (if (and prec (< prec (string-length arg)))
                         (string-take arg prec)
                         arg)
                       (error "string required for %s conversion:" arg))]
      [(#\%)         "%"]
      [else          (error "unsupported conversion:" conv)]))
  (define (fill w f s)
    (or (and-let* ([ w ]
                   [len (string-length s)]
                   [(< len w)]
                   [pad (- w len)])
          (if (string-index f #\-)
            (cons s (make-string pad #\space))
            (cons (make-string pad (if (string-index f #\0) #\0 #\space)) s)))
        s))
  (define (sign arg f s)
    (define (pos-sign)
      (cond [(string-index f #\+) "+"]
            [(string-index f #\space) " "]
            [else ""]))
    (string-append (if (negative? arg) "-" (pos-sign)) s))
  (define (minpad p s)
    (if (and p (< (string-length s) p))
      (string-append (make-string (- p (string-length s)) #\0) s)
      s))
  (define (prefix f c s)
    (if (string-index f #\#)
      (case (ref c 0)
        [(#\o) (string-append "0" s)]

        [(#\x) (string-append "0x" s)]
        [(#\X) (string-append "0X" s)])
      s))
  (define (int arg p f r c)
    (unless (and (exact? arg) (integer? arg))
      (error "exact integer required for conversion:" c))
    (sign arg f (prefix f c (minpad p (number->string (abs arg) r (equal? c "X"))))))
  (define (maxprec p s)
    (cond [(not p) s]
          [else
           (regexp-replace*
            s #/\.(\d+)/
            (lambda (m)
              (cond [(zero? p) ""]
                    [else
                     (let1 oprec (string-length (m 1))
                       (cond [(< p oprec)
                              #`".,(round->exact (/. (string->number (m 1)) (expt 10 (- oprec p))))"]
                             [else
                              #`".,(m 1),(make-string (- p oprec) #\\0)"]))
                     ])))
           ]))
  (define (real arg p f c)
    (unless (real? arg)
      (error "real number required for conversion:" c))
    (maxprec p (number->string arg)))
  (tree->string (rec fmt args)))

しまった。複数のフォーマット指示子がある場合にちゃんと動きませんでした。

 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
*** t.scm    2007-11-25 01:51:56.000000000 -1000
--- t2.scm    2007-11-25 01:53:52.000000000 -1000
***************
*** 13,28 ****
    (define (pick-arg conv args)
      (if (equal? conv "%") (values #f args) (car+cdr args)))
    (define (rec fmt args)
!     (rxmatch-case fmt
!       [#/(.*)%([-+ #0]*)(\d+|\*)?(?:\.(\d*|\*))?([diouxXeEfFgGaAcspn%])(.*)/
!        (_ before flags width prec conv after)
!        (receive (width args) (get-var-arg 'width width args)
!          (receive (prec args) (get-var-arg 'prec prec args)
!            (receive (arg args) (pick-arg conv args)
!              `(,before
!                ,(fill width flags (dispatch arg prec flags conv))
!                ,(rec after args)))))]
!       [else fmt]))
    (define (dispatch arg prec flags conv)
      (case (ref conv 0)
        [(#\d #\i #\u) (int arg prec flags 10 conv)]
--- 13,31 ----
    (define (pick-arg conv args)
      (if (equal? conv "%") (values #f args) (car+cdr args)))
    (define (rec fmt args)
!     (receive (pre post) (string-scan fmt #\% 'both)
!       (if post
!         (rxmatch-case post
!           [#/([-+ #0]*)(\d+|\*)?(?:\.(\d*|\*))?([diouxXeEfFgGaAcspn%])(.*)/
!            (_ flags width prec conv after)
!            (receive (width args) (get-var-arg 'width width args)
!              (receive (prec args) (get-var-arg 'prec prec args)
!                (receive (arg args) (pick-arg conv args)
!                  `(,pre
!                    ,(fill width flags (dispatch arg prec flags conv))
!                    ,(rec after args)))))]
!           [else (error "bad format string:" fmt)])
!         fmt)))
    (define (dispatch arg prec flags conv)
      (case (ref conv 0)
        [(#\d #\i #\u) (int arg prec flags 10 conv)]

Haskellの場合,可変長引数の扱いに工夫が必要です.

下手なコードを書くより,ghcのライブラリを読んで実際にどうしているかを見る方が勉強になると思うので,ライブラリのコード(Text.Printfモジュール)をそのまま掲載します.

  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
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
-----------------------------------------------------------------------------
-- |
-- Module      :  Text.Printf
-- Copyright   :  (c) Lennart Augustsson, 2004
-- License     :  BSD-style (see the file libraries/base/LICENSE)
-- 
-- Maintainer  :  lennart@augustsson.net
-- Stability   :  provisional
-- Portability :  portable
--
-- A C printf like formatter.
--
-----------------------------------------------------------------------------

module Text.Printf(
   printf, hPrintf,
   PrintfType, HPrintfType, PrintfArg, IsChar
) where

import Prelude
import Data.Char
import Data.Int
import Data.Word
import Numeric(showEFloat, showFFloat, showGFloat)
import System.IO

-------------------

-- | Format a variable number of arguments with the C-style formatting string.
-- The return value is either 'String' or @('IO' a)@.
--
-- The format string consists of ordinary characters and /conversion
-- specifications/, which specify how to format one of the arguments
-- to printf in the output string.  A conversion specification begins with the
-- character @%@, followed by one or more of the following flags:
--
-- >    -      left adjust (default is right adjust)
-- >    +      always use a sign (+ or -) for signed conversions
-- >    0      pad with zeroes rather than spaces
--
-- followed optionally by a field width:
-- 
-- >    num    field width
-- >    *      as num, but taken from argument list
--
-- followed optionally by a precision:
--
-- >    .num   precision (number of decimal places)
--
-- and finally, a format character:
--
-- >    c      character               Char, Int, Integer, ...
-- >    d      decimal                 Char, Int, Integer, ...
-- >    o      octal                   Char, Int, Integer, ...
-- >    x      hexadecimal             Char, Int, Integer, ...
-- >    X      hexadecimal             Char, Int, Integer, ...
-- >    u      unsigned decimal        Char, Int, Integer, ...
-- >    f      floating point          Float, Double
-- >    g      general format float    Float, Double
-- >    G      general format float    Float, Double
-- >    e      exponent format float   Float, Double
-- >    E      exponent format float   Float, Double
-- >    s      string                  String
--
-- Mismatch between the argument types and the format string will cause
-- an exception to be thrown at runtime.
--
-- Examples:
--
-- >   > printf "%d\n" (23::Int)
-- >   23
-- >   > printf "%s %s\n" "Hello" "World"
-- >   Hello World
-- >   > printf "%.2f\n" pi
-- >   3.14
--
printf :: (PrintfType r) => String -> r
printf fmts = spr fmts []

-- | Similar to 'printf', except that output is via the specified
-- 'Handle'.  The return type is restricted to @('IO' a)@.
hPrintf :: (HPrintfType r) => Handle -> String -> r
hPrintf hdl fmts = hspr hdl fmts []

-- |The 'PrintfType' class provides the variable argument magic for
-- 'printf'.  Its implementation is intentionally not visible from
-- this module. If you attempt to pass an argument of a type which
-- is not an instance of this class to 'printf' or 'hPrintf', then
-- the compiler will report it as a missing instance of 'PrintfArg'.
class PrintfType t where
    spr :: String -> [UPrintf] -> t

-- | The 'HPrintfType' class provides the variable argument magic for
-- 'hPrintf'.  Its implementation is intentionally not visible from
-- this module.
class HPrintfType t where
    hspr :: Handle -> String -> [UPrintf] -> t

{- not allowed in Haskell 98
instance PrintfType String where
    spr fmt args = uprintf fmt (reverse args)
-}
instance (IsChar c) => PrintfType [c] where
    spr fmts args = map fromChar (uprintf fmts (reverse args))

instance PrintfType (IO a) where
    spr fmts args = do
    putStr (uprintf fmts (reverse args))
    return undefined

instance HPrintfType (IO a) where
    hspr hdl fmts args = do
    hPutStr hdl (uprintf fmts (reverse args))
    return undefined

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmts args = \ a -> spr fmts (toUPrintf a : args)

instance (PrintfArg a, HPrintfType r) => HPrintfType (a -> r) where
    hspr hdl fmts args = \ a -> hspr hdl fmts (toUPrintf a : args)

class PrintfArg a where
    toUPrintf :: a -> UPrintf

instance PrintfArg Char where
    toUPrintf c = UChar c

{- not allowed in Haskell 98
instance PrintfArg String where
    toUPrintf s = UString s
-}
instance (IsChar c) => PrintfArg [c] where
    toUPrintf = UString . map toChar

instance PrintfArg Int where
    toUPrintf = uInteger

instance PrintfArg Int8 where
    toUPrintf = uInteger

instance PrintfArg Int16 where
    toUPrintf = uInteger

instance PrintfArg Int32 where
    toUPrintf = uInteger

instance PrintfArg Int64 where
    toUPrintf = uInteger

#ifndef __NHC__
instance PrintfArg Word where
    toUPrintf = uInteger
#endif

instance PrintfArg Word8 where
    toUPrintf = uInteger

instance PrintfArg Word16 where
    toUPrintf = uInteger

instance PrintfArg Word32 where
    toUPrintf = uInteger

instance PrintfArg Word64 where
    toUPrintf = uInteger

instance PrintfArg Integer where
    toUPrintf = UInteger 0

instance PrintfArg Float where
    toUPrintf = UFloat

instance PrintfArg Double where
    toUPrintf = UDouble

uInteger :: (Integral a, Bounded a) => a -> UPrintf
uInteger x = UInteger (toInteger $ minBound `asTypeOf` x) (toInteger x)

class IsChar c where
    toChar :: c -> Char
    fromChar :: Char -> c

instance IsChar Char where
    toChar c = c
    fromChar c = c

-------------------

data UPrintf = UChar Char | UString String | UInteger Integer Integer | UFloat Float | UDouble Double

uprintf :: String -> [UPrintf] -> String
uprintf ""       []       = ""
uprintf ""       (_:_)    = fmterr
uprintf ('%':'%':cs) us   = '%':uprintf cs us
uprintf ('%':_)  []       = argerr
uprintf ('%':cs) us@(_:_) = fmt cs us
uprintf (c:cs)   us       = c:uprintf cs us

fmt :: String -> [UPrintf] -> String
fmt cs us =
    let (width, prec, ladj, zero, plus, cs', us') = getSpecs False False False cs us
        adjust (pre, str) = 
        let lstr = length str
            lpre = length pre
            fill = if lstr+lpre < width then take (width-(lstr+lpre)) (repeat (if zero then '0' else ' ')) else ""
        in  if ladj then pre ++ str ++ fill else if zero then pre ++ fill ++ str else fill ++ pre ++ str
            adjust' ("", str) | plus = adjust ("+", str)
            adjust' ps = adjust ps
        in
    case cs' of
    []     -> fmterr
    c:cs'' ->
        case us' of
        []     -> argerr
        u:us'' ->
        (case c of
        'c' -> adjust  ("", [toEnum (toint u)])
        'd' -> adjust' (fmti u)
        'i' -> adjust' (fmti u)
        'x' -> adjust  ("", fmtu 16 u)
        'X' -> adjust  ("", map toUpper $ fmtu 16 u)
        'o' -> adjust  ("", fmtu 8  u)
        'u' -> adjust  ("", fmtu 10 u)
        'e' -> adjust' (dfmt' c prec u)
        'E' -> adjust' (dfmt' c prec u)
        'f' -> adjust' (dfmt' c prec u)
        'g' -> adjust' (dfmt' c prec u)
        'G' -> adjust' (dfmt' c prec u)
        's' -> adjust  ("", tostr u)
        _   -> perror ("bad formatting char " ++ [c])
         ) ++ uprintf cs'' us''

fmti :: UPrintf -> (String, String)
fmti (UInteger _ i) = if i < 0 then ("-", show (-i)) else ("", show i)
fmti (UChar c)      = fmti (uInteger (fromEnum c))
fmti _            = baderr

fmtu :: Integer -> UPrintf -> String
fmtu b (UInteger l i) = itosb b (if i < 0 then -2*l + i else i)
fmtu b (UChar c)      = itosb b (toInteger (fromEnum c))
fmtu _ _              = baderr

toint :: UPrintf -> Int
toint (UInteger _ i) = fromInteger i
toint (UChar c)      = fromEnum c
toint _             = baderr

tostr :: UPrintf -> String
tostr (UString s) = s
tostr _          = baderr

itosb :: Integer -> Integer -> String
itosb b n = 
    if n < b then 
        [intToDigit $ fromInteger n]
    else
        let (q, r) = quotRem n b in
        itosb b q ++ [intToDigit $ fromInteger r]

stoi :: Int -> String -> (Int, String)
stoi a (c:cs) | isDigit c = stoi (a*10 + digitToInt c) cs
stoi a cs                 = (a, cs)

getSpecs :: Bool -> Bool -> Bool -> String -> [UPrintf] -> (Int, Int, Bool, Bool, Bool, String, [UPrintf])
getSpecs _ z s ('-':cs) us = getSpecs True z s cs us
getSpecs l z _ ('+':cs) us = getSpecs l z True cs us
getSpecs l _ s ('0':cs) us = getSpecs l True s cs us
getSpecs l z s ('*':cs) us = 
        case us of
        [] -> argerr
        nu : us' ->
        let n = toint nu
        (p, cs'', us'') =
            case cs of
                    '.':'*':r -> case us' of { [] -> argerr; pu:us''' -> (toint pu, r, us''') }
            '.':r     -> let (n', cs') = stoi 0 r in (n', cs', us')
            _         -> (-1, cs, us')
        in  (n, p, l, z, s, cs'', us'')
getSpecs l z s ('.':cs) us =
    let (p, cs') = stoi 0 cs
    in  (0, p, l, z, s, cs', us)
getSpecs l z s cs@(c:_) us | isDigit c =
    let (n, cs') = stoi 0 cs
        (p, cs'') = case cs' of
            '.':r -> stoi 0 r
            _     -> (-1, cs')
    in  (n, p, l, z, s, cs'', us)
getSpecs l z s cs       us = (0, -1, l, z, s, cs, us)

dfmt' :: Char -> Int -> UPrintf -> (String, String)
dfmt' c p (UDouble d) = dfmt c p d
dfmt' c p (UFloat f)  = dfmt c p f
dfmt' _ _ _           = baderr

dfmt :: (RealFloat a) => Char -> Int -> a -> (String, String)
dfmt c p d =
    case (if isUpper c then map toUpper else id) $
             (case toLower c of
                  'e' -> showEFloat
                  'f' -> showFFloat
                  'g' -> showGFloat
                  _   -> error "Printf.dfmt: impossible"
             )
               (if p < 0 then Nothing else Just p) d "" of
    '-':cs -> ("-", cs)
    cs     -> ("" , cs)

perror :: String -> a
perror s = error ("Printf.printf: "++s)
fmterr, argerr, baderr :: a
fmterr = perror "formatting string ended prematurely"
argerr = perror "argument list ended prematurely"
baderr = perror "bad argument"

仕様削ってもいいから、シンプルに実装しろとかなら良かったのに。
組み込みとかの厳しい環境だと、printfの関数を入れると、かなりのメモリが圧迫されるケースがあるわけで、そういう奴用に。

前に作ったやつがあるので恥ずかしながら投げてみます。
shiroさんがきっと書いてくれると思ったのでshiroさんの投稿を待った事をここに告白します。
ライブラリにntというディレクトリを作ってその下に置き、
(use nt.printf) して使います。

Gauche 0.8.12で動きます。0.8.11以前では正規表現にmatchしなかった場合に#fではなく""が返るので一部修正が必要です。
なお、104行目の (d o x X) のところを (d b o x X) にすると、%bで2進表記をするオレオレ拡張が入ります。

テストコードも以下に付けておきます。
;;; nt/test/printf.scm
(use gauche.test)

(test-start "nt.printf")
(use nt.printf)
(test-module 'nt.printf)

(test-section "escaped symbols")
(test* "\\t" "\t" (sprintf "\t"))
(test* "\\n" "\n" (sprintf "\n"))
(test* "\\\"" "\"" (sprintf "\""))
(test* "\\\\" "\\" (sprintf "\\"))
(test* "%%" "%" (sprintf "%%"))

(test-section "%d")
(test* "%d 1" "1" (sprintf "%d" 1))
(test* "%3d 1" "  1" (sprintf "%3d" 1))
(test* "%3d 1111" "1111" (sprintf "%3d" 1111))
(test* "%03d 1" "001" (sprintf "%03d" 1))
(test* "%-3d" "1  " (sprintf "%-3d" 1))
(test* "%+d 1" "+1" (sprintf "%+d" 1))
(test* "%+3d 1" " +1" (sprintf "%+3d" 1))

(test* "%d zero" "0" (sprintf "%d" 0))

(test* "%d minusvalue" "-1" (sprintf "%d" -1))
(test* "%3d minusvalue" " -1" (sprintf "%3d" -1))
;(test* "%03d minusvalue" "-01" (sprintf "%03d" -1))
(test* "%-d minusvalue" "-1" (sprintf "%-d" -1))
(test* "%+d minusvalue" "-1" (sprintf "%+d" -1))
(test* "%+3d minusvalue" " -1" (sprintf "%+3d" -1))

(test-section "%d with non-integer values")
(test* "%d float 2.71828" "2" (sprintf "%d" 2.71828))
(test* "%d rational 3/2" "1" (sprintf "%d" 3/2))
(test* "%d string \"123\"" "123" (sprintf "%d" "123"))
(test* "%d string \"abc\"" "0" (sprintf "%d" "abc"))
(test* "%d symbol '1" "1" (sprintf "%d" '1))
(test* "%d symbol 'a" "0" (sprintf "%d" 'a))

(test-section "%i %u")
(test* "%i" "999" (sprintf "%i" 999))
(test* "%u" "999" (sprintf "%u" 999))

(test-section "%x %X")
(test* "%x 15" "f" (sprintf "%x" 15))
(test* "%3x 15" "  f" (sprintf "%3x" 15))
(test* "%03x 15" "00f" (sprintf "%03x" 15))
(test* "%03x 65535" "ffff" (sprintf "%03x" 65535))
;(test* "%-3x 15" "f  " (sprintf "%-3x" 15))

(test* "%X 15" "F" (sprintf "%X" 15))
(test* "%3X 15" "  F" (sprintf "%3X" 15))
(test* "%03X 15" "00F" (sprintf "%03X" 15))
(test* "%03X 65535" "FFFF" (sprintf "%03X" 65535))
;(test* "%-3x 15" "f  " (sprintf "%-3x" 15))

(test-section "%o")
(test* "%o 9" "11" (sprintf "%o" 9))
(test* "%3o 9" " 11" (sprintf "%3o" 9))
(test* "%03o 9" "011" (sprintf "%03o" 9))
(test* "%3o 255" "377" (sprintf "%3o" 255))
(test* "%3o 511" "777" (sprintf "%3o" 511))
(test* "%3o 585" "1111" (sprintf "%3o" 585))
;(test* "%-3o" "11 " (sprintf "%-3o" 9))

;(test-section "%b") ; original feature
;(test* "%b 2" "10" (sprintf "%b" 2))
;(test* "%3b 2" " 10" (sprintf "%3b" 2))
;(test* "%03b 2" "010" (sprintf "%03b" 2))
;(test* "%3b 15" "1111" (sprintf "%3b" 15))
;;(test* "%-3b 2" "10 " (sprintf "%-3b" 3))

(test-section "%c")
(test* "%c 9" "\t" (sprintf "%c" 9))
(test* "%c 13" "\r" (sprintf "%c" 13))
(test* "%c 32" " " (sprintf "%c" #x20)) ; 32
(test* "%c 55" "7" (sprintf "%c" #x37)) ; 55
(test* "%c 69" "E" (sprintf "%c" #x45)) ; 69
(test* "%c 12354" "あ" (sprintf "%c" #x3042)) ; = 12354

(test* "%c \"abc\"" "a" (sprintf "%c" "a"))
(test* "%c \"いろは\"" "い" (sprintf "%c" "いろは"))

(test-section "%s")
(test* "%s" "\n" (sprintf "%s" "\n"))
(test* "%s" "a" (sprintf "%s" "a"))
(test* "%3s" "   " (sprintf "%3s" ""))
(test* "%3s" "  a" (sprintf "%3s" "a"))
(test* "%3s" " aa" (sprintf "%3s" "aa"))
(test* "%3s" "aaa" (sprintf "%3s" "aaa"))
(test* "%3s" "aaaa" (sprintf "%3s" "aaaa"))
(test* "%-3s" "   " (sprintf "%-3s" ""))
(test* "%-3s" "a  " (sprintf "%-3s" "a"))
(test* "%-3s" "aa " (sprintf "%-3s" "aa"))
(test* "%-3s" "aaa" (sprintf "%-3s" "aaa"))
(test* "%-3s" "aaaa" (sprintf "%-3s" "aaaa"))

(test-section "%s with non-string values")
(test* "%s integer 5" "5" (sprintf "%s" 5))
(test* "%s float 3.14" "3.14" (sprintf "%s" 3.14))
(test* "%s rational 3/2" "3/2" (sprintf "%s" 3/2))
(test* "%s symbol 'abc" "abc" (sprintf "%s" 'abc))
(test* "%s list (1 2 3)" "(1 2 3)" (sprintf "%s" '(1 2 3)))
(test* "%s empty list ()" "()" (sprintf "%s" '()))
(test* "%s dotted list (1 . 2)" "(1 . 2)" (sprintf "%s" '(1 . 2)))
(test* "%s #t" "#t" (sprintf "%s" #t))
(test* "%s #f" "#f" (sprintf "%s" #f))

(test-section "%f")
(test* "%f" "3.140000" (sprintf "%f" 3.14))
(test* "%f" "3.141593" (sprintf "%f" 3.1415926))

(test* "%f" "-3.140000" (sprintf "%f" -3.14))
(test* "%f" "-3.141593" (sprintf "%f" -3.1415926))

(test* "%.0f" "3" (sprintf "%.0f" 3.14))
(test* "%.1f" "3.1" (sprintf "%.1f" 3.14))
(test* "%.2f" "3.14" (sprintf "%.2f" 3.14))
(test* "%.3f" "3.140" (sprintf "%.3f" 3.14))
(test* "%.4f" "3.1400" (sprintf "%.4f" 3.14))

(test* "%1.0f 3.14" "3" (sprintf "%1.0f" 3.14))
(test* "%1.1f 3.14" "3.1" (sprintf "%1.1f" 3.14))
(test* "%1.2f 3.14" "3.14" (sprintf "%1.2f" 3.14))
(test* "%1.0f 3.15" "3" (sprintf "%1.0f" 3.15))
(test* "%1.1f 3.15" "3.2" (sprintf "%1.1f" 3.15))
(test* "%1.2f 3.15" "3.15" (sprintf "%1.2f" 3.15))

(test* "%2.0f" " 3" (sprintf "%2.0f" 3.14))
(test* "%2.1f" "3.1" (sprintf "%2.1f" 3.14))
(test* "%2.2f" "3.14" (sprintf "%2.2f" 3.14))
(test* "%-2.0f" "3 " (sprintf "%-2.0f" 3.14))
(test* "%-2.1f" "3.1" (sprintf "%-2.1f" 3.14))
(test* "%-2.2f" "3.14" (sprintf "%-2.2f" 3.14))

(test* "%3.0f" "  3" (sprintf "%3.0f" 3.14))
(test* "%3.1f" "3.1" (sprintf "%3.1f" 3.14))
(test* "%3.2f" "3.14" (sprintf "%3.2f" 3.14))
(test* "%3.3f" "3.140" (sprintf "%3.3f" 3.14))
(test* "%-3.0f" "3  " (sprintf "%-3.0f" 3.14))
(test* "%-3.1f" "3.1" (sprintf "%-3.1f" 3.14))
(test* "%-3.2f" "3.14" (sprintf "%-3.2f" 3.14))
(test* "%-3.3f" "3.140" (sprintf "%-3.3f" 3.14))

(test* "%4.0f" "   3" (sprintf "%4.0f" 3.14))
(test* "%4.1f" " 3.1" (sprintf "%4.1f" 3.14))
(test* "%4.2f" "3.14" (sprintf "%4.2f" 3.14))
(test* "%4.3f" "3.140" (sprintf "%4.3f" 3.14))
(test* "%4.4f" "3.1400" (sprintf "%4.4f" 3.14))
(test* "%-4.0f" "3   " (sprintf "%-4.0f" 3.14))
(test* "%-4.1f" "3.1 " (sprintf "%-4.1f" 3.14))
(test* "%-4.2f" "3.14" (sprintf "%-4.2f" 3.14))
(test* "%-4.3f" "3.140" (sprintf "%-4.3f" 3.14))
(test* "%-4.4f" "3.1400" (sprintf "%-4.4f" 3.14))

(test* "%5.0f" "    3" (sprintf "%5.0f" 3.14))
(test* "%5.1f" "  3.1" (sprintf "%5.1f" 3.14))
(test* "%5.2f" " 3.14" (sprintf "%5.2f" 3.14))
(test* "%5.3f" "3.140" (sprintf "%5.3f" 3.14))
(test* "%5.4f" "3.1400" (sprintf "%5.4f" 3.14))
(test* "%5.5f" "3.14000" (sprintf "%5.5f" 3.14))
(test* "%-5.0f" "3    " (sprintf "%-5.0f" 3.14))
(test* "%-5.1f" "3.1  " (sprintf "%-5.1f" 3.14))
(test* "%-5.2f" "3.14 " (sprintf "%-5.2f" 3.14))
(test* "%-5.3f" "3.140" (sprintf "%-5.3f" 3.14))
(test* "%-5.4f" "3.1400" (sprintf "%-5.4f" 3.14))
(test* "%-5.5f" "3.14000" (sprintf "%-5.5f" 3.14))

(test-section "%e %E")
(test* "%e 0.0000314" "3.140000e-05" (sprintf "%e" 0.0000314))
(test* "%e 0.000314" "3.140000e-04" (sprintf "%e" 0.000314))
(test* "%e 0.00314" "3.140000e-03" (sprintf "%e" 0.00314))
(test* "%e 0.0314" "3.140000e-02" (sprintf "%e" 0.0314))
(test* "%e 0.314" "3.140000e-01" (sprintf "%e" 0.314))
(test* "%e 3.14" "3.140000e+00" (sprintf "%e" 3.14))
(test* "%e 31.4" "3.140000e+01" (sprintf "%e" 31.4))
(test* "%e 314" "3.140000e+02" (sprintf "%e" 314))
(test* "%e 3140" "3.140000e+03" (sprintf "%e" 3140))
(test* "%e 31400" "3.140000e+04" (sprintf "%e" 31400))
(test* "%e 314000" "3.140000e+05" (sprintf "%e" 314000))
(test* "%.0e 31415926" "3e+07" (sprintf "%.0e" 31415926))
(test* "%.1e 31415926" "3.1e+07" (sprintf "%.1e" 31415926))
(test* "%.2e 31415926" "3.14e+07" (sprintf "%.2e" 31415926))
(test* "%.3e 31415926" "3.142e+07" (sprintf "%.3e" 31415926))
(test* "%.4e 31415926" "3.1416e+07" (sprintf "%.4e" 31415926))
(test* "%.5e 31415926" "3.14159e+07" (sprintf "%.5e" 31415926))

(test* "%E" "3.140000E+00" (sprintf "%E" 3.14))

(test-section "%g")
(test* "%g" "3.14" (sprintf "%g" 3.14))
(test* "%g" "-3.14" (sprintf "%g" -3.14))
(test* "%g" "3.1415926" (sprintf "%g" 3.1415926))

(test-end)
  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
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
;;; nt/printf.scm
(define-module nt.printf
  (export
   printf
   sprintf
   ))

(select-module nt.printf)

(define (log10 x) (/ (log x) (log 10.0)))

(define (sprintf fmt . args)
  (let ((out (open-output-string)))
    (let loop ((fmt fmt) (args args))
      (define (ret) (display fmt out) (get-output-string out))
      (let ((match (#/%(?<flag>[-+# 0]*)(?<num>[1-9][0-9]*)?(\.(?<below>[0-9]+))?(?<type>[%diouxXfeEgGbcs])/ fmt)))
        (if match
            (let* ((flags (if (match 'flag) (string->list (match 'flag)) '()))
                   (num (match 'num)) ;str/指定がなければ#f. ※0.8.11までは#fではなく""が返る
                   (below (match 'below)) ;str/指定がなければ#f. ※0.8.11までは#fではなく""が返る
                   (type (string->symbol (match 'type)))
                   (consumes-an-arg? (not (eq? '% type)))
                   (flush-left? (memq #\- flags))
                   (signed? (memq #\+ flags))
                   (zero-pad? (memq #\0 flags))
                   )
              (if (and consumes-an-arg? (null? args))
                  (ret) ;;; arguments-exhausted
                  (let ((arg (if consumes-an-arg? (car args) 'not-eaten)))
                    
                    (define (format-int-value type)
                      (let ((%value (if (integer? arg) arg (x->integer (truncate (x->number arg))))))
                        (if flush-left?
                            (let ((%fmt (string-append "~" type)))
                              (format (string-append "~" (or num "") "a") (format %fmt %value)))
                            (let ((%fmt (string-append "~" (or num "") "," (if zero-pad? "'0" "")
                                                       (if signed? "@" "") type)))
                              (format %fmt %value)))))

                    (define (format-float-value); type)
                      (let* ((%value (if (real? arg) arg (x->number arg)))
                             (%e-offset (x->integer (floor (log10 (abs %value)))))
                             (%precision (case type
                                           ((f)
;                                            (if (string=? "" below) 6 (x->integer below))) ; < 0.8.12
                                            (if below (x->integer below) 6))
                                           ((e E)
                                            (cond ((> %e-offset 0) ;;; sorry i'm using (set!)
                                                   (set! %value (/ %value (expt 10 %e-offset))))
                                                  ((< %e-offset 0)
                                                   (set! %value (* %value (expt 10 (abs %e-offset)))))
                                                  (else #t))
;                                            (if (string=? "" below) 6 (x->integer below))) ;< 0.8.12
                                            (if below (x->integer below) 6))
;                                           (else (if (string=? "" below) 12 (x->integer below))))) ;< 0.8.12
                                           (else (if below (x->integer below) 12))))
                             )
                        (let* ((%int (x->integer (if (= 0 %precision)
                                                     (round %value)
                                                     (truncate %value))))
                               (%fract (abs (- %value %int)))
                               (%rounded (x->integer (round (+ (expt 0.1 (+ %precision 1))
                                                               (* (expt 10 %precision) %fract)))))
                               (%str (if (= 0 %precision)
                                         (format "~d" %int)
                                         (format (string-append "~d.~" (format "~d" %precision) ",'0d")
                                                 %int %rounded)))
                               (%f-fmt (string-append "~" (or num "") ;"," (if zero-pad? "'0" "")
                                                      (if flush-left? "" "@") "a"))
                               (%e-fmt (string-append %f-fmt
                                                      "~a" ; [eE]
                                                      (if (< %e-offset 0) "-" "+")
                                                      "~2,'0d"))
                               )
                          (case type
                            ((f)
                             (format %f-fmt %str))
                            ((g G)
                             (format %f-fmt (regexp-replace #/0+$/ %str "")))
                            ((e E)
                             (format %e-fmt %str type (abs %e-offset))
                             )
                            ))))
                    
                    (display (match 'before) out)
                    ;; warnings
;                    (case type
;                      ((d i)
;                       (when (not (integer? arg))
;                             (print "warning: %~a requires <integer>" type) ))
;                      ((b o u X x c)
;                       (when (not (and (integer? arg) (< 0 arg)))
;                             (print "warning: %~a requires <unsigned integer>" type) ))
;                      ((f e E g G)
;                       (when (not (real? arg))
;                             (print "warning: %~a requires <real>" type)))
;;                      ((b)) ; we use %b for unsigned binary
;                      ((s)
;                       (when (not (string? arg))
;                             (format "warning: %s requires <string>")))
;                      )
                    (display (case type
                               ((d o x X) ;;  %bを2進表記に使いたい場合は (d b o x X)
                                (format-int-value (match 'type)))
                               ((i u) ; signed/unsigned decimal
                                (format-int-value "d"))
                               ((f) ;float ; [-]ddd.ddd
                                (format-float-value))
                               ((e E) ;'not-supported) ; [-]d.ddde+-dd
                                (format-float-value))
                               ((g G) ; 'not-supported) ;
                                (format-float-value))
;                               ((b) 'not-supported) ; backslash-escape seq
                               ((c) ; first-char
                                (cond ((integer? arg)
                                       (string (integer->char arg)))
                                      ((string? arg)
                                       (string-ref arg 0))
                                      (else 
                                       (string (integer->char (truncate (x->number arg))))) ))
                               ((s) ; 'str
                                (let1 %s-fmt (string-append "~"
                                                            (format "~a~a"
                                                                    (or num "")
                                                                    (if flush-left? "" "@")
                                                                    )
                                                            "a")
                                      (format %s-fmt arg)))
                               ((%) "%")) out)
                    (loop (match 'after)
                          (if consumes-an-arg? (cdr args) args))
                    )))
            (ret))))))

(define (printf fmt . args) (display (apply sprintf (cons fmt args))))

(provide "nt/printf")
;;EOF

これ。できたらお題じゃなくて、トピックに回してもらえませんかねぇ…(^_^;)。←泣きそう

あと、以前の B+-Tree、一つ前のクイズスタイルのお題のときにも感じたのですが、お題の投稿に際して出題者には、ご自身の答えの事前の登録も義務づける(公開は一定時間経過後なされる)ようにしてはどうかと思いました。実際、書くとなると(出題者が)想像していたよりたいへんじゃん…てなことにならないように。


そうですねぇ…

とりあえずこのお題はマイナス評価の激しさから見て カバレッジ計算の対象から外した方がよさそうですね。

残りの二つに関しては今のところプラス評価とマイナス評価が拮抗しているようなので、このコメントを見て「このお題も外すべき」と思った人はマイナス評価、「残すべき」と思った人はプラス評価を押すということでどうでしょう。


超手抜き実装。単に int, float, char, string を単純に変換して埋め込むだけのものです。
OCaml ではコンパイラをいじらないと、まともに使えるものはできっこないので、こんなもんで良いんです。

3 ファイルを同じディレクトリに置き、
% ocamlbuild tiny_printf.cma
などとします。

% ocaml -I _build tiny_printf.cma
# let r = Obj.repr;;               
val r : 'a -> Obj.t = <fun>
# Tiny_printf.sprintf "hoge %% %d %s %c %f" [r 10; r "fuga"; r 'C'; r 2.1];;
- : string = "hoge % 10 fuga C 2.1"

 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
/* parser.mly */
%token <string> CONV
%token <string> STR
%start expr
%type <string> expr
%%
expr:
     CONV  { $1 }
   | STR   { $1 }
;

(* lexer.mll *)
{
open Parser
exception Eof
}
let conv = ['d' 'f' 'c' 's']
rule token = parse
     ('%' (conv | '%')) as str   { CONV(str) }
   | ([^'%'] +) as str           { STR(str) }
   | eof                         { raise Eof }

(* tiny_printf.ml *)
let sprintf format_str args =
   let strbuf = Buffer.create 10 in
   let add    = Buffer.add_string strbuf in
   let lexbuf = Lexing.from_string format_str in
   let rec loop args' =
      match Parser.expr Lexer.token lexbuf with
      | "%%" -> (add "%"; loop args')
      | s when s.[0] = '%' -> begin
           let arg, rest =
              match args' with
              | x::xs -> x, xs
              | _ -> failwith "number of args mismatch!"
           in
           add begin
              match s with
              | "%%" -> "%"
              | "%d" when Obj.is_int arg ->
                   string_of_int (Obj.obj arg)
              | "%f" when Obj.tag arg = Obj.double_tag ->
                   string_of_float (Obj.obj arg)
              | "%c" when Obj.is_int arg ->
                   String.make 1 (Obj.obj arg)
              | "%s" when Obj.tag arg = Obj.string_tag ->
                   Obj.obj arg
              | _ ->
                   failwith "type mismatch!"
           end;
           loop rest
        end
      | s -> (add s; loop args')
   in
   try loop args
   with Lexer.Eof -> Buffer.contents strbuf

そうですね。printfの仕様自体が大きいので、問題の焦点がどこにあるかぼやけちゃってる感じがします。

フラグとwidth, precision等をオミットして%d, %f, %s, %cくらいに絞れば、(1)文字列のスキャン、 (2)オブジェクトから文字列への変換、(3)数も型も不定な引数リスト、あたりで課題がはっきりするので、「マルチリンガルレシピ」としてもわりと有用なものになったような気がします。


OCaml で、サポートする書式指定は #4409 の jijixi さんのと大体同じですが、ちゃんと型チェックする版。

基本は参考ページの手法を使って、でもそれだけだと printf っぽく見えないので Camlp4 を被せました。

KURO-BOX% ocaml
        Objective Caml version 3.09.2

# #load "camlp4o.cma";;
        Camlp4 Parsing version 3.09.2

# #load "pa_printf.cmo";;
# myprintf "hoge %% %d %s %c %f" 10 "fuga" 'C' 2.1;;
- : string = "hoge % 10 fuga C 2.1"
# myprintf "hoge %% %d %s %c %f" 3.14 "fuga" 'C' 2.1;;
This expression has type float but is here used with type int
 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
(* ocamlc -c -I +camlp4 -pp 'camlp4o pa_extend.cmo q_MLast.cmo' pa_printf.ml *)

let make_printf _loc format =
  let parse_format format =
    let rec lit e s = parser
    | [< ''%'; strm >] -> esc <:expr< compose $e$ (lit $str:s$) >>  "" strm
    | [< 'c; strm >] -> lit e (s  ^ String.make 1 c) strm
    | [< >] -> <:expr< compose $e$ (lit $str:s$) >>
    and esc e s = parser
    | [< ''d'; strm >] -> lit <:expr< compose $e$ int >> "" strm
    | [< ''f'; strm >] -> lit <:expr< compose $e$ float >> "" strm
    | [< ''c'; strm >] -> lit <:expr< compose $e$ char >> "" strm
    | [< ''s'; strm >] -> lit <:expr< compose $e$ str >> "" strm
    | [< ''%'; strm >] -> lit <:expr< compose $e$ (lit "%") >> "" strm
    in
    lit <:expr< fun x -> x >> "" (Stream.of_string format)
  in
  let parsed_format = parse_format format in
  <:expr<
    let compose f g x = f (g x) in
    let lit x k s = k (s ^ x) in
    let int k s x = k (s ^ string_of_int x) in
    let str k s x = k (s ^ x) in
    let float k s x = k (s ^ string_of_float x) in
    let char k s x = k (s ^ String.make 1 x) in
    let format p = p (fun s -> s) "" in
    format $parsed_format$
  >>
;;

EXTEND
  Pcaml.expr: LEVEL "expr1" [
    [ "myprintf"; format = STRING -> make_printf _loc format ]
  ];
END

「引数・返り値等の仕様はできるだけ似せればよい」っていうのは仕様の適当なサブセットを決めるのが投稿者に任されてるってことじゃないですかね。「できるだけ」っていう言葉の解釈の問題かな?


そんなに神経質にならなくてもという気がします。もちろん言語のカバレッジや一番を目指すというのはモチベーションとしてはいいと思います。でもコンテストで何かの優劣をつけるのが目的ではないと思うのですがどうでしょう。いろいろな言語で同じお題を解いてみることで、一人ではとても収集できない対訳集のようなものが形成できるのが楽しみであってもいいと思っています。 もちろん質のよいコードを集められる良問であればそれは素晴らしいことなので大いに評価すればよいし、残念ながらそうでない場合はそのような評価になってしまうのはしょうがないです。それでもネタが出ないよりもいいと思います。


出題した者です。
確かにここに出すには適当でない問題でした。軽率でした。
申し訳ありません。

このお題の処理は管理人さんの判断に任せます。
問題の質に対する対応を考える機会にしてくだされば幸いです。

(関連リンクのところにJavaScriptで実装していて感動したものを載せておきます・・・)

 #4418は/.的に『参考になる』か『興味深い』でモデレートしたいところ。

モデレートの種類が無いとモデレートしにくいなぁ。
コメントそのものが優れているわけじゃなくて、コメントの先が優れているので。

問題を「作法」とか「思考」などのラベルで分類できるようになればよいのでしょうか.

・doukaku.orgが目指す「他の言語でどう書くの?」的データを増やすための,一つ一つの答えはルーチンワークっぽいけど的確なものが要求される問題,

・もう一つの「コロシアム」的側面としてのプログラマのアイデアやセンスも必要とされる問題,

など.前者だけだとつまんないなーという気はします.

今回のクイズについては(出題者としてどうなるかと思いつつ),

・問題自体は簡単で紙と鉛筆でも答えの一つは見つけられる,

・小さい部分では総当たりでもそこそこ簡単に解が求まり,

・1桁絞りはそんなに難しくなく,その場合は,実はその一つ前の自然数の分割が伏線だったりする(ruckerさんご指摘),

・やってるうちに法則性が見えて効率のよい枝刈りをして高速な答えが,

・最終的には完全に答えを出せる,

あたりで全体としてはよい流れだったなあと思っているのですが,ご不満な方も多いようで難しいですね.


nobsunの意見に大賛成です。野球にたとえると、一つのゲームの中でピッチャーが「ストライクゾーンに入らない球」を投げることはよくあることですし、逆にピッチャーがストライクゾーンを外すのをおそれてど真ん中ばかり投げたのではゲームになりません。

yappyさんにはかわいそうなことになってしまいましたけど、今回投げた球はストライクゾーンよりちょっと「めんどくさい度高い側」にそれてしまったようです。でもそれに関してyappyさんを責める人がいるとしたらそれは間違いです。こういう処理が得意な言語があるかもしれないですから。実際下の方のOCamlでの解答(http://ja.doukaku.org/comment/4412/)なんか想像以上に短いと思いました。 なのでこのお題がストライクなのかボールなのかはすごく判断の難しいところかなと思います。かなり悩んでいます。

一方、ボール球はトピックに移すべきかと思います。これは単純な理由で「言語詳細ページの『未解決問題』に表示して注目を集めつづける対象として適切か?」の答えがNoだと思うからなんです。投稿者を責めたり投稿を削除したりはしないんですが、初めてこのサイトに来た人が一番最初にたどり着くお題は自分の一番得意な言語の未解決問題だと思うんです。その最初に見る問題がマイナス評価のたくさん付いているお題なのはよくないかな、と。


ちょっと前に作ったのがあるので投稿します。template haskellのサンプルほぼそのままです。

> ghci -fth Print.hs

> print ($(printf "%d %d %d %x %o") 1 2 3 10 10)

"1 2 3 a 12"

> print ($(printf "Hello, %s") "World!")

"Hello, World!"

 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
import qualified Numeric as N
import Language.Haskell.TH
import Text.ParserCombinators.Parsec

data Format = S | D | X | O | L String deriving Show

run s = case parse myParser "print" s of
            Left err -> error $ show err
            Right x  -> x

myParser = do
  x <- lineParser <|> return (L "")
  case x of
    L ""      -> return []
    otherwise -> do y <- myParser; return (x:y)

lineParser = (try (char '%' >> choice [s, d, x, o, l]))
             <|> (try (many1 (noneOf "%")) >>= \x -> return (L x))
  where s = char 's' >> return S
        d = char 'd' >> return D
        x = char 'x' >> return X
        o = char 'o' >> return O
        l = anyChar >>= \x -> return (L ('%':[x]))

gen :: [Format] -> ExpQ -> ExpQ
gen []        x = x
gen (D  : xs) x = [|\n -> $(gen xs [|$x ++ show n|])|]
gen (X  : xs) x = [|\n -> $(gen xs [|$x ++ toHex n|])|]
gen (O  : xs) x = [|\n -> $(gen xs [|$x ++ toOct n|])|]
gen (S  : xs) x = [|\s -> $(gen xs [|$x ++ s|])|]
gen (L s: xs) x = gen xs [|$x ++ $(stringE s)|] 

toHex n = N.showHex n ""
toOct n = N.showOct n ""

printf :: String -> ExpQ
printf s = gen (run s) [|""|]

Squeak Smalltalk で。
 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
| printf |
printf := [:args |
    | formStrm valsStrm outStrm |
    formStrm := args first readStream.
    valsStrm := args allButFirst readStream.
    outStrm := String new writeStream.
    [formStrm atEnd] whileFalse: [
        | next width print scale format |
        outStrm nextPutAll: (formStrm upTo: $%).
        next := formStrm next.
        width := scale := 0.
        next ifNotNil: [
            next isDigit ifTrue: [
                formStrm back.
                width := Integer readFrom: formStrm.
                next := formStrm next].
            next = $. ifTrue: [
                scale := Integer readFrom: formStrm.
                next := formStrm next]].
        print := [:val |
            | str |
            str := val asString.
            width := width max: str size.
            outStrm nextPutAll: (str forceTo: width paddingStartWith: $ )].
        format := [:float |
            | str idx |
            str := (scale > 0 ifTrue: [float asScaledDecimal: scale] ifFalse: [float]) asString.
            (idx := str indexOf: $s) > 0 ifTrue: [str := str first: idx - 1].
            str].
        next caseOf: {
            [$%] -> [outStrm nextPut: $%].
            [$d] -> [print value: valsStrm next asInteger].
            [$f] -> [print value: (format value: valsStrm next asFloat)].
            [$o] -> [print value: (valsStrm next radix: 16)].
            [$x] -> [print value: (valsStrm next radix: 8)].
            [$c] -> [outStrm nextPut: valsStrm next asCharacter].
            [$s] -> [outStrm nextPutAll: valsStrm next asString]} otherwise: []].
    outStrm contents].

printf value: {'hoge %6.2f %6d %o %x %c %s %% fuga'. 1.234. 1234. 1234. 1234. 'a'. 'abc'}
"=> 'hoge   1.23   1234 4D2 2322 a abc % fuga' "

反則にしか見えないが、これでも注意事項の

  • 標準でついているprintf系関数の使用禁止
  • 標準でついているライブラリ以外の使用禁止

を満たしている点に注目。

Dan the Perl Monger

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/local/bin/perl
use strict;
use warnings;
sub mysprintf{
    my $cmd = join(' ', 'printf', @_);
    return `$cmd`
}

if (__FILE__ eq $0){
    print mysprintf("%.6g", exp(1));
}
__END__

%%, %d, %f, %sに対応。フラグ文字はつけても無視されます。(glib拡張にある'I'など[A-Za-z]にマッチするフラグがあった場合、結果がおかしくなるかエラーになります)

14行目、int(arg.next()).__str__()でなくstr(int(arg.next()))とすると
    elif fmt[-1] == "d": return str(int(arg.next()))
TypeError: 'str' object is not callable
のようなエラーが出たので__str__()を使いました。
 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
import re

def sprintf(str, *args):
  def getPercent():
    index = str.find("%")
    while index != -1:
      yield index
      t = str[index+2:].find("%")
      if t == -1: raise StopIteration
      index += t + 2

  def parseFormat(fmt, arg):
    if fmt[-1] == "%": return "%"
    elif fmt[-1] == "d": return int(arg.next()).__str__()
    elif fmt[-1] == "s": return arg.next().__str__()
    elif fmt[-1] == "f": return float(arg.next()).__str__()
    else: return "__Error or Unsupported__"

  s = ""
  arg = (i for i in args)
  index = 0
  for i in getPercent():
    s += str[index:i]
    fmt = re.match("%.*?[A-Za-z%]", str[i:]).group()
    s += parseFormat(fmt, arg)
    index = i + len(fmt)
  s += str[index:]
  return s

print sprintf("%d, %s%d(%s)", 2007, "Nov.", 27, "Tue") # => 2007, Nov.27(Tue)
print sprintf("%d/%d = %f%%", 12, 34, 12.0/34*100) # => 12/34 = 35.2941176471%

マクロ、クロージャ、高階関数を使って。わりと Lisp らしく書けたかも?

d,x,o,f,c,s,% に対応。フラグなどは実装してないし手抜き感はありますが。

 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
(defconstant *format-char-table* (make-hash-table))
(defmacro define-format-char (char lambda-list &body body)
  `(setf (gethash ,char *format-char-table*)
         (lambda ,lambda-list ,@body)))

(defun process-format-char (char args stream)
  (let ((fn (gethash char *format-char-table*)))
    (if fn (funcall fn args stream)
      (error "Undefined format character: ~C" char))))

(defun sprintf (fmt &rest args)
  (let ((rest args))
    (flet ((getarg (&optional n) (if n (nth n args) (pop rest))))
      (with-output-to-string (out)
        (with-input-from-string (in fmt)
          (do () ((null (peek-char nil in nil)))
            (let ((c (read-char in)))
              (if (char= c #\%)
                  (process-format-char (read-char in) #'getarg out)
                (write-char c out)))))))))

(define-format-char #\f (fn stream) (write (funcall fn) :stream stream))
(define-format-char #\c (fn stream) (write-char (funcall fn) stream))
(define-format-char #\s (fn stream) (princ (funcall fn) stream))
(define-format-char #\% (fn stream) (write-char #\% stream))

(defun format-integer (n base stream)
  (write n :base base :stream stream))

(defmacro define-integer-format-char (char base)
  `(define-format-char ,char (fn stream)
     (format-integer (funcall fn) ,base stream)))

(define-integer-format-char #\d 10)
(define-integer-format-char #\x 16)
(define-integer-format-char #\o 8)

;;; test
(sprintf (sprintf "%s/%%%c" "%x/%d/%o" #\f)
         11259375 (* 9 3607 3803) 2054353 3.141592653589793d0)
;; => "ABCDEF/123456789/7654321/3.141592653589793d0"

多分、printfのオリジナルの仕様の5分の1とかくらいなんじゃないかと思うんですが・・・

全部やってたらキリが無いので、 一旦アップします。 もし、「ここの実装だけは行って欲しい」と言う仕様があったら、コメント下さい。 追加で作ってみます。

 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
//WScript.Echo( printf("%5x%x", 1234, 1234 ) );
//WScript.Echo( printf("%5d%d", "F", "F" ) );
//WScript.Echo( printf("%5o%o", 1234, 1234 ) );
//WScript.Echo( printf("%%%%", 1234, 1234 ) );

function printf(){
  var re = RegExp, arg = arguments, s = arg[0];
  var _ = {
    'd' : function(a,$){return RPAD( eval("0x" + a).toString(10) ,$ );} ,  //10進
    'o' : function(a,$){return RPAD( eval( a.toString() ).toString(8)  ,$ );} ,  //8進
    'x' : function(a,$){return RPAD( eval( a.toString() ).toString(16) ,$ );} ,  //16進(小文字)
    '%' : function(a,$){return '%';}
  };
  var mk = [];
  for(var key in _) mk.push(key);
  var r = "%(\\d\*)(" + mk.join("|") + ")";
  for(var i = 1; i < arg.length; i++) {
    if( !s.match(r) ) continue;
    var x = re.$1 || 0, y = re.$2;
    s = s.replace( re(r), _[y]( arg[i] + "", x ) );
  }
  return s;
  function RPAD( s, len ){ while( s.length < (len - 0) ) s += ' ';    return s; }
  function LPAD( s, len ){ while( s.length < (len - 0) ) s = ' ' + s; return s; }
}

要求仕様どころか、バグってましたね。 申し訳無いです。 修正します。

っていうか、

 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
//WScript.Echo( printf("%5x%x", 1234, 1234 ) );
//WScript.Echo( printf("%5d%d", "F", 10 ) );
//WScript.Echo( printf("%5o%o", 1234, 1234 ) );
//WScript.Echo( printf("%%%%", 1234, 1234 ) );

function printf(){
  var re = RegExp, arg = arguments, s = arg[0];
  var _ = {
    'd' : function(a,$){   //10進
      if( a.match(/[a-fA-F]/) ) return RPAD(eval( "0x" + a.toString() ).toString(10) ,$ );
      else                      return RPAD(eval( a.toString() ).toString(10) ,$ );
    } ,
    'o' : function(a,$){return RPAD( eval( a.toString() ).toString(8)  ,$ );} ,  //8進
    'x' : function(a,$){return RPAD( eval( a.toString() ).toString(16) ,$ );} ,  //16進(小文字)
    '%' : function(a,$){return '%';}
  };
  var mk = [];
  for(var key in _) mk.push(key);
  var r = "%(\\d\*)(" + mk.join("|") + ")";
  for(var i = 1; i < arg.length; i++) {
    if( !s.match(r) ) continue;
    var x = re.$1 || 0, y = re.$2;
    s = s.replace( re(r), _[y]( arg[i] + "", x ) );
  }
  return s;
  function RPAD( s, len ){ while( s.length < (len - 0) ) s += ' ';    return s; }
  function LPAD( s, len ){ while( s.length < (len - 0) ) s = ' ' + s; return s; }
}

Ruby の sprintf を参考にしました。あまりテストしてないのでバグだらけの可能性があります。
 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
function sprintf(format){
  var args = arguments, x = 1;
  function addZero(s, p){ return ((p -= s.length) > 0) ? Array(p + 1).join(0) + s : s }
  return String(format).replace(
    /%(?:(\d+)\$)?([-+ #0]*)(\d*|\*(?:(\d+)\$)?)(?:\.(\d+|\*(?:(\d+)\$)?))?([diubBoOxXeEfFgGaAcsp%])/g,
    function($, pos_s, flag, width, pos_w, prec, pos_p, type){
      if(type === '%') return '%';
      if(~width.indexOf('*')) width = pos_w > 0 ? args[pos_w] : args[x++];
      prec = prec === undefined ? -1
        : ~prec.indexOf('*') ? (pos_p > 0 ? args[pos_p] : args[x++]) : +prec;
      var r = '', a = pos_s > 0 ? args[pos_s] : args[x++], sharp = ~flag.indexOf('#');
      var sign = a < 0 && (a *= -1) ? '-' : ~flag.indexOf('+') ? '+' : ~flag.indexOf(' ')?' ':'';
      switch(type){
       case'd': case'i': r = !r && prec == 0 ? '' : sign + addZero(''+ (a | 0), prec); break;
       case'u':
        a = sign == '-' ? (sign = '', 0x100000000 - (a | 0)) : a | 0;
        r = sign + addZero(''+ a, prec); break;
       case'b': case'B': r = sign + (sharp ? '0b' : '') + addZero((+a).toString(2),  prec); break;
       case'o': case'O': r = sign + (sharp ? '0'  : '') + addZero((+a).toString(8),  prec); break;
       case'x': case'X': r = sign + (sharp ? '0x' : '') + addZero((+a).toString(16), prec); break;
       case'f': case'F':
        r = sign + (+a).toFixed(~prec ? prec : 6);
        if(!prec && sharp) r += '.'; break;
       case'e': case'E':
        r = sign + (+a).toExponential(~prec ? prec : 6);
        if(!prec && sharp) r = r.replace(/e/, '.e'); break;
       case'g': case'G':
        r = sign + (+a).toPrecision(~prec ? prec : 6);
        if(!sharp) r = r.replace(/\.?0+(?=e|$)/, '');
        break;
       case'a': case'A':
        r = sign + (+a).toString(16);
        if(~prec) r = r.replace(/([^.]+)\.?([^\(]*)(.*)/, function(i,d,e){
          return i + (prec ? '.'+ (d +'0000000000000').substr(0, prec) + e : sharp?'.':'') });
       case'c': r = String.fromCharCode(a | 0); break;
       case'p': if(typeof uneval == 'function') a = uneval(a); // fallthrough
       case's': r += ~prec ? a.substr(0, prec) : a; break;
      }
      if(/[BXEGA]/.test(type)) r = r.toUpperCase();
      if((width -= r.length) > 0){
        var t = Array(width + 1).join(~flag.indexOf(0) && /[^cs]/.test(type) ? 0 : ' ');
        r = ~flag.indexOf('-') ? r + t : t + r;
      }
      return r;
    });
}

Mac OS X (PowerPC 32bit) アセンブリで、大雑把ですが。%%, %s, %d, %fのみ実装しています。精度指定はできません。

浮動小数点数の命令がよくわからず、小数点を出力するのに「10倍した浮動小数点数の一の位を出力する」ことを精度分やってます。

可変長引数はコンパイラ依存なので、次の専用の型を想定することにしました。

typedef union dk_va {
  char *sval;
  int ival;
  float fval;
} dk_va, *dk_va_list_t;

int dk_sprintf(char *dest, const char *format, dk_va_list_t args);

例:

dk_va args[3];
int i;
args[0].sval = "hello";
args[1].ival = 1510;
args[2].fval = -384.148388;
i = dk_sprintf(dest, "%%, %s, %d, %f", args);
  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
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
;; ------------------------------------------------
;; mysprintf.s for Mac OS X (PowerPC 32bit)
;; % as -o dk_sprintf.o dk_sprintf.s
;; ------------------------------------------------

        .machine ppc
        .globl  _dk_sprintf

;; typedef union dk_va {
;;   char *sval;
;;   int ival;
;;   float fval;
;; } dk_va, *dk_va_list_t;
;; 
;; int dk_sprintf(char *dest, const char *format, dk_va_list_t args)

;; r3: char *dest
;; r4: const char *format
;; r5: dk_va_list_t args
;;
;; r7(buf):  dest の書き込み用コピー
;; r8(c):    スキャンする文字
;; r9(flag): 前の文字が % なら 1
_dk_sprintf:
        ;; 初期化
        mr      r7, r3
        li      r9, 0

_scan:
        ;; format から一文字読み込む
        lbz     r8, 0(r4)       ; c = *format

        ;; ディレクティブ
        cmpli   cr7, r9, 1      ; flag == 1
        beq     cr7, _scan_directive
        cmpli   cr7, r8, 37     ; c == '%'
        beq     cr7, _switch_directive
                
        ;; format から読み込んだ文字を書き込む
        stb     r8, 0(r7)       ; *buf = c
        addi    r7, r7, 1       ; buf++

        ;; 文字列の終端
        cmpli   cr7, r8, 0      ; c == 0
        beq     cr7, _dk_sprintf_return

        ;; 繰り返し
        addi    r4, r4, 1       ; format++
        b       _scan

_switch_directive:
        li      r9, 1           ; flag = 1
        addi    r4, r4, 1       ; format++
        b       _scan

_scan_directive:
        li      r9, 0           ; flag = 0

        ;; %%
        cmpli   cr7, r8, 37     ; c == '%'
        beq     cr7, _write_escape

        ;; %s   
        cmpli   cr7, r8, 115    ; c == 's'
        beq     cr7, _write_s
        
        ;; %d
        cmpli   cr7, r8, 100    ; c == 'd'
        beq     cr7, _write_d
        
        ;; %f
        cmpli   cr7, r8, 102    ; c == 'f'
        beq     cr7, _write_f
        
        ;; 未定義のディレクティブ
        li      r10, 37         ; '%'
        stb     r10, 0(r7)      ; *buf = '%'
        stb     r8, 1(r7)       ; *(buf+1) = c
        addi    r7, r7, 2       ; buf += 2
        addi    r4, r4, 1       ; format++
        b       _scan

;; %%
_write_escape:
        li      r10, 37         ; '%'
        stb     r10, 0(r7)      ; *buf = '%'
        addi    r7, r7, 1       ; buf++
        addi    r4, r4, 1       ; format++
        b       _scan

;; %s
_write_s:
        lwz     r10, 0(r5)      ; args->sval
        addi    r5, r5, 4
        addi    r4, r4, 1       ; format++

__write_s:
        lbz     r11, 0(r10)     ; s = args->sval
        cmpli   cr7, r11, 0
        beq     cr7, _scan
        stb     r11, 0(r7)      ; *buf = s
        addi    r7, r7, 1       ; buf++
        addi    r10, r10, 1     ; args->sval++
        b       __write_s

;; %d
_write_d:
        lwz     r10, 0(r5)      ; i = args->ival
        addi    r5, r5, 4
        addi    r4, r4, 1       ; format++
        mflr    r16             ; リンクレジスタをスタックに退避
        bl      _write_digit
        mtlr    r16             ; リンクレジスタをスタックから復帰
        b       _scan

;; %f
;; r17: 精度
;; f0: 元の数値
;; f1: 整数部
;; f5: 0
;; f6: 10
_write_f:
        ;; 定数の準備など
        lis     r13, hi16(zero)
        addi    r13, r13, lo16(zero)
        lfs     f5, 0(r13)      ; 0
        lis     r13, hi16(ten)
        addi    r13, r13, lo16(ten)
        lfs     f6, 0(r13)      ; 10
        li      r17, 6          ; 精度
        
        lfs     f0, 0(r5)       ; f = args->fval
        addi    r5, r5, 4
        addi    r4, r4, 1       ; format++

        ;; 整数部
        fctiwz  f1, f0          ; (int)f, ビットは整数表現になる
        stfd    f1, -8(r1)      ; スタックを通して
        lwz     r10, -4(r1)     ; 汎用レジスタに入れる
        mflr    r16
        bl      _write_digit
        mtlr    r16

        ;; 区切り
        li      r14, 46         ; '.'
        stb     r14, 0(r7)      ; *buf = '.'
        addi    r7, r7, 1       ; buf++

        fabs    f2, f0
        li      r11, 10
        
;; 小数部
_write_fpointpart:
        fmuls   f2, f2, f6
        fctiwz  f3, f2
        stfd    f3, -8(r1)
        lwz     r10, -4(r1)

        divwu   r12, r10, r11
        mullw   r12, r12, r11
        sub     r10, r10, r12
        
        addi    r10, r10, 48    ; ASCII
        stb     r10, 0(r7)
        addi    r7, r7, 1       ; buf++

        subi    r17, r17, 1
        cmpli   cr7, r17, 0
        bgt     cr7, _write_fpointpart

        b       _scan


;; 整数を10進数で書き込む
;; r10: 書き込む整数
_write_digit:
        li      r11, 25000      ; fig, 桁
        mulli   r11, r11, 4
        li      r14, 10         ; 桁の除算用
        li      r15, 0          ; 残りを数字をすべて表示するか

        ;; 負の数
        cmpi    cr7, r10, 0
        bgt     cr7, _write_each_digit
        li      r12, 45         ; '-'
        stb     r12, 0(r7)      ; *buf = '-'
        addi    r7, r7, 1       ; buf++
        mulli   r10, r10, -1    ; i = -i

;; 上の桁から順に出力する
_write_each_digit:
        divw    r12, r10, r11   ; d = i / fig
        mullw   r13, r11, r12   ; rem = i - fig * d
        sub     r10, r10, r13
        divw    r11, r11, r14   ; fig /= 10

        cmpli   cr7, r15, 0
        cmpli   cr6, r12, 0     ; d == 0
        crand   2, 30, 26       ; cr0[eq] = cr7[eq] && cr6[eq]
        beq     cr0, _write_each_digit

        li      r15, 1
        addi    r12, r12, 48    ; ASCIIコードにする
        stb     r12, 0(r7)      ; *buf = s
        addi    r7, r7, 1       ; buf++

        ;; 最後の桁
        cmpli   cr7, r11, 1     ; fig = 1
        bgt     cr7, _write_each_digit
        mr      r12, r10
        addi    r12, r12, 48    ; ASCIIコードにする
        stb     r12, 0(r7)      ; *buf = s
        addi    r7, r7, 1       ; buf++ 

        blr                     ; 終了

_dk_sprintf_return:
        sub     r3, r7, r3      ; return buf - dest
        subi    r3, r3, 1
        blr

        .data
        .align  2

zero:
        .single 0.0
        .align  2

ten:
        .single 10
        .align  2

やっつけで解いてみました。

実行結果
------------------
abcdefg
abc%defg
abc1010e\fg
%c = あ.
%f = 10.5.
%8ld =       10.
%8d =       10.
%s = abcdefg.
%08s = 0abcdefg.
%08x = 0000000a.
%08X = 0000000A.
------------------
 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PrintFormat {
    private static final Pattern PATTERN = Pattern.compile("%%|%0??[0-9]*?l??[dfcsxX]");

    public static String format(String aFormat, Object... aValues) {
        StringBuilder tBuffer = new StringBuilder();
        int tValueIndex = 0;

        Matcher tMatcher = PATTERN.matcher(aFormat);
        int tPreviousIndex = 0;

        while (tMatcher.find()) {
            tBuffer.append(aFormat, tPreviousIndex, tMatcher.start());
            tPreviousIndex = tMatcher.end();
            String tMatch = tMatcher.group();
            if (tMatch.equals("%%")) {
                tBuffer.append('%'); // エスケープ
                continue;
            }
            boolean tZeroPadding = tMatch.startsWith("%0");
            boolean tLongType = tMatch.indexOf('l') >= 0;
            int tDigitNumber = 0;
            if (Character.isDigit(tMatch.charAt(1))) {
                tDigitNumber = Integer.parseInt(tMatch.substring(tZeroPadding ? 2 : 1, tMatch.length() - (tLongType ? 2 : 1)));
            }

            Object tValue = aValues[tValueIndex++];
            if (tMatch.endsWith("c")) {
                tBuffer.append(tValue);
            } else if (tMatch.endsWith("f")) {
                tBuffer.append(padding(tValue.toString(), tDigitNumber, tZeroPadding));
            } else if (tMatch.endsWith("d")) {
                tBuffer.append(padding(tValue.toString(), tDigitNumber, tZeroPadding));
            } else if (tMatch.endsWith("s")) {
                tBuffer.append(padding(tValue.toString(), tDigitNumber, tZeroPadding));
            } else if (tMatch.endsWith("x")) {
                if (tLongType) {
                    tBuffer.append(padding(Long.toHexString((Long) tValue), tDigitNumber, tZeroPadding));
                } else {
                    tBuffer.append(padding(Integer.toHexString((Integer) tValue), tDigitNumber, tZeroPadding));
                }
            } else if (tMatch.endsWith("X")) {
                if (tLongType) {
                    tBuffer.append(padding(Long.toHexString((Long) tValue).toUpperCase(), tDigitNumber, tZeroPadding));
                } else {
                    tBuffer.append(padding(Integer.toHexString((Integer) tValue).toUpperCase(), tDigitNumber, tZeroPadding));
                }
            }
        }

        tBuffer.append(aFormat, tPreviousIndex, aFormat.length());
        return new String(tBuffer);
    }

    private static String padding(String aText, int aLength, boolean aZeroPadding) {
        if (aLength == 0) {
            return aText; // 長さ指定なし
        }

        int tDelta = aLength - aText.length();
        if (tDelta < 0) {
            return aText.substring(-tDelta); // 長すぎる
        } else if (tDelta == 0) {
            return aText; // ぴったり
        }

        StringBuilder tBuilder = new StringBuilder(aLength);
        for (int i = 0; i < tDelta; i++) {
            tBuilder.append(aZeroPadding ? '0' : ' ');
        }
        tBuilder.append(aText);

        return new String(tBuilder);
    }

    public static void main(String[] args) {
        System.out.println(format("abcdefg", new Object[] {}));
        System.out.println(format("abc%%defg", new Object[] {}));
        System.out.println(format("abc%ld%de\\fg", new Object[] { 10L, 10 }));
        System.out.println(format("%%c = %c.", new Object[] { 'あ' }));
        System.out.println(format("%%f = %f.", new Object[] { 10.5 }));
        System.out.println(format("%%8ld = %8ld.", new Object[] { 10L }));
        System.out.println(format("%%8d = %8d.", new Object[] { 10 }));
        System.out.println(format("%%s = %s.", new Object[] { "abcdefg" }));
        System.out.println(format("%%08s = %08s.", new Object[] { "abcdefg" }));
        System.out.println(format("%%08x = %08x.", new Object[] { 10 }));
        System.out.println(format("%%08X = %08X.", new Object[] { 10 }));
    }
}

Rubyのsprintfの仕様に従って実装しました. pフラグは仕様が不明瞭だったので実装しませんでした.

format系のメソッドは仕様不可ということで, 指数表示変換は自前で容易したのでバグが潜在している可能性が高いです.

 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#%[フラグ][最小フィールド幅][.精度][長さ修飾子]変換指定子
MATCH_STR = '%([\+\-\s#0\*]*)(\d*)((?:\.\d+)?)([hliztl]*)([\w%]*)'
def mysprintf(*argv)
  def check_flag(str, flag, width, precision)
    str = str.to_s
    width = width.to_i
    pos = flag.include?('-')
    sign = (/\A([\-\+])\w+\z/ =~ str or flag.include?('+'))
    zero = flag.include?('0')
    w = (width.to_i > str.size ? width-str.size : 0)
    if sign
      s = (str.to_i.abs >= 0 ? '+' : '-')
      w = (w >= 1 ? w - 1 : 0)
    else
      s = ''
    end
    num = (/\.(\d+)/ =~ precision and $1.to_i <= w and $1.to_i > 0) ? $i.to_i+1 : 0
    filler = (zero ? '0' : ' ')*(w-num) + '0'*num
    "#{pos ? '' : filler}#{s}#{str}#{pos ? filler : ''}"
  end
  def check_spec(str, spec, opt)
    def exp_expr(n) #有理数を指数表示に変換
      i = 0
      break if eval("10**#{n.abs > 1 ? '1' : '0'}*10**(i#{n.abs > 1 ? '+= 1) >' : '-= 1) <='}#{'-1*' if n < 0}n") while 1
      "#{n.quo(10**(i+1))}e#{i+1}"
    end
    def e_flag(str)
      if str.include?('e')
        if str.include('.')
          str
        else
          "#{str.split('e')[0]}.e#{str.split('e')[1]}"
        end
      else
        exp_expr(str.to_f)
      end
    end
    def g_flag(str)
      str.to_f < 10e-3 ? e_flag(str) : str.to_f.to_s
    end
    if opt
      case spec
      when 'b': "0b#{str.to_i.to_s(2)}"
      when 'o': "0#{str.to_i.oct}"
      when 'x': "0x#{str.to_i.hex}"
      when 'X': "0x#{str.to_i.hex}".upcase
      when 'f': str.include('.') ? str : "#{str}."
      when 'F': (str.include('.') ? str : "#{str}.").upcase
      when 'e': e_flag(str)
      when 'E': e_flag(str).upcase
      when 'g': g_flag(str)
      when 'G': g_flag(str).upcase
      else
      end
    else
      case spec
      when 'd': str.to_i
      when 'i': str.to_i
      when 'u': (str.to_i < 0 ? 2**32 + str.to_i : str.to_i)
      when 'o': str.to_i.oct
      when 'x': str.to_i.hex
      when 'X': str.to_i.hex.to_s.upcase
      when 'e': e_flag(str)
      when 'E': e_flag(str).upcase
      when 'f': str.to_f
      when 'F': str.to_f.to_s.upcase
      when 'g': g_flag(str)
      when 'G': g_flag(str).upcase
      when 'c': str[0].chr
      when 's': str.to_s
      when '%': '%'
      else
        raise ArgumentError.new("malformed format string - #{spec}")
      end
    end
  end
  f = argv[0].match(/\A#{MATCH_STR}\z/)[1..-1]
  if f[0].include?('*')
    str = argv[2].to_s
    width = argv[1]
  else
    str = argv[1].to_s
    width = f[1]
  end
  if /\A[di]\z/ =~ f[4] and str.to_f != str.to_i
    raise ArgumentError.new("invalid value for Integer: #{str}")
  end
  flag = f[0]
  prec = f[2]
  spec = f[4]
  opt = f[0].include?('#')
  check_flag(check_spec(str, spec, opt), flag, width, prec)
end
p mysprintf("%%", "testtest") # => "%"
p sprintf("%%", "testtest")   # => "%"
p mysprintf("%5.0u", '-1')    # => "4294967295"
p sprintf("%5.0u", '-1')      # => "4294967295"
p mysprintf("% 2d", "1", '2') # => " 1"
p sprintf("% 2d", "1", '2')   # => " 1"

まじめに実装するのは面倒だし、クックブックに向かないので #4410に書かれていたように、仕様を限定して(%d %f %s %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
static string PrintF(string format, params object[] args)
{
  Regex re = new Regex(@"%[dfsc%]");
  int i = 0;
  return re.Replace(format, delegate(Match m)
  {
    if (args.Length <= i)
      return m.Value;
    object o = args[i++];
    string r = null;
    switch (m.Value[1])
    {
      case 'd':
        r = Convert.ToDecimal(o).ToString();
        break;
      case 'f':
        r = Convert.ToDecimal(o).ToString();
        break;
      case 's':
        r = (o == null) ? "" : o.ToString();
        break;
      case 'c':
        if (o.GetType().IsValueType)
        {
          r = Convert.ToString((char)o);
        }
        else
        {
          string s = (o == null) ? "" : o.ToString();
          r = s.Length > 0 ? new String(s[0], 1) : "";
        }
        break;
      default:
        r = m.Value;
        break;
    }
    return r;
  });
}

超簡易板です。

  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
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*-
 * The MIT License
 * 
 * Copyright (c) 2008 虹原いんく
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdio.h>
#include <stdarg.h>

char *itoa( int num ,char* buf, const int step)
{
    const char table[] = "0123456789abcdef";
    char *p = buf;
    int tmp;

    if( num < 0)
    {
        *p++ = '-';
        num = - num;
    }

    for(tmp=num;tmp>0;tmp /= step) p++;
    *p='\0';
    for(tmp=num;tmp>0;tmp /= step) *--p=table[tmp%step];

    return buf;
}

int mysprintf(char *str, const char *format, ... )
{
    char buf[64];
    char *s, *b;
    const char *fp, *np;
    
    char *cp;
    int  num;
    
    va_list va;
    va_start(va, format);

    fp= format;
    s= str;
    b= str;
    for(; *fp!='\0';) {
        if( *fp != '%' ) {
            *s++ = *fp++;
        }
        else {
            *fp++; /* skip % */
            switch((int)(unsigned char) *fp)
            {
            case '%':
                *s++ = *fp++;
                continue;
            break;
            case 's':
                np = (char *)va_arg( va, char * );
            break;
            case 'b':
                num = (int)va_arg( va, int );
                np = itoa( num , buf, 2 );
            break;
            case 'o':
                num = (int)va_arg( va, int );
                np = itoa( num , buf, 8 );
            break;
            case 'x':
                num = (int)va_arg( va, int );
                np = itoa( num , buf, 16 );
            break;
            case 'd':
                num = (int)va_arg( va, int );
                np = itoa( num , buf, 10 );
            break;
            }
            *fp++; /* skip %V */
            for(; *np!='\0';*s++ = *np++);
        }
    }
    *s = '\0';
    va_end(va);
    
    return (s - str);
}

/* ------------------------------------------------------------------ */
int main()
{
    char str[256];
    int ret;
    
    ret = mysprintf(str, "%s %d%%%s%%", "ABCD", -12345678, "EFGHIJK" );
    printf("len = %d [%s]", ret, str );
    
    return 0;
}

Javaのprintf系APIは使わずに自作。%dと%sに対応。フォーマット後の文字列を返すようにしています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def mysprintf(fmt, Object...arglist) {
  def n = 0
  fmt.replaceAll (/%(-?[0-9]*)([sd])/) {m0,m1,m2 ->
    def value = arglist[n++].toString()
    if (m1.size() != 0) {
      def w = Integer.parseInt(m1)
      if (w < 0) {
        value = value.padRight(-w)
      }
      else if (w > 0) {
        value = value.padLeft(w)
      }
    }
    return value
  }
}

assert mysprintf("AAA%10sBBB", "abc") == "AAA       abcBBB"
assert mysprintf("AAA%-10sBBB", "abc") == "AAAabc       BBB"
assert mysprintf("AAA%10dBBB", 10) == "AAA        10BBB"
assert mysprintf("AAA%-10dBBB", 10) == "AAA10        BBB"
assert mysprintf("%s,%d", "a", 1) == "a,1"

Index

Feed

Other

Link

Pathtraq

loading...