Print a Cantor Set

19

1

The Challenge

Build a N-Leveled Cantor Set.

The Cantor ternary set is created by repeatedly deleting the open middle thirds of a set of line segments.

The program receives one parameter N (a integer number) and then prints (in console or similar way) a Cantor Set of N levels. The print can only contain undescore (_) and whithe spaces characters. The parameter can be positive or negative and the sign indicates the Cantor Set construction orientation: If N > 0 the Cantor Set is constructed downwards and if N < 0 the Cantor Set is constructed upwards. If N = 0 then the program prints a single line (_).

For example:

N = 2

_________
___   ___
_ _   _ _

N = -2

_ _   _ _
___   ___
_________

N = 3

___________________________
_________         _________
___   ___         ___   ___
_ _   _ _         _ _   _ _

N = -3

_ _   _ _         _ _   _ _
___   ___         ___   ___
_________         _________
___________________________

Winning criteria

As it is a code golf challenge, the shortest code wins.

Edited: Modify 0 input by ugoren's suggestion.

Averroes

Posted 2012-01-30T09:24:03.037

Reputation: 3 771

Why print nothing when N=0? This makes 0 a special case, and makes it harder to use recursion. General handling would be to print a single _ (but print it downward when getting -0). – ugoren – 2012-01-30T09:55:06.870

Right. I have modified the specs already. – Averroes – 2012-01-30T10:11:29.650

Answers

10

GolfScript, 49 42 40 chars

~.abs.3\?'_'*\{.3%..,' '*\++}*](0>2*(%n*

With thanks to hammar for 42->40.

My best attempt yet at a more number-theoretic approach is sadly much longer:

~.abs:^3\?,{3^)?+3base(;1+1?.'_'*^@-)' '*+}%zip\0>2*(%n*

or

~.abs 3\?:^,{6^*+3base.1+1?.('_'*@,@-' '*+}%zip\0>2*(%n*

and I suspect that the length of base and zip will make it impossible to catch up.

Peter Taylor

Posted 2012-01-30T09:24:03.037

Reputation: 41 901

~.abs.@/\.3\?'_'*\{.3%..,' '*\++}*](%n* is 39 chars, but crashes on input 0. :-( – Ilmari Karonen – 2012-02-02T17:12:26.967

@IlmariKaronen, yes, division by zero was a pain for the C implementation I wrote too, because it meant you can't do n/abs(n) to get signum(n). – Peter Taylor – 2012-02-02T22:43:38.323

6

Python, 116 113 104 103 chars

n=input()
d=n>0 or-1
for i in range(n*d+1)[::d]:
 s='_'*3**i
 while i<n*d:s+=len(s)*' '+s;i+=1
 print s

Older algorithm topped out at 113 characters

r=input()
u='_'
l=[u]
for _ in abs(r)*u:o=len(l[0]);l=[s+o*' '+s for s in l]+[u*o*3]
print'\n'.join(l[::r>0 or-1])

Steven Rumbalski

Posted 2012-01-30T09:24:03.037

Reputation: 1 353

5

Ruby (97)

Based on Steven Rumbalski's python version:

n,r=$*[0].to_i,[?_]
n.abs.times{z=r[0].size;r=r.map{|s|s+' '*z+s}+[?_*z*3]}
puts n<0?r:r.reverse

Previous attempts, both same length (112)

Build lines from parts:

c=->x,n{n<1??_*x :(z=c[s=x/3,n-1])+' '*s+z}
r=(0..m=(n=$*[0].to_i).abs).map{|i|c[3**m,i]}
puts n<0?r.reverse: r

Start with one line, make holes in it:

r=[?_*3**a=(n=$*[0].to_i).abs]
a.times{|c|r<<r[-1].gsub((x=?_*o=3**(a-c-1))*3,x+' '*o+x)}
puts n<0?r.reverse: r

jsvnm

Posted 2012-01-30T09:24:03.037

Reputation: 441

3

Perl, 93 chars

@x=($t=$x=_ x 3**($a=abs($n=<>)),map$x.=$"x($x=~s/(.)../$1/g).$x,1..$a);say for$n<0?sort@x:@x

I thought I'd try to see how well Peter Taylor's GolfScript solution would port to Perl. Notable features include the use of sort instead of reverse to save three chars, using the fact that a space sorts before _.

Ilmari Karonen

Posted 2012-01-30T09:24:03.037

Reputation: 19 513

2

R, 141 139 137 bytes

m=abs(n<-scan());write("if"(n<m,rev,c)(c(" ","_")[Reduce(`%x%`,rep(list(matrix(c(1,1,1,1,0,1),3)),m),t(1))[,1+2^m-2^(m:0)]+1]),1,3^m,,"")

Try it online!

-15 bytes thanks too Giuseppe's use of '(' as the identity function; write instead of cat to print output; clever use of%x%.

-2 bytes thanks to Kirill L. by using c instead of '(' as the identity function.

JayCe

Posted 2012-01-30T09:24:03.037

Reputation: 2 655

could a Kronecker product work here? %x%? There might be some issues with taking alternating rows perhaps... – Giuseppe – 2018-05-23T19:18:55.967

@Giuseppe I tried, building on your "Create an “H” from smaller “H”s" answer... I'll give it another try. – JayCe – 2018-05-23T19:25:54.303

Ah, so it was you who upvoted that. that's the only reason I thought of kron too! I'd imagine this should be able to go down to like 125 bytes if we can find the right approach. – Giuseppe – 2018-05-23T19:27:43.977

you can use \(`` as the identity function so you can use write directly instead of cat and a for loop. 141 bytes

– Giuseppe – 2018-05-23T19:58:09.917

@Giuseppe I had no idea ( could be used this way, or that if could be used to select from two functions. And I will start using write... saves a lot of "\n". – JayCe – 2018-05-23T20:07:07.477

I think, you can also use just c instead of ( as identity for -2 bytes, like I did here

– Kirill L. – 2018-05-24T10:11:27.710

@KirillL. @Giuseppe c does work! – JayCe – 2018-05-24T12:54:30.820

Aha! 137 bytes using %x%! Note that your above answer could be 137 bytes as a full program as well.

– Giuseppe – 2018-05-24T13:55:41.903

@Giuseppe this is just wow. – JayCe – 2018-05-24T14:05:14.780

2

C, 219 193 179 143 136 131 characters

Followed another of Petyer Taylor's ideas, plus an improvement of my own, saved 6 more.
Integrated some tips from @PeterTaylor, plus copied his main function, with slight changes, which save a character (is it fair to copy it? Since neither of us will win this one, I guess it isn't too bad).
I thought of a significant improvement in how my recursion works, and after seeing Peter Taylor's answer, I implemented it to regain the lead. When reading his answer again, I saw that I did almost exactly what he did. So this seems like the hybridization he suggested.
Also simplified the loop in main, keeping the same length.
And took Peter's trick to print newline, instead of puts("") - saves a character.

Removed int from variable declaration - a warning, but saves 4 chars.
New algorithm doesn't calculate 3^x in advance, but uses a single loop to print 3^x characters.
Can save one more by defining int*v, but then 64bit won't work.
Characters count excludes whitespace (which can be removed).

o,i,n;
p(c) {
    n-- ?
        p(c),p(o>n?c:32),p(c)
    :
        putchar(c);
    n++;
}
main(c,v)int**v; {
    for(n=abs(c=atoi(v[1]));i<=n;i++)o=c+n?n-i:i,p(95),puts("");
}

Older algorithm, 219 chars:

p(l,o,i,m,c,j) {
    for(;i<(m=l);i++)
        for(j=0,c=95;m/o||!putchar(c);j++)
            i/m%3-1||(c=32),m/=3;
    puts("");
}
main(c,v,n,i,l,o)int**v;{
    (n=atoi(v[1]))<0?n=-n:(c=0);
    for(i=n,l=1;i;i--)l*=3;
    o=c?1:l;
    for (;i<=n;i++)p(l,o,0),c?o*=3:(o/=3);
}

ugoren

Posted 2012-01-30T09:24:03.037

Reputation: 16 527

@PeterTaylor, I can't remove the i parameter, because using the global would interfere with main. l-- will interfere with o>=l, and I'll have to replace it with > (so why do I write it as if it's a bad thing?) I could also copy you main, which is simpler and shorter than mine. – ugoren – 2012-02-04T20:04:33.303

@PeterTaylor, you were right about i - I missed the fact that I really no longer use it (I thought you mean I don't pass it). – ugoren – 2012-02-04T20:12:51.730

By the way, I don't mind you taking my main function. My rule of thumb is that copying someone else's solution to change a single character is overly aggressive, copying someone else's solution to rewrite half of it is perfectly fair, and there's a grey area somewhere in between. We should perhaps try to agree some community standards on meta. – Peter Taylor – 2012-02-05T13:35:40.737

@PeterTaylor, I think we reached a sort of stalemate. My p seems quite optimal now, and your main was better (I'm not sure it's optimal, but can't improve it further). So except for a new ingenious program structure, the only way to go was either of us copying the other's code. – ugoren – 2012-02-05T14:09:23.783

BTW How are you counting your characters? Because I make your latest version 138 chars, not 136. – Peter Taylor – 2012-02-05T14:38:22.267

And I can see how to save one more to 137: o,i,l;p(c){l--?p(c),p(o>l?c:32),p(c):putchar(c);l++;}main(c,v,n)int**v;{for(;i<=(l=n=abs(c=atoi(v[1])));i++)o=c+n?n-i:i,p(95),l=0,p(10);} – Peter Taylor – 2012-02-05T14:47:10.353

After joining everything to one line, wc says 137, but it counts the \n. l++ is nice - makes me wonder what the stack is for. And I thought my p wasn't the place to make progress... – ugoren – 2012-02-05T14:53:56.877

2

C (163 161 chars)

i,l,N;f(n,m,s){if(n){s=--n<l?m:s;f(n,m,s);f(n,s,s);f(n,m,s);}else
putchar(m);}main(n,v)int**v;{for(i=N=abs(n=atoi(1[v]));i+1;i--)l=n<N?N-i:i,f(N,95,32),f(0,10);}

Borrows a couple of tricks from ugoren's answer, but the core logic is quite different. I couldn't follow his for loop, so it may be possible to hybridise and save a few more.

Peter Taylor

Posted 2012-01-30T09:24:03.037

Reputation: 41 901

2

Common Lisp, 217 210 characters

(defun m(x)(flet((c(n v)(if(= n 0)`((,v))(cons(substitute v nil(make-list(expt 3 n)))(mapcar #'append(c(1- n)v)(c(1- n)" ")(c(1- n)v))))))(format t "~{~{~a~}~%~}"(let((r(c(abs x)'_)))(if(< x 1)(reverse r)r)))))

Expanded:

(defun m(x)
  (flet((c(n v)
    (if(= n 0)
       `((,v))
       (cons(substitute v nil(make-list(expt 3 n)))
            (mapcar #'append
                    (c(1- n)v)
                    (c(1- n)" ")
                    (c(1- n)v))))))
   (format t "~{~{~a~}~%~}"(let((r(c(abs x)'_)))(if(< x 1)(reverse r)r)))))

I figure if the Lisp code manages to beat any initial count for another language (C, 219) I'm doing OK :)

Paul Richter

Posted 2012-01-30T09:24:03.037

Reputation: 770

2

J, 44 39 38 37 bytes

' _'{~0&>_&(]|.)(,:1)1&(,],.0&*,.])~|

Uses iteration to build the next set starting with 1 (representing _) initially.

Usage

   f =: ' _'{~0&>_&(]|.)(,:1)1&(,],.0&*,.])~|
   f 0
_
   f 1
___
_ _
   f _1
_ _
___
   f 2
_________
___   ___
_ _   _ _
   f _2
_ _   _ _
___   ___
_________
   f 3
___________________________
_________         _________
___   ___         ___   ___
_ _   _ _         _ _   _ _
   f _3
_ _   _ _         _ _   _ _
___   ___         ___   ___
_________         _________
___________________________

Explanation

' _'{~0&>_&(]|.)(,:1)1&(,],.0&*,.])~|  Input: integer n
                                    |  Absolute value of n
                (,:1)                  The array [1]
                     1&(          )~   Repeat abs(n) times starting with x = [1]
                                 ]       Identity function, gets x
                            0&*          Multiply x by 0
                               ,.        Join the rows together
                         ]               Identity function, gets x
                          ,.             Join the rows together
                     1  ,                Prepend a row of 1's and return
      0&>                              Test if n is negative, 1 if true else 0
         _&(   )                       If n is negative
             |.                          Reverse the previous result
            ]                            Return that
                                       Else pass the previous result unmodified
' _'                                   The string ' _'
    {~                                 Select from the string using the result
                                       as indices and return

miles

Posted 2012-01-30T09:24:03.037

Reputation: 15 654

Nice! I haven't personally tried, but I love using the agenda @.--perhaps it, combined with $:, could be of some use here? E.g. something like (zero case)`(positive case)`(negative case)@.*, or even maybe ":@_:`(positive case)`(|."1@$:)@.*. – Conor O'Brien – 2016-09-19T18:47:59.150

I haven't attempted a recursive solution, but I could try it. – miles – 2016-09-19T19:20:09.710

1

JavaScript 121 bytes

Inner recursive function, then take care of backward output if needed

n=>(f=(n,t=n&&f(n-1),r=t[0])=>n?[r+r+r,...t.map(x=>x+t[n]+x)]:['_',' '],f=f(n<0?-n:n),f.pop(),n<0?f.reverse():f).join`\n`

Less golfed

n=>{
  var f = n => { // recursive function
    var t = n && f(n-1), r = t[0]
    return n 
      ? [r+r+r, ...t.map(x => x+t[n]+x)]
      : ['_',' ']
  };
  f = f(n < 0 ? -n : n);
  f.pop(); // last row is all blanks
  if (n<0) f.reverse();
  return f.join`\n`
}

Test

var F=
n=>(f=(n,t=n&&f(n-1),r=t[0])=>n?[r+r+r,...t.map(x=>x+t[n]+x)]:['_',' '],f=f(n<0?-n:n),f.pop(),n<0?f.reverse():f).join`\n`

function go()
{
  var n=+I.value
  O.textContent = F(n)
}

go()
<input id=I type=number value=3 oninput='go()'>
<pre id=O></pre>

edc65

Posted 2012-01-30T09:24:03.037

Reputation: 31 086

1

Python, 177 164 characters

N=input()
n=abs(N)
c=lambda x:0if x<1 else x%3==1or c(x/3)
r=["".join([["_"," "][c(x/3**i)]for x in range(3**n)])for i in range(n+1)]
print"\n".join(r[::N>0 or-1])

Ante

Posted 2012-01-30T09:24:03.037

Reputation: 321

Since you are using Python 2 you don't need to cast the results of input as int. Your last two lines could be shortened to print"\n".join(r[::N>0 or-1]) – Steven Rumbalski – 2012-02-01T15:39:51.250

@Steven I made changes. Thank you. – Ante – 2012-02-01T16:46:54.180

1

Perl, 113 chars

$i=abs($I=<>);@w=$_='_'x3**$i;while($i--){$x=3**$i;s/(__){$x}/'_'x$x.' 'x$x/eg;push@w,$_}say for$I>0?reverse@w:@w

Expanded:

$i=abs($I=<>);
@w=$_='_'x3**$i;
while($i--){
    $x=3**$i;
    s/(__){$x}/'_'x$x.' 'x$x/eg;
    push@w,$_
}
say for$I>0?reverse@w:@w

Toto

Posted 2012-01-30T09:24:03.037

Reputation: 909

1

Batch, 265 262 242 236 235 bytes

@echo off
set/pn=
set c=%n%,-1,0
if %n% lss 0 set c=0,1,%n:-=%
for /l %%i in (%c%)do call:l %%i
exit/b
:l
set s=_
for /l %%j in (1,1,%n:-=%)do call:m %1 %%j
echo %s%
:m
set t=%s%
if %1 lss +%2 set t=%s:_= %
set s=%s%%t%%s%

Edit: Saved 12 19 bytes thanks to @l4m2. Saved 8 bytes by removing the unnecessary %a% variable.

Neil

Posted 2012-01-30T09:24:03.037

Reputation: 95 035

This for 247 bytes. – Conor O'Brien – 2016-09-19T18:14:37.200

@ConorO'Brien Mind you it would be 261 if I counted all the CRs as well as the LFs (which I'm sure you're not required to do but I'm lazy like that). – Neil – 2016-09-19T18:52:00.710

So you're not removing CR's from your code? Even though it's not required by .BAT files and stripped by SE anyhow? :P – Conor O'Brien – 2016-09-19T18:53:32.960

@ConorO'Brien It's a penalty I accept of using Notepad to write Batch files. – Neil – 2016-09-19T20:58:24.420

Can you do something like set c=%n%,-1,0 [LF] if %n% lss 0 set c=0,1,%a% [LF] for /l %%i in (%c%)do call:l %%i? – l4m2 – 2018-05-24T11:59:05.010

@l4m2 Thanks, in fact it turns out I don't even need %a%. – Neil – 2018-05-24T14:00:16.447

seems also fine to let l jump into m – l4m2 – 2018-05-27T04:25:13.013

@l4m2 Won't work because m expects %1 and %2 to be numeric. – Neil – 2018-05-27T08:55:18.413

@Neil If you decide to count crlf as 2 bytes you should turn some newline into &(should try to golf) – l4m2 – 2018-05-27T09:47:22.760

@l4m2 Numbers beginning with 0 are treated as octal. – Neil – 2018-05-27T09:49:05.383

@Neil what about https://paste.ubuntu.com/p/mWCTRrXf9Q/

– l4m2 – 2018-05-27T10:44:07.793

@l4m2 Yes that works thanks. – Neil – 2018-05-27T14:25:34.080

Can if %1 lss +%2 set t=%s:_= % workwithout the added zero before? – l4m2 – 2018-05-27T15:09:22.487

@l4m2 Both +1 and -1 are greater than + and -... WTF? – Neil – 2018-05-27T17:11:34.007

Let us continue this discussion in chat.

– l4m2 – 2018-05-27T17:13:58.123

0

JavaScript (Node.js), 148 bytes

n=>f=(L=n<0&&n,R=n>0&&n)=>[...Array(r=3**(n>0?n:-n))].map(_=>((j++).toString(3)+1).indexOf(1)>(L>0?L:-L)?'_':' ',j=r+r).join``+`
${L++<R?f(L,R):''}`

Try it online!

l4m2

Posted 2012-01-30T09:24:03.037

Reputation: 5 985

0

Python 2, 102 bytes

lambda n:'\n'.join(eval("[i+' '*len(i)+i for i in"*abs(n)+"'_'"+"]+['___'*len(i)]"*abs(n))[::n<1or-1])

Try it online!

Erik the Outgolfer

Posted 2012-01-30T09:24:03.037

Reputation: 38 134

0

Prolog (SWI), 265 232 213 bytes

S-E-R:-between(S,E,R).
[]/R/R.
[H|T]/B/R:-T/[H,32,H|B]/R.
N+R:-(N>0->O is N-1,O+S,S/[]/R;R=`_`).
N*[H|T]:-1-N-_,writef("%n",[H]);N*T.
_*[]:-nl.
-N:-(0-N-J,K is N-J;N-0-I,J is -I,K is I-N),L is 3^K,J+R,L*R,1=0;1=1.

Try it online!

ASCII-only

Posted 2012-01-30T09:24:03.037

Reputation: 4 687

0

PowerShell, 111 bytes

filter f{if($s=[math]::Sign($_)){($x=$_-$s|f|%{$_+' '*($l=$_|% Le*)+$_})|?{$s-1};'_'*3*$l;$x|?{$s+1}}else{'_'}}

Try it online!

Less golfed:

filter f{
    if($sign=[math]::Sign($_)){
        $x=$_-$sign|f|%{
            $_+' '*($length=$_|% Length)+$_
        }
        $x|?{$sign-1}  # output $x if $_ is negative
        '_'*3*$length
        $x|?{$sign+1}  # output $x if $_ is positive
    }
    else{
        '_'
    }
}

mazzy

Posted 2012-01-30T09:24:03.037

Reputation: 4 832