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\$
  • \$\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 '14 at 12:03
  • \$\begingroup\$ %c displays the ASCII value of a passed-in int IIRC \$\endgroup\$ – marinus Jan 3 '14 at 12:04
  • \$\begingroup\$ It prints the character, so 97 and 'a' would both become a on the output. \$\endgroup\$ – nyuszika7h Jan 3 '14 at 12:15
  • \$\begingroup\$ no need to support some form like %-02d right? just that three %c, %s, %d? \$\endgroup\$ – YOU Jan 3 '14 at 12:20
  • \$\begingroup\$ @YOU Correct. That's enough. \$\endgroup\$ – nyuszika7h Jan 3 '14 at 12:22
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\$
  • \$\begingroup\$ It's always fun to see esoteric languages that look like gibberish. ;) \$\endgroup\$ – nyuszika7h Jan 3 '14 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 '14 at 12:54
  • \$\begingroup\$ I see, interesting. \$\endgroup\$ – nyuszika7h Jan 3 '14 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\$ – Konrad Borowski Jan 3 '14 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\$ – Jerry Jeremiah Dec 23 '15 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\$
  • \$\begingroup\$ You can supply an anonymous function, so drop the leading f \$\endgroup\$ – cat Mar 21 '16 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 '16 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\$
  • \$\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 '14 at 13:34
  • \$\begingroup\$ @manatwork - Try here \$\endgroup\$ – Egor Skriptunoff Jan 3 '14 at 13:42
  • \$\begingroup\$ Yepp, works there. \$\endgroup\$ – manatwork Jan 3 '14 at 13:53
  • \$\begingroup\$ Works for me on Lua 5.2.3. \$\endgroup\$ – nyuszika7h Jan 3 '14 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\$
  • \$\begingroup\$ This doesn't compile, as it doesn't have a main function. \$\endgroup\$ – nyuszika7h Jan 3 '14 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\$ – Konrad Borowski Jan 3 '14 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 '14 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\$

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.