9
\$\begingroup\$

Challenge

Write a function that implements C printf-style string formatting.

Rules

  1. You must implement at least %%, %c, %s, %d and %f.
  2. You must not use a built-in string formatting method.
  3. You must not run external programs or connect to the Internet from your program.
  4. It's up to you to decide how to handle invalid input, but your program must not terminate abnormally.
  5. You should write a variadic function if possible.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

\$\endgroup\$
9
  • \$\begingroup\$ What does %c do? Pretty sure %s, %d and %f are for strings, ints and floats respectivelly, but unsure about %c. \$\endgroup\$
    – Sumurai8
    Jan 3, 2014 at 12:03
  • \$\begingroup\$ %c displays the ASCII value of a passed-in int IIRC \$\endgroup\$
    – marinus
    Jan 3, 2014 at 12:04
  • \$\begingroup\$ It prints the character, so 97 and 'a' would both become a on the output. \$\endgroup\$
    – nyuszika7h
    Jan 3, 2014 at 12:15
  • \$\begingroup\$ no need to support some form like %-02d right? just that three %c, %s, %d? \$\endgroup\$
    – YOU
    Jan 3, 2014 at 12:20
  • \$\begingroup\$ @YOU Correct. That's enough. \$\endgroup\$
    – nyuszika7h
    Jan 3, 2014 at 12:22

5 Answers 5

4
\$\begingroup\$

APL (73)

{⊃,/,⌿↑(⊂2∘↓¨Z⊂G),⊂{'c'0≡⍵,∊⊃⍺:⎕UCS⍺⋄⍕⍺}/⍵,⍪⌷∘G¨1↓1+(Z←G='%')/⍳⍴G←'%!',⍺}

Some tests:

      'a:%c b:%s c:%d'{⊃,/,⌿↑(⊂2∘↓¨Z⊂G),⊂{'c'0≡⍵,∊⊃⍺:⎕UCS⍺⋄⍕⍺}/⍵,⍪⌷∘G¨1↓1+(Z←G='%')/⍳⍴G←'%!',⍺} 65 'foo' 67
a:A b:foo c:67 

      printf←{⊃,/,⌿↑(⊂2∘↓¨Z⊂G),⊂{'c'0≡⍵,∊⊃⍺:⎕UCS⍺⋄⍕⍺}/⍵,⍪⌷∘G¨1↓1+(Z←G='%')/⍳⍴G←'%!',⍺}
      '1:%s 2:%s 3:%d 4:%c 5:%c' printf 'foo' 'bar' 100 110 'z'
1:foo 2:bar 3:100 4:n 5:z   
      'The %s brown %c%c%c jumps over the %s dog.' printf 'quick' 102 111 'x' 'lazy'
The quick brown fox jumps over the lazy dog.

Explanation:

  • G←'%!',⍺: prefix a dummy specifier to the string (for easier processing)
  • (Z←G='%')/⍳⍴G: find the indices of all the % characters in the string; also store a bitmask in Z
  • ⌷∘G¨1↓1+: select all the characters next to the %s, and drop the dummy.
  • ⍵,⍪: match up each specifier with its value from the right argument.
  • {...}/: run the following function on each pair:
    • 'c'0≡⍵,∊⊃⍺: if the argument is a number and the specifier is c:
    • :⎕UCS⍺: then return the unicode value of the argument,
    • ⋄⍕⍺: otherwise, return the string representation of the argument.
  • : enclose
  • ⊂2∘↓¨Z⊂G: split the string on the %s and then remove the first two characters of each substring (this is where the dummy comes in), and enclose the result of that.
  • : make a matrix out of the two enclosed arrays, matching up each substring with the value that should follow it.
  • ,⌿: join each substring with its value.
  • ⊃,/: then join the resulting strings.
\$\endgroup\$
5
  • \$\begingroup\$ It's always fun to see esoteric languages that look like gibberish. ;) \$\endgroup\$
    – nyuszika7h
    Jan 3, 2014 at 12:53
  • 2
    \$\begingroup\$ @nyuszika7h: This is actually a serious language. It dates from the 1960s and it is still in use. It would look a bit less like gibberish if it wasn't golfed. \$\endgroup\$
    – marinus
    Jan 3, 2014 at 12:54
  • \$\begingroup\$ I see, interesting. \$\endgroup\$
    – nyuszika7h
    Jan 3, 2014 at 12:55
  • \$\begingroup\$ @nyuszika7h: Well, technically it's list oriented programming language, so you could say it's designed for code golf, especially considering it uses a special character set intended to make programs more readable, and less verbose. And it was an inspiration for J programming language and GolfScript. \$\endgroup\$ Jan 3, 2014 at 14:12
  • \$\begingroup\$ @xfix I thought LISP was the list oriented programming language? We used APL in university for real work - being able to handle arrays natively is really handy. J was designed by one of the inventers of APL as its "successor" - of course that doesn't mean that it isn't useful for code golf... \$\endgroup\$ Dec 23, 2015 at 1:24
2
\$\begingroup\$

Ruby: 102 characters

f=->s,*a{s.gsub(/%(.)/){$1==?%??%:a.shift.send({?c=>:chr,?s=>:to_s,?d=>:to_i,?f=>:to_f}[$1])rescue$&}}

Sample run:

irb(main):001:0> f=->s,*a{s.gsub(/%(.)/){$1==?%??%:a.shift.send({?c=>:chr,?s=>:to_s,?d=>:to_i,?f=>:to_f}[$1])rescue$&}}
=> #<Proc:0x96634ac@(irb):1 (lambda)>

irb(main):002:0> puts f["percent : %%\n   char : %c or %c\n string : %s or %s or %s\ndecimal : %d or %d or %d\n  float : %f or %f or %f\ninvalid : %x or %s or %d or %f", 65, 'B', 'format me', 42, Math::PI, 42, Math::PI, '2014', 42, Math::PI, '2014', 'more']
percent : %
   char : A or B
 string : format me or 42 or 3.141592653589793
decimal : 42 or 3 or 2014
  float : 42.0 or 3.141592653589793 or 2014.0
invalid : %x or  or 0 or 0.0
=> nil

Invalid format specifiers are kept in place. Format specifiers without argument value are replaced with the empty value of the given type.

\$\endgroup\$
2
  • \$\begingroup\$ You can supply an anonymous function, so drop the leading f \$\endgroup\$
    – cat
    Mar 21, 2016 at 11:14
  • \$\begingroup\$ Indeed. But as I remember, at the time of posting this, anonymous functions were not unanimously accepted. As for now neither the Lua answer was updated to anonymous function (to save the same amount of characters), I think I will not start the update campaign. \$\endgroup\$
    – manatwork
    Mar 21, 2016 at 12:26
2
\$\begingroup\$

Lua 5.2, 115 bytes

-- Function definition, 115 chars
function f(f,...)n,t=0,{...}return(f:gsub('%%(%a)',function(s)n=n+1return(({c=s.char})[s]or tostring)(t[n])end))end

-- Usage example
print(f('Happy %cew %d %s %f',78,2014,'Year!',math.pi))
-- Output: Happy New 2014 Year! 3.1415926535898
\$\endgroup\$
4
  • \$\begingroup\$ Nice one. Which version of Lua? 5.1.5 gives “malformed number near '1return'”. Small problem with “%c”, it fails on 'N' instead of 78. Or is it also just my old Lua's peculiarity? \$\endgroup\$
    – manatwork
    Jan 3, 2014 at 13:34
  • \$\begingroup\$ @manatwork - Try here \$\endgroup\$ Jan 3, 2014 at 13:42
  • \$\begingroup\$ Yepp, works there. \$\endgroup\$
    – manatwork
    Jan 3, 2014 at 13:53
  • \$\begingroup\$ Works for me on Lua 5.2.3. \$\endgroup\$
    – nyuszika7h
    Jan 3, 2014 at 14:11
1
\$\begingroup\$

C++ (281 characters)

#include<sstream>
#include<cstdarg>
#define q(x)va_arg(v,x);break;case
std::string p(char*f,...){std::ostringstream r;va_list v;va_start(v,f);while(*f)if(*f=='%')switch(++f,*f++){case's':r<<q(char*)'d':r<<q(int)'c':r<<(char)q(int)'%':r<<'%';}else r<<*f++;va_end(v);return r.str();}

I hate C++, but it seemed like a good choice (I really would go with C, if not that char* pointer requires too much effort to be actually useful). Takes char* arguments, and std::string result, but hey, that's C++, so who cares about consistency (in language that itself isn't consistent)?

\$\endgroup\$
3
  • \$\begingroup\$ This doesn't compile, as it doesn't have a main function. \$\endgroup\$
    – nyuszika7h
    Jan 3, 2014 at 14:17
  • \$\begingroup\$ @nyuszika7h: The question was about making a function, not main. But if you need sample main, try gist.github.com/xfix/8238576 (I used that one while testing this function). \$\endgroup\$ Jan 3, 2014 at 14:21
  • \$\begingroup\$ True, you can't really make a meaningful main function, adding one would just increase the character count. If I didn't want to modify the code, I could add an accompanying header file and #include it from my test program. \$\endgroup\$
    – nyuszika7h
    Jan 3, 2014 at 14:25
1
\$\begingroup\$

Java, 201 186 174 bytes

12 bytes thanks to Kevin Cruijssen

String f(String s,Object...a){String r="";for(char c,i=0,j=0;i<s.length();r+=c==37?(c=s.charAt(i++))<38?c:c==99?(char)(int)a[j++]:a[j++]:c==37?"":c)c=s.charAt(i++);return r;}

Try it online!

\$\endgroup\$
3

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.