Write the shortest program that takes a single integer as input and prints out a Suanpan abacus

Testcases

Input:

314159

Output:

|\======================================/|
||  (__)  (__)  (__)  (__)  (__)  (__)  ||
||  (__)  (__)  (__)  (__)   ||    ||   ||
||   ||    ||    ||    ||    ||    ||   ||
||   ||    ||    ||    ||   (__)  (__)  ||
|<======================================>|
||  (__)  (__)  (__)  (__)   ||   (__)  ||
||  (__)   ||   (__)   ||    ||   (__)  ||
||  (__)   ||   (__)   ||    ||   (__)  ||
||   ||    ||   (__)   ||    ||   (__)  ||
||   ||    ||    ||    ||    ||    ||   ||
||   ||    ||    ||    ||   (__)   ||   ||
||   ||   (__)   ||   (__)  (__)   ||   ||
||   ||   (__)   ||   (__)  (__)   ||   ||
||  (__)  (__)   ||   (__)  (__)   ||   ||
||  (__)  (__)  (__)  (__)  (__)  (__)  ||
|/======================================\|

Input:

6302715408

Output:

|\==============================================================/|
||  (__)  (__)  (__)  (__)  (__)  (__)  (__)  (__)  (__)  (__)  ||
||   ||   (__)  (__)  (__)   ||   (__)   ||   (__)  (__)   ||   ||
||   ||    ||    ||    ||    ||    ||    ||    ||    ||    ||   ||
||  (__)   ||    ||    ||   (__)   ||   (__)   ||    ||   (__)  ||
|<==============================================================>|
||  (__)  (__)   ||   (__)  (__)  (__)   ||   (__)   ||   (__)  ||
||   ||   (__)   ||   (__)  (__)   ||    ||   (__)   ||   (__)  ||
||   ||   (__)   ||    ||    ||    ||    ||   (__)   ||   (__)  ||
||   ||    ||    ||    ||    ||    ||    ||   (__)   ||    ||   ||
||   ||    ||    ||    ||    ||    ||    ||    ||    ||    ||   ||
||   ||    ||   (__)   ||    ||    ||   (__)   ||   (__)   ||   ||
||  (__)   ||   (__)   ||    ||   (__)  (__)   ||   (__)   ||   ||
||  (__)   ||   (__)  (__)  (__)  (__)  (__)   ||   (__)   ||   ||
||  (__)  (__)  (__)  (__)  (__)  (__)  (__)   ||   (__)  (__)  ||
||  (__)  (__)  (__)  (__)  (__)  (__)  (__)  (__)  (__)  (__)  ||
|/==============================================================\|
link|improve this question

60% accept rate
Can the number be given on the command-line? – Joey Mar 12 '11 at 1:04
Any restrictions on input length? – Joey Mar 12 '11 at 2:07
Similar to golf.shinh.org/p.rb?Soroban+Fixed if anyone needs some ideas on how to golf more. – Nabb Mar 12 '11 at 4:02
1  
So the top row and the bottom row are always completely filled? Why did they invent such a redundant abacus? :) – Timwi Mar 13 '11 at 16:44
@Timwi, the same abacus can be used for hexidecimal. When used for decimal the extra rows are mostly used when performing multiplications and divisions – gnibbler Mar 14 '11 at 4:44
show 1 more comment
feedback

8 Answers

up vote 9 down vote accepted

Perl (151 chars)

(168 163 158 157 156 154)

$i=<>;$c.=$_-4?"
||  $i||":"
|<$m>|",$m='==',$c=~s!\d!$m.='='x6;($_-$&%5)/5%2|(5*$_+$&)/10%7==1?' ||   ':'(__)  '!eg for 0..14;print"|\\$m/|$c
|/$m\\|"

Explanation

# Read the number from STDIN.
$i = <>;

# for statement starts here...

    # Append to $c a line containing either the horizontal dividing bar (in row #0)
    # or the current abacus row with the digits in place of the pegs.
    # This relies on the fact that $m will have been computed after at least one iteration.
    $c .= $_-4 ? "\n||  $i||" : "\n|<$m>|",

    # Notice that $m is recomputed from scratch in each iteration.
    $m = '==',

    # Substitute the correct pegs for the digit characters.
    $c =~ s!\d!
        $m .= '=' x 6;

        # Weird expression for the abacus function.
        # I have to use “% 7” because otherwise the division is floating-point...
        # Notice that $_ is the row and $& is the digit.
        ($_ - $& % 5)/5 % 2 | (5*$_ + $&)/10 % 7 == 1
        ? ' ||   '
        : '(__)  '
    !eg
for 0..14;

# Could shorten further by using “say” if you don’t mind excluding the “-E” from the count...
print "|\\$m/|$c\n|/$m\\|"

Edits

  • (154 → 151) Changed three \ns to actual newline characters. Can’t believe I didn’t think of that earlier!
link|improve this answer
feedback

Ruby 1.9, 154 characters

puts'|\%s/|'%$r=?=*(2+6*gets.size),(0..14).map{|a|a==4?"|<#$r>|":"|| #{$_.gsub(/./){(5*a+n=$&.hex)/10!=1&&(a-n%5)/5!=1?' (__) ':'  ||  '}} ||"},"|/#$r\\|"

Edits:

  • (267 -> 244) Let the lambda add the beginning/end of most lines
  • (244 -> 236) Use string interpolation, some inlining.
  • (236 -> 218) Rewrote the lambda, more inlining
  • (218 -> 216) Inlining is fun
  • (216 -> 211) Removed some unnecessary stuff
  • (211 -> 203) Rewrote the lambda so it only needs one argument
  • (203 -> 193) Merged the two enumerations
  • (193 -> 192) String interpolation is shorter than concatenation
  • (192 -> 191) Changed one == to a <
  • (191 -> 184) Changed the lambda to an inline block
  • (184 -> 183) Inlined $r
  • (183 -> 180) Changed iteration index
  • (180 -> 176) Modified the main condition
  • (176 -> 174) Use Array#join to create the spaces between each column
  • (174 -> 170) Simplified last part of the main condition
  • (170 -> 167) And simplified the other part, too. Idea based on Timwi's Perl solution, thanks!
  • (167 -> 165) Removed obsolete parentheses
  • (165 -> 163) Inline n
  • (163 -> 159) Iterate over the input's bytes directly, not via index
  • (159 -> 157) Actually don't iterate at all, but use gsub
  • (157 -> 155) Don't need l anymore
  • (155 -> 154) Replace one string interpolation with String#format
link|improve this answer
feedback

Windows PowerShell, 191

$y='='*(2+6*($i=[char[]]"$input").Count)
filter f($v){"|| $((' (__)','  || ')[($i|%{iex $_%$v})])  ||"}"|\$y/|"
f 1
f 10-ge5
f 1+1
f 10-lt5
"|<$y>|"
1..5|%{f 5-lt$_}
1..5|%{f 5-ge$_}
"|/$y\|"

History:

  • 2011-03-11 23:54 (340) Initial attempt.
  • 2011-03-12 00:21 (323) Using string interpolation throughout the code.
  • 2011-03-12 00:21 (321) Inlined $l.
  • 2011-03-12 01:07 (299) Used a function for the more repetitive parts as well as a format string.
  • 2011-03-12 01:19 (284) Changed the arguments to the function slightly. Hooray for command parsing mode.
  • 2011-03-12 01:22 (266) More variables for recurring expressions.
  • 2011-03-12 01:28 (246) Now every row is generated by the function.
  • 2011-03-12 01:34 (236) Since I use the chars only in string interpolation I can safely ignore the % that makes numbers from the digits.
  • 2011-03-12 01:34 (234) Slightly optimized the array index generation in the function.
  • 2011-03-12 01:42 (215) I no longer need $r and $b. And $a is also obsolete. As is $l.
  • 2011-03-12 01:46 (207) No need to set $OFS if I need it only once.
  • 2011-03-12 01:49 (202) Inlined $f.
  • 2011-03-12 01:57 (200) No need for the format string anymore. String interpolation works just fine.
  • 2011-03-12 02:00 (198) Slightly optimized generating the individual rows (re-ordering the pipeline and array index).
  • 2011-03-12 02:09 (192) No need for -join since we can actually use the additional space to good effect.
link|improve this answer
feedback

Delphi, 348

This version builds up a string to write just once; The digits are handled by a separate function that works via a digit modulo m >= value construct (negated if value <0).

var d,s,o,p:string;c:Char;i:Int8;function g(m,v:Int8):string;begin p:='|| ';for c in d do p:=p+Copy('  ||   (__) ',1+6*Ord(((Ord(c)+2)mod m>=Abs(v))=(v>0)),6);g:=p+' ||'^J;end;begin ReadLn(d);s:=StringOfChar('=',2+6*Length(d));for i:=1to 5do o:=g(5,6-i)+o+g(5,-i);Write('|\'+s+'/|'^J+g(1,-1)+g(10,-5)+g(1,1)+g(10,5)+'|<'+s+'>|'^J+o+'|/'+s+'\|')end.

Delphi, 565

First attempt :

var _:array[0..6]of string=('  ||  ',' (  ) ','======','|\==/|','||  ||','|/==\|','|<==>|');m:array[0..186]of Byte;o:array[0..16]of string;i,j,c,f,l:Word;begin for i:=0to 9do begin f:=i*17;m[f+1]:=1;m[f+2]:=Ord(i<5);m[f+3]:=0;m[f+4]:=Ord(i>4);for j:=6to 10do m[f+j]:=Ord(i mod 5>j-6);for j:=11to 15do m[f+j]:=Ord(i mod 5<=j-11);m[f]:=2;m[5+f]:=2;m[16+f]:=2;end;f:=170;m[f]:=3;for i:=1to 15do m[f+i]:=4;m[f+5]:=6;m[f+16]:=5;repeat for i:=0to 16do Insert(_[m[f+i]],o[i],l);Read(PChar(@c)^);c:=c-48;f:=c*17;l:=Length(o[0])-2;until c>9;for i:=0to 16do WriteLn(o[i])end.

This uses 3 arrays; one for the 7 strings that could be discerned, one for the output lines and one to map the 7 strings to 11 columns (10 digits and 1 initial column).

link|improve this answer
feedback

Haskell, 243 characters

z x|x=" (__) ";z _="  ||  "
h[a,b]d f w='|':a:replicate(2+6*length d)'='++b:"|\n"++q z++q(z.not)
 where q b=w>>=(\v->"|| "++(d>>=b.(>v).f)++" ||\n")
s d=h"\\/"d(5-)[-9,0]++h"><"d(`mod`5)[0..4]++h"/\\"d id[]
main=interact$s.map(read.(:[])).init

Not particularly clever. I'm sure it can be shortened somehow...


  • Edit: (246 -> 243) took @FUZxxl's suggestion to use interact
link|improve this answer
How about using interact – FUZxxl Mar 23 '11 at 19:33
First line can be shortend to z x|x=" (__) "|0<1=" || ". – FUZxxl Mar 23 '11 at 20:33
Your alternative first line is only shorter because you dropped two spaces that are required! – MtnViewMark Mar 24 '11 at 2:07
Oops! You're of course right. – FUZxxl Mar 24 '11 at 18:42
feedback

C, 548

#define p(a) printf(a);
#define F(x,m) for(x=0;x<m;x++)
#define I(x) {p("||")F(j,l)if(b[l*(i+x)+j]){p("  (__)")}else{p("   || ")}p("  ||\n")}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int a,char* c[]){int i,j,l,m,*b;l=strlen(c[1]);b=(int*)malloc(l*56);m=6*l;F(i,14*l)b[i]=0;
F(j,l){b[j]=1;if(c[1][j]<53){b[l+j]=1;}else{b[3*l+j]=1;c[1][j]-=5;}F(i,5){if(i<c[1][j]-'0'){
b[(i+5)*l+j]=1;}else{b[(i+9)*l+j]=1;}}}p("|\\=")F(i,m)p("=")p("=/|\n")F(i,4)I(0)p("|<=")F(i,m)
p("=")p("=>|\n")F(i,9)I(5)p("|/=")F(i,m)p("=")p("=\\|\n")}

First version, just a bit of golfing so far.

link|improve this answer
feedback

Scala (489 characters)

def a(i:String){val b=" (__) ";val n="  ||  ";1 to 17 map{l=>;{print(l match{case 1=>"|\\=";case 6=>"|<=";case 17=>"|/=";case _=>"|| "});print(l match{case 1|6|17=>"======"*i.size;case 2|16=>b*i.size;case 4|11=>n*i.size;case 3=>i flatMap{d=>{if(d.asDigit<5)b else n}};case 5=>i flatMap{d=>{if(d.asDigit>4)b else n}};case _=>i flatMap{d=>{if(l<11)if(d.asDigit%5<l-6)n else b else if(d.asDigit%5>l-12)n else b}}});;print(l match{case 1=>"=/|";case 6=>"=>|";case 17=>"=\\|";case _=>" ||"})}}}

Pretty crappy attempt really.

link|improve this answer
feedback

J, 225

Passes two tests given, should work up to at least several hundred digits.

c=:2 6$'   ||   (__)'
f=:(2{.[),('='#~2+6*#@]),2}.[
d=:'||',"1'  ||',~"1,"2&(c{~|:)
g=:('|\/|'&f,d&(1,.-.,.0,.])&(4&<),'|<>|'&f,d&(5($!.0"1)0,~"(1)1#~"0|~&5),|.&d&(5($!.0"1)1#~"0(5-5|])),'|/\|'&f)
4(1!:2)~LF,"1 g"."0}:(1!:1)3

First off: Yes, yes, gravedigging. Second: That's just embarassingly long. Oh well. I haven't decided yet whether to golf it further or curl up in fetal position and cry. (Or both!)

Here's a bit of explanation in lieu of a shorter program:

  • c is 2x6 table of empty cell, bead cell for rendering.
  • f renders an '=' row with the four outside characters as left argument.
  • d renders an abacus row by translating 0/1 matrices into bead cells padded with ||
  • g takes digits and vertically compiles character rows using f for 'formatting' rows and d for abacus rows.
  • The last row gets input, splits into characters and converts those to numbers, feeds to g and then prints.
link|improve this answer
feedback

Your Answer

 
or
required, but never shown

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