printfの自作
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
see: Olivier Danvy (1998) "Functional Unparsing"
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で実装していて感動したものを載せておきます・・・)
see: 高度な 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!"
see: template haskell
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) [|""|]
|
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; }
}
|
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"
|






yappy
#4119()
[
C
]
Rating-2/20=-0.10
Rating-2/20=-0.10-0+
[ reply ]