Cantor set

From Rosetta Code
Revision as of 21:10, 1 July 2022 by Aamrun (talk | contribs) (Added credits)
Task
Cantor set
You are encouraged to solve this task according to the task description, using any language you may know.
Task

Draw a Cantor set.


See details at this Wikipedia webpage:   Cantor set

11l

Translation of: Python

<lang 11l>V WIDTH = 81 V HEIGHT = 5

F cantor(start, len, index)

  V seg = len I/ 3
  I seg == 0
     R
  L(it) 0 .< :HEIGHT - index
     V i = index + it
     L(jt) 0 .< seg
        V j = start + seg + jt
        V pos = i * :WIDTH + j
        :lines[pos] = ‘ ’
  cantor(start, seg, index + 1)
  cantor(start + seg * 2, seg, index + 1)

V lines = [‘*’] * (WIDTH * HEIGHT) cantor(0, WIDTH, 1)

L(i) 0 .< HEIGHT

  V beg = WIDTH * i
  print((lines[beg .< beg + WIDTH]).join(‘’))</lang>
Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Action!

<lang Action!>PROC FillRect(INT x,y,w,h)

 INT i
 FOR i=y TO y+h-1
 DO
   Plot(x,i)
   DrawTo(x+w-1,i)
 OD

RETURN

PROC DrawCantor(INT x0,y0,h,level)

 INT x,y,i,j,w,w2,h2
 w=1
 FOR i=0 TO level-1
 DO w==*3 OD
 
 Color=1
 y=y0
 FOR i=0 TO level
 DO
   FillRect(x0,y,w,h)
   y==+h*2
 OD
 Color=0
 w2=1 h2=h*2
 FOR i=0 TO level-1
 DO
   x=w2 y=(level-i)*(h*2)
   WHILE x<w
   DO
     FillRect(x0+x,y0+y,w2,h2)
     x==+w2*2
   OD
   w2==*3
   h2==+h*2
 OD

RETURN

PROC Main()

 BYTE CH=$02FC,COLOR1=$02C5,COLOR2=$02C6
 Graphics(8+16)
 COLOR1=$0C
 COLOR2=$02
 DrawCantor(38,48,8,5)
 DO UNTIL CH#$FF OD
 CH=$FF

RETURN</lang>

Output:

Screenshot from Atari 8-bit computer

Ada

<lang Ada>with Ada.Text_IO;

procedure Cantor_Set is

  subtype Level_Range is Integer range 1 .. 5;
  Image : array (Level_Range) of String (1 .. 81) := (others => (others => ' '));
  procedure Cantor (Level : Natural; Length : Natural; Start : Natural) is
  begin
     if Level in Level_Range then
        Image (Level) (Start .. Start + Length - 1) := (others => '*');
        Cantor (Level + 1, Length / 3, Start);
        Cantor (Level + 1, Length / 3, Start + 2 * Length / 3);
     end if;
  end Cantor;

begin

  Cantor (Level  => Level_Range'First,
          Length => 81,
          Start  => 1);
  for L in Level_Range loop
     Ada.Text_IO.Put_Line (Image (L));
  end loop;

end Cantor_Set;</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

ALGOL 68

<lang algol68>BEGIN

   # draw a Cantor Set using ASCII                                            #
   INT    lines     = 5; # number of lines for the set                        #
   # we must choose the line width so that the width of each segment is       #
   # divisible by 3 ( except for the final line where the segment width will  #
   # be 1 )                                                                   #
   INT    set width = 3 ^ ( lines - 1 );
   [ set width ]CHAR set;
   # start with a complete line #
   FOR i TO set width DO set[ i ] := "#" OD;
   print( ( set, newline ) );
   # repeatedly modify the line, replacing the middle third of each segment   #
   # with blanks                                                              #
   INT   segment width := set width OVER 3;
   WHILE segment width > 0 DO
       INT   set pos := 1;
       WHILE set pos < ( set width - segment width ) DO
           set pos   +:= segment width;
           FOR char pos FROM set pos TO ( set pos + segment width ) - 1 DO
               set[ char pos ] := " "
           OD;
           set pos +:= segment width
       OD;
       print( ( set, newline ) );
       segment width OVERAB 3
   OD

END</lang>

Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

ALGOL W

Based on the Algol 68 sample. <lang algolw>begin

   % draw a Cantor Set using ASCII                                            %
   integer LINES;        % number of lines for the set                        %
   integer setWidth;     % width of each line of the set                      %
   % we must choose the line width so that the width of each segment is       %
   % divisible by 3 ( except for the final line where the segment width will  %
   % be 1 )                                                                   %
   LINES    := 5;
   setWidth := round( 3 ** ( LINES - 1 ) );
   begin % start new block so the array can have computed bounds              %
       logical array set ( 1 :: setWidth );
       integer segmentWidth;
       % start with a complete line %
       for i := 1 until setWidth do set( i ) := true;
       segmentWidth := setWidth;
       for l := 1 until LINES do begin
           % print the latest line, all lines start with a "#"                %
           write( "#" );
           for i := 2 until setWidth do writeon( if set( i ) then "#" else " " );
           % modify the line, replacing the middle third of each segment      %
           % with blanks, unless this was the last line                       %
           if l < LINES then begin
               integer   setPos;
               segmentWidth := segmentWidth div 3;
               setPos := 1;
               while setPos < ( setWidth - segmentWidth ) do begin
                   setPos := setPos + segmentWidth;
                   for charPos := setPos until ( setPos + segmentWidth ) - 1 do set( charPos ) := false;
                   setPos := setPos + segmentWidth
               end while_setPos_in_range ;
           end if_l_lt_LINES
       end for_l
   end

end.</lang>

Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

AppleScript

Using composable library functions whenever possible, for better productivity: <lang applescript>------------------------- CANTOR SET -----------------------

-- cantor :: [String] -> [String] on cantor(xs)

   script go
       on |λ|(s)
           set m to (length of s) div 3
           set blocks to text 1 thru m of s
           
           if "█" = text 1 of s then
               {blocks, replicate(m, space), blocks}
           else
               {s}
           end if
       end |λ|
   end script
   concatMap(go, xs)

end cantor



TEST --------------------------

on run

   showCantor(5)

end run

-- showCantor :: Int -> String on showCantor(n)

   unlines(map(my concat, ¬
       take(n, iterate(cantor, ¬
           {replicate(3 ^ (n - 1), "█")}))))

end showCantor



GENERIC FUNCTIONS --------------------

-- concat :: a -> [a] -- concat :: [String] -> String on concat(xs)

   set lng to length of xs
   if 0 < lng and string is class of (item 1 of xs) then
       set acc to ""
   else
       set acc to {}
   end if
   repeat with i from 1 to lng
       set acc to acc & item i of xs
   end repeat
   acc

end concat


-- concatMap :: (a -> [b]) -> [a] -> [b] on concatMap(f, xs)

   set lng to length of xs
   set acc to {}
   tell mReturn(f)
       repeat with i from 1 to lng
           set acc to acc & |λ|(item i of xs, i, xs)
       end repeat
   end tell
   return acc

end concatMap


-- map :: (a -> b) -> [a] -> [b] on map(f, xs)

   tell mReturn(f)
       set lng to length of xs
       set lst to {}
       repeat with i from 1 to lng
           set end of lst to |λ|(item i of xs, i, xs)
       end repeat
       return lst
   end tell

end map


-- Lift 2nd class handler function into 1st class script wrapper -- mReturn :: First-class m => (a -> b) -> m (a -> b) on mReturn(f)

   if class of f is script then
       f
   else
       script
           property |λ| : f
       end script
   end if

end mReturn


-- iterate :: (a -> a) -> a -> Gen [a] on iterate(f, x)

   script
       property v : missing value
       property g : mReturn(f)'s |λ|
       on |λ|()
           if missing value is v then
               set v to x
           else
               set v to g(v)
           end if
           return v
       end |λ|
   end script

end iterate


-- replicate :: Int -> String -> String on replicate(n, s)

   set out to ""
   if n < 1 then return out
   set dbl to s
   
   repeat while (n > 1)
       if (n mod 2) > 0 then set out to out & dbl
       set n to (n div 2)
       set dbl to (dbl & dbl)
   end repeat
   return out & dbl

end replicate


-- take :: Int -> [a] -> [a] -- take :: Int -> String -> String on take(n, xs)

   set c to class of xs
   if list is c then
       if 0 < n then
           items 1 thru min(n, length of xs) of xs
       else
           {}
       end if
   else if string is c then
       if 0 < n then
           text 1 thru min(n, length of xs) of xs
       else
           ""
       end if
   else if script is c then
       set ys to {}
       repeat with i from 1 to n
           set v to xs's |λ|()
           if missing value is v then
               return ys
           else
               set end of ys to v
           end if
       end repeat
       return ys
   else
       missing value
   end if

end take


-- unlines :: [String] -> String on unlines(xs)

   set {dlm, my text item delimiters} to ¬
       {my text item delimiters, linefeed}
   set str to xs as text
   set my text item delimiters to dlm
   str

end unlines</lang>

Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Arturo

<lang rebol>width: 81 height: 5

lines: array.of: height repeat `*` width

cantor: function [start length idx].export:[lines][

   seg: length / 3
   if seg = 0 -> return null
   loop idx..dec height 'i [
       loop (start + seg).. dec start + 2 * seg 'j 
           -> set lines\[i] j ` `
   ]
   cantor start seg idx+1
   cantor start + 2 * seg seg idx+1

]

cantor 0 width 1

loop lines 'line

   -> print join line</lang>
Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

AWK

<lang AWK>

  1. syntax: GAWK -f CANTOR_SET.AWK
  2. converted from C

BEGIN {

   WIDTH = 81
   HEIGHT = 5
   for (i=0; i<HEIGHT; ++i) {
     for (j=0; j<WIDTH; ++j) {
       lines[i][j] = "*"
     }
   }
   cantor(0,WIDTH,1)
   for (i=0; i<HEIGHT; ++i) {
     for (j=0; j<WIDTH; ++j) {
       printf("%s",lines[i][j])
     }
     printf("\n")
   }
   exit(0)

} function cantor(start,leng,indx, i,j,seg) {

   seg = int(leng/3)
   if (seg == 0) { return }
   for (i=indx; i<HEIGHT; ++i) {
     for (j=start+seg; j<start+seg*2; ++j) {
       lines[i][j] = " "
     }
   }
   cantor(start,seg,indx+1)
   cantor(start+seg*2,seg,indx+1)

} </lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

BASIC

<lang gwbasic>10 DEFINT A-Z 20 N = 4 30 W = 3^(N-1) 40 S = W 50 L$ = STRING$(W, "#") 60 PRINT L$ 70 IF S = 1 THEN END 80 S = S\3 90 P = 1 100 IF P >= W-S GOTO 60 110 P = P+S 120 MID$(L$,P,S) = SPACE$(S) 130 P = P+S 140 GOTO 100</lang>

Output:
###########################
#########         #########
###   ###         ###   ###
# #   # #         # #   # #

BASIC256

Translation of: FreeBASIC

<lang BASIC256> global ancho, alto, intervalo ancho = 81 : alto = 5 dim intervalo(alto, ancho)

subroutine Cantor() for i = 0 to alto - 1 for j = 0 to ancho - 1 intervalo[i, j] = "■" next j next i end subroutine

subroutine ConjCantor(inicio, longitud, indice) segmento = longitud / 3 if segmento = 0 then return for i = indice to alto - 1 for j = inicio + segmento to inicio + segmento * 2 - 1 intervalo[i, j] = " " next j next i call ConjCantor(inicio, segmento, indice + 1) call ConjCantor(inicio + segmento * 2, segmento, indice + 1) end subroutine

call Cantor() call ConjCantor(0, ancho, 1) for i = 0 to alto - 1 for j = 0 to ancho - 1 print intervalo[i, j]; next j print next i End </lang>

Output:
Igual que la entrada de FreeBASIC.

BQN

<lang bqn>Cantor ← {" •" ⊏˜ >⥊¨(¯1⊸⊏⊢¨¨⊢)1‿0‿1∧⌜⍟(↕𝕩)1}</lang>

Output:
   Cantor 5
┌─
╵"•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
  •••••••••••••••••••••••••••                           •••••••••••••••••••••••••••
  •••••••••         •••••••••                           •••••••••         •••••••••
  •••   •••         •••   •••                           •••   •••         •••   •••
  • •   • •         • •   • •                           • •   • •         • •   • •"
                                                                                    ┘

C

Translation of: Kotlin

<lang c>#include <stdio.h>

  1. define WIDTH 81
  2. define HEIGHT 5

char lines[HEIGHT][WIDTH];

void init() {

   int i, j;
   for (i = 0; i < HEIGHT; ++i) {
       for (j = 0; j < WIDTH; ++j) lines[i][j] = '*';
   }

}

void cantor(int start, int len, int index) {

   int i, j, seg = len / 3;
   if (seg == 0) return;
   for (i = index; i < HEIGHT; ++i) {
       for (j = start + seg; j < start + seg * 2; ++j) lines[i][j] = ' ';
   }
   cantor(start, seg, index + 1);
   cantor(start + seg * 2, seg, index + 1);

}

void print() {

   int i, j;
   for (i = 0; i < HEIGHT; ++i) {
       for (j = 0; j < WIDTH; ++j) printf("%c", lines[i][j]);
       printf("\n");
   }

}

int main() {

   init();
   cantor(0, WIDTH, 1);
   print();
   return 0;

}</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

C#

Translation of: Java

<lang csharp>using System;

namespace CantorSet {

   class Program {
       const int WIDTH = 81;
       const int HEIGHT = 5;
       private static char[,] lines = new char[HEIGHT, WIDTH];
       static Program() {
           for (int i = 0; i < HEIGHT; i++) {
               for (int j = 0; j < WIDTH; j++) {
                   lines[i, j] = '*';
               }
           }
       }
       private static void Cantor(int start, int len, int index) {
           int seg = len / 3;
           if (seg == 0) return;
           for (int i = index; i < HEIGHT; i++) {
               for (int j = start + seg; j < start + seg * 2; j++) {
                   lines[i, j] = ' ';
               }
           }
           Cantor(start, seg, index + 1);
           Cantor(start + seg * 2, seg, index + 1);
       }
       static void Main(string[] args) {
           Cantor(0, WIDTH, 1);
           for (int i = 0; i < HEIGHT; i++) {
               for (int j = 0; j < WIDTH; j++) {
                   Console.Write(lines[i,j]);
               }
               Console.WriteLine();
           }
       }
   }

}</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

C++

Translation of: D

<lang cpp>#include <iostream>

const int WIDTH = 81; const int HEIGHT = 5;

char lines[WIDTH*HEIGHT];

void cantor(int start, int len, int index) { int seg = len / 3; if (seg == 0) return; for (int i = index; i < HEIGHT; i++) { for (int j = start + seg; j < start + seg * 2; j++) { int pos = i * WIDTH + j; lines[pos] = ' '; } } cantor(start, seg, index + 1); cantor(start + 2 * seg, seg, index + 1); }

int main() { // init for (int i = 0; i < WIDTH*HEIGHT; i++) { lines[i] = '*'; }

// calculate cantor(0, WIDTH, 1);

// print for (int i = 0; i < HEIGHT*WIDTH; i += WIDTH) { printf("%.*s\n", WIDTH, lines + i); }

return 0; }</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

CLU

<lang clu>cantor = cluster is make

   rep = null
   ac = array[char]
   aac = array[array[char]]
   
   make = proc (width, height: int, ch: char) returns (string)
       lines: aac := aac$fill_copy(0, height, ac$fill(0, width, ch))
       cantor_step(lines, 0, width, 1)
       s: stream := stream$create_output()
       for line: ac in aac$elements(lines) do
           stream$putl(s, string$ac2s(line))
       end
       return(stream$get_contents(s))
   end make
   cantor_step = proc (lines: aac, start, len, index: int)
       seg: int := len / 3
       if seg = 0 then return end
       for i: int in int$from_to(index, aac$high(lines)) do
           for j: int in int$from_to(start+seg, start+seg*2-1) do
               lines[i][j] := ' '
           end
       end
       cantor_step(lines, start, seg, index+1)
       cantor_step(lines, start+seg*2, seg, index+1)
   end cantor_step

end cantor

start_up = proc ()

   po: stream := stream$primary_output()
   cs: string := cantor$make(81, 5, '*')
   stream$puts(po, cs)

end start_up</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

COBOL

<lang cobol> IDENTIFICATION DIVISION.

      PROGRAM-ID. CANTOR.
      
      DATA DIVISION.
      WORKING-STORAGE SECTION.
      01 SETTINGS.
         03 NUM-LINES     PIC 9 VALUE 5.
         03 FILL-CHAR     PIC X VALUE '#'.
      01 VARIABLES.
         03 CUR-LINE.
            05 CHAR       PIC X OCCURS 81 TIMES.
         03 WIDTH         PIC 99.
         03 CUR-SIZE      PIC 99.
         03 POS           PIC 99.
         03 MAXPOS        PIC 99.
         03 NEXTPOS       PIC 99.
         03 I             PIC 99.
         
      PROCEDURE DIVISION.
      BEGIN.
          COMPUTE WIDTH = 3 ** (NUM-LINES - 1).
          PERFORM INIT.
          MOVE WIDTH TO CUR-SIZE.
          DISPLAY CUR-LINE.
          PERFORM DO-LINE UNTIL CUR-SIZE IS EQUAL TO 1.
          STOP RUN.
          
      INIT.
          PERFORM INIT-CHAR VARYING I FROM 1 BY 1
               UNTIL I IS GREATER THAN WIDTH.
      
      INIT-CHAR.
          MOVE FILL-CHAR TO CHAR(I).
          
      DO-LINE.
          DIVIDE 3 INTO CUR-SIZE.
          MOVE 1 TO POS.
          SUBTRACT CUR-SIZE FROM WIDTH GIVING MAXPOS.
          PERFORM BLANK-REGIONS UNTIL POS IS GREATER THAN MAXPOS.
          DISPLAY CUR-LINE.
      
      BLANK-REGIONS.
          ADD CUR-SIZE TO POS.
          PERFORM BLANK-CHAR CUR-SIZE TIMES.
          
      BLANK-CHAR.
          MOVE SPACE TO CHAR(POS).
          ADD 1 TO POS.</lang>
Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

D

Translation of: C

<lang d>import std.stdio;

enum WIDTH = 81; enum HEIGHT = 5;

char[WIDTH*HEIGHT] lines;

void cantor(int start, int len, int index) {

   int seg = len / 3;
   if (seg == 0) return;
   for (int i=index; i<HEIGHT; i++) {
       for (int j=start+seg; j<start+seg*2; j++) {
           int pos = i*WIDTH + j;
           lines[pos] = ' ';
       }
   }
   cantor(start, seg, index+1);
   cantor(start+seg*2, seg, index+1);

}

void main() {

   // init
   lines[] = '*';
   // calculate
   cantor(0, WIDTH, 1);
   // print
   for (int i=0; i<HEIGHT; i++) {
       int beg = WIDTH * i;
       writeln(lines[beg..beg+WIDTH]);
   }

}</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Delphi

Translation of: Java

<lang Delphi> program Cantor_set;

{$APPTYPE CONSOLE}

const

 WIDTH: Integer = 81;
 HEIGHT: Integer = 5;

var

 Lines: TArray<TArray<Char>>;

procedure Init; var

 i, j: Integer;

begin

 SetLength(lines, HEIGHT, WIDTH);
 for i := 0 to HEIGHT - 1 do
   for j := 0 to WIDTH - 1 do
     lines[i, j] := '*';

end;

procedure Cantor(start, len, index: Integer); var

 seg, i, j: Integer;

begin

 seg := len div 3;
 if seg = 0 then
   Exit;
 for i := index to HEIGHT - 1 do
   for j := start + seg to start + seg * 2 - 1 do
     lines[i, j] := ' ';
 Cantor(start, seg, index + 1);
 Cantor(start + seg * 2, seg, index + 1);

end;

var

 i, j: Integer;

begin

 Init;
 Cantor(0, WIDTH, 1);
 for i := 0 to HEIGHT - 1 do
 begin
   for j := 0 to WIDTH - 1 do
     Write(lines[i, j]);
   Writeln;
 end;
 Readln;

end. </lang>

Output:

Same result of Java

Excel

LAMBDA

Binding names to the following lambda expressions in the Name Manager of the Excel WorkBook:

(See LAMBDA: The ultimate Excel worksheet function)

<lang lisp>CANTOR =LAMBDA(n,

   APPLYN(n)(
       LAMBDA(grid,
           APPENDROWS(grid)(
               CANTOROW(
                   LASTROW(grid)
               )
           )
       )
   )({0,1})

)


CANTOROW =LAMBDA(xys,

   LET(
       nCols, COLUMNS(xys),
       IF(2 > nCols,
           xys,
           IF(3 < nCols,
               APPENDCOLS(
                   CANTORSLICES(TAKECOLS(2)(xys))
               )(
                   CANTOROW(DROPCOLS(2)(xys))
               ),
               CANTORSLICES(TAKECOLS(2)(xys))
           )
       )
   )

)


CANTORSLICES =LAMBDA(ab,

   LET(
       a, INDEX(ab, 1),
       b, INDEX(ab, 2),
       third, (b - a) / 3,
       CHOOSE({1,2,3,4}, a, a + third, b - third, b)
   )

)


SHOWCANTOR =LAMBDA(grid,

   LET(
       leaves, LASTROW(grid),
       leafWidth, INDEX(leaves, 1, 2) - INDEX(leaves, 1, 1),
       leafCount, 1 / leafWidth,
       SHOWCANTROWS(leafCount)(grid)
   )

)


SHOWCANTROWS =LAMBDA(leafCount,

   LAMBDA(grid,
       LET(
           xs, FILTERP(
              LAMBDA(x, NOT(ISNA(x)))
           )(
               HEADCOL(grid)
           ),
           
           runLengths, LAMBDA(x, 
               CEILING.MATH(leafCount * x))(
                   SUBTRACT(TAILROW(xs))(INITROW(xs)
               )
           ),
           
           iCols, SEQUENCE(1, COLUMNS(runLengths)),
           
           CONCAT(
               REPT(
                   IF(ISEVEN(iCols), " ", "█"), 
                   runLengths
               )
           ) & IF(1 < ROWS(grid),
               CHAR(10) & SHOWCANTROWS(leafCount)(
                   TAILCOL(grid)
               ),
               ""
           )
       )
   )

)</lang>

and also assuming the following generic bindings in the Name Manager for the WorkBook:

<lang lisp>APPENDCOLS =LAMBDA(xs,

   LAMBDA(ys,
       LET(
           nx, COLUMNS(xs),
           ny, COLUMNS(ys),
           colIndexes, SEQUENCE(1, nx + ny),
           rowIndexes, SEQUENCE(MAX(ROWS(xs), ROWS(ys))),
           IFERROR(
               IF(nx < colIndexes,
                   INDEX(ys, rowIndexes, colIndexes - nx),
                   INDEX(xs, rowIndexes, colIndexes)
               ),
               NA()
           )
       )
   )

)


APPENDROWS =LAMBDA(xs,

   LAMBDA(ys,
       LET(
           nx, ROWS(xs),
           rowIndexes, SEQUENCE(nx + ROWS(ys)),
           colIndexes, SEQUENCE(
               1,
               MAX(COLUMNS(xs), COLUMNS(ys))
           ),
           IFERROR(
               IF(rowIndexes <= nx,
                   INDEX(xs, rowIndexes, colIndexes),
                   INDEX(ys, rowIndexes - nx, colIndexes)
               ),
               NA()
           )
       )
   )

)


APPLYN =LAMBDA(n,

   LAMBDA(f,
       LAMBDA(x,
           IF(0 < n,
               APPLYN(n - 1)(f)(
                   f(x)
               ),
               x
           )
       )
   )

)


DROPCOLS =LAMBDA(n,

   LAMBDA(xs,
       INDEX(
           xs,
           SEQUENCE(ROWS(xs), 1),
           SEQUENCE(1, (COLUMNS(xs) - n), 1 + n, 1)
       )
   )

)


FILTERP =LAMBDA(p,

   LAMBDA(xs,
       FILTER(xs, p(xs))
   )

)


HEADCOL =LAMBDA(xs,

   LET(REM, "The first item of each column in xs",
       INDEX(xs, 1, SEQUENCE(1, COLUMNS(xs)))
   )

)


INITROW =LAMBDA(xs,

   INDEX(
       xs,
       SEQUENCE(
           1,
           COLUMNS(xs) - 1,
           1, 1
       )
   )

)


LASTROW =LAMBDA(xs,

   INDEX(
       xs,
       ROWS(xs),
       SEQUENCE(1, COLUMNS(xs), 1, 1)
   )

)


SUBTRACT =LAMBDA(a,

   LAMBDA(b, a - b)

)


TAILCOL =LAMBDA(cols,

   LET(REM, "The tail of each column in the grid.",
       INDEX(
           cols,
           SEQUENCE(ROWS(cols) - 1, 1, 2, 1),
           SEQUENCE(1, COLUMNS(cols))
       )
   )

)


TAILROW =LAMBDA(xs,

   LET(REM,"The tail of each row in the grid",
       n, COLUMNS(xs) - 1,
       IF(0 < n,
           INDEX(
               xs,
               SEQUENCE(ROWS(xs), 1, 1, 1),
               SEQUENCE(1, n, 2, 1)
           ),
           NA()
       )
   )

)


TAKECOLS =LAMBDA(n,

   LAMBDA(xs,
       INDEX(
           xs,
           SEQUENCE(ROWS(xs)),
           SEQUENCE(1, n)
       )
   )

)</lang>

Output:

As a string, with the format of the cell set to a mono-spaced font, and with Format > Cells > Alignment > Wrap text = True, to allow for the display of Excel's (platform - independent) CHAR(10) line breaks:

fx =SHOWCANTOR(CANTOR(1))
A B
1 SHOWCANTOR(CANTOR(0)) <lang>█</lang>
2 SHOWCANTOR(CANTOR(1)) <lang>███

█ █</lang>

3 SHOWCANTOR(CANTOR(2)) <lang>█████████

███ ███ █ █ █ █</lang>

4 SHOWCANTOR(CANTOR(3)) <lang>███████████████████████████

█████████ █████████ ███ ███ ███ ███ █ █ █ █ █ █ █ █</lang>

The diagrams above are drawn from the array of underlying fractions defined by CANTOR(n).

(The expression in B2 below defines the values which populate the grid B2:Q5)

The enclosing application of the built-in IFNA function maps undefined numeric cells in that grid (with the Excel value #N/A) to empty strings, for a more readable display.

(The number format for the numeric cells is set to Fraction > Up to three digits.)

fx =IFNA(CANTOR(3), "")
A B C D E F G H I J K L M N O P Q
1
2 Unit range 0 1
3 Cantor 1 0 1/3 2/3 1
4 Cantor 2 0 1/9 2/9 1/3 2/3 7/9 8/9 1
5 Cantor 3 0 1/27 2/27 1/9 2/9 7/27 8/27 1/3 2/3 19/27 20/27 7/9 8/9 25/27 26/27 1

Factor

<lang factor>USING: grouping.extras io kernel math sequences sequences.repeating ; IN: rosetta-code.cantor-set

CONSTANT: width 81 CONSTANT: depth 5

cantor ( n -- seq )
   dup 0 = [ drop { 0 1 } ]
   [ 1 - cantor [ 3 / ] map dup [ 2/3 + ] map append ] if ;

! Produces a sequence of lengths from a Cantor set, depending on ! width. Even indices are solid; odd indices are blank. ! e.g. 2 cantor gaps -> { 9 9 9 27 9 9 9 } !

gaps ( seq -- seq )
   [ width * ] map [ - abs ] 2clump-map ;
   
print-cantor ( n -- )
   cantor gaps [ even? "#" " " ? swap repeat ] map-index
   concat print ;
   

depth <iota> [ print-cantor ] each</lang>

Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

Forth

Where is says [email protected] it should say c@, but I'm not keen on writing it as c&#64; in the actual code. <lang Forth>warnings off

4 \ iterations

** 1 swap 0 ?DO over * LOOP nip ;

3 swap ** constant width \ Make smallest step 1

create string here width char # fill width allot

print string width type cr ;

\ Overwrite string with new holes of size 'length'. \ Pointer into string at TOS. create length width ,

reduce length dup @ 3 / swap ! ;
done? dup string - width >= ;
hole? dup c@ bl = ;
skip length @ + ;
whipe dup length @ bl fill skip ;
step hole? IF skip skip skip ELSE skip whipe skip THEN ;
split reduce string BEGIN step done? UNTIL drop ;

\ Main

done? length @ 1 <= ;
step split print ;
go print BEGIN step done? UNTIL ;

go bye</lang> Output:

#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

FreeBASIC

<lang freebasic> Const ancho = 81 Const alto = 5 Dim Shared intervalo(alto, ancho) As String Dim As Integer i, j

Sub Cantor()

   Dim As Integer i, j
   For i = 0 To alto - 1
       For j = 0 To ancho - 1
           intervalo(i, j) = Chr(254)
       Next j
   Next i

End Sub

Sub ConjCantor(inicio As Integer, longitud As Integer, indice As Integer)

   Dim As Integer i, j
   Dim segmento As Integer = longitud / 3
   If segmento = 0 Then Return
   For i = indice To alto - 1
       For j = inicio + segmento To inicio + segmento * 2 - 1
           intervalo(i, j) = Chr(32)
       Next j
   Next i
   ConjCantor(inicio, segmento, indice + 1)
   ConjCantor(inicio + segmento * 2, segmento, indice + 1)

End Sub

Cantor() ConjCantor(0, ancho, 1) For i = 0 To alto - 1

   For j = 0 To ancho - 1
       Print intervalo(i, j);
   Next j
   Print

Next i End </lang>

Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Go

Translation of: Kotlin

<lang go>package main

import "fmt"

const (

   width = 81
   height = 5

)

var lines [height][width]byte

func init() {

   for i := 0; i < height; i++ {
       for j := 0; j < width; j++ {
           lines[i][j] = '*'
       }
   }

}

func cantor(start, len, index int) {

   seg := len / 3
   if seg == 0 {
       return
   }
   for i := index; i < height; i++ {
       for j := start + seg; j < start + 2 * seg; j++ {
           lines[i][j] = ' '
       }
   }
   cantor(start, seg, index + 1)
   cantor(start + seg * 2, seg, index + 1)

}

func main() {

   cantor(0, width, 1)
   for _, line := range lines {
       fmt.Println(string(line[:]))
   }

}</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Groovy

Translation of: Java

<lang groovy>class App {

   private static final int WIDTH = 81
   private static final int HEIGHT = 5
   private static char[][] lines
   static {
       lines = new char[HEIGHT][WIDTH]
       for (int i = 0; i < HEIGHT; i++) {
           for (int j = 0; j < WIDTH; j++) {
               lines[i][j] = '*'
           }
       }
   }
   private static void cantor(int start, int len, int index) {
       int seg = (int) (len / 3)
       if (seg == 0) return
       for (int i = index; i < HEIGHT; i++) {
           for (int j = start + seg; j < start + seg * 2; j++) {
               lines[i][j] = ' '
           }
       }
       cantor(start, seg, index + 1)
       cantor(start + seg * 2, seg, index + 1)
   }
   static void main(String[] args) {
       cantor(0, WIDTH, 1)
       for (int i = 0; i < HEIGHT; i++) {
           for (int j = 0; j < WIDTH; j++) {
               System.out.print(lines[i][j])
           }
           System.out.println()
       }
   }

}</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Haskell

Interval bars

Translation of: Python

(Functional version)

<lang haskell>-------------------------- CANTOR ------------------------

cantor :: [(Bool, Int)] -> [(Bool, Int)] cantor = concatMap go

 where
   go (bln, n)
     | bln && 1 < n =
         let m = quot n 3
          in [(True, m), (False, m), (True, m)]
     | otherwise = [(bln, n)]

TEST -------------------------

main :: IO () main = putStrLn $ cantorLines 5


DISPLAY ------------------------

cantorLines :: Int -> String cantorLines n =

 unlines $
   showCantor
     <$> take n (iterate cantor [(True, 3 ^ pred n)])

showCantor :: [(Bool, Int)] -> String showCantor = concatMap $ uncurry (flip replicate . c)

 where
   c True = '*'
   c False = ' '</lang>
Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Or, using strings for the model as well as the display:

<lang haskell>-------------------------- CANTOR ------------------------

cantor :: [String] -> [String] cantor = (go =<<)

 where
   go x
     | '█' == head x = [block, replicate m ' ', block]
     | otherwise = [x]
     where
       m = quot (length x) 3
       block = take m x
       

TEST -------------------------

main :: IO () main = putStrLn $ cantorLines 5



DISPLAY ------------------------

cantorLines :: Int -> String cantorLines =

 unlines . (concat <$>)
   . ( take
         <*> ( iterate cantor
                 . return
                 . flip replicate '█'
                 . (3 ^)
                 . pred
             )
     )</lang>
Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Dual representation

Intervals as fraction pairs, and intervals as graphic bars: <lang haskell>import Control.Monad (join) import Data.Bifunctor (bimap) import Data.List (intercalate, mapAccumL, maximumBy) import Data.Ratio (Ratio, denominator, numerator, (%))


CANTOR ------------------------

cantor :: (Rational, Rational) -> (Rational, Rational) cantor = iterate (go =<<) . pure

 where
   go (x, y) = [(x, x + r), (y - r, y)]
     where
       r = (y - x) / 3

TEST -------------------------

main :: IO () main =

 ( ( (>>)
       . putStrLn
       . unlines
       . fmap intervalRatios
   )
     <*> (putStrLn . intervalBars)
 )
   $ take 4 $ cantor (0, 1)

DISPLAY ------------------------

intervalBars :: (Rational, Rational) -> String intervalBars xs = unlines $ go (d % 1) <$> xs

 where
   d = maximum $ denominator . fst <$> last xs
   go w xs =
     concat . snd $
       mapAccumL
         ( \a (rx, ry) ->
             let (wy, wx) = (w * ry, w * rx)
              in ( wy,
                   replicate (floor (wx - a)) ' '
                     <> replicate (floor (wy - wx)) '█'
                 )
         )
         0
         xs

intervalRatios :: [(Rational, Rational)] -> String intervalRatios =

 ('(' :) . (<> ")")
   . intercalate ") ("
   . fmap
     (uncurry ((<>) . (<> ", ")) . join bimap showRatio)

showRatio :: Rational -> String showRatio = ((<>) . show . numerator) <*> (go . denominator)

 where
   go x
     | 1 /= x = '/' : show x
     | otherwise = []</lang>
Output:
(0, 1)
(0, 1/3) (2/3, 1)
(0, 1/9) (2/9, 1/3) (2/3, 7/9) (8/9, 1)
(0, 1/27) (2/27, 1/9) (2/9, 7/27) (8/27, 1/3) (2/3, 19/27) (20/27, 7/9) (8/9, 25/27) (26/27, 1)

███████████████████████████
█████████         █████████
███   ███         ███   ███
█ █   █ █         █ █   █ █

IS-BASIC

<lang IS-BASIC>100 PROGRAM "Cantor.bas" 110 GRAPHICS HIRES 2 120 SET PALETTE BLACK,WHITE 130 CALL CANTOR(28,500,1216,32) 140 DEF CANTOR(X,Y,L,HEIGHT) 150 IF L>3 THEN 160 PLOT X,Y;X+L,Y,X,Y+4;X+L,Y+4 170 CALL CANTOR(X,Y-HEIGHT,L/3,HEIGHT) 180 CALL CANTOR(X+2*L/3,Y-HEIGHT,L/3,HEIGHT) 190 END IF 200 END DEF</lang>

J

The argument to the cantor_dust monad is an integer that describes the depth of the dust. Shown here are results for cantor_dust 2 and for cantor_dust 3 . It works by checking for 1 digits in the base 3 representation of the coordinates. These background coordinates are plotted with # character using ASCII art. 1j1 #"1 expands the lines to improve aspect ratio on character cell (console) display. }:"1 curtails the extra space character line by line. < draws a pretty box. <lang J> odometer =: [: (4 $. $.) $&1

cantor_dust =: monad define

shape =. ,~ 3 ^ y
a =. shape $ ' '
i =. odometer shape
< (}:"1) 1j1 #"1 '#' (([: <"1 [: ;/"1 (#~ 1 e."1 [: (,/"2) 3 3&#:)) i)}a

) </lang>

   cantor_dust 2
┌─────────────────┐
│  #   # # #   #  │
│# # # # # # # # #│
│  #   # # #   #  │
│# # # # # # # # #│
│# # # # # # # # #│
│# # # # # # # # #│
│  #   # # #   #  │
│# # # # # # # # #│
│  #   # # #   #  │
└─────────────────┘

   cantor_dust 3
┌─────────────────────────────────────────────────────┐
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
│# # # # # # # # # # # # # # # # # # # # # # # # # # #│
│  #   # # #   #     #   # # #   #     #   # # #   #  │
└─────────────────────────────────────────────────────┘
   

With an `x' argument cantor_dust generalizes to higher dimensions. Try 3 cantor_dust 2 <lang J> cantor_dust =: 2&$: :(dyad define)

shape =. x # 3 ^ y
a =. shape $ ' '
i =. odometer shape
< (}:"1) 1j1 #"1 '#' (([: <"1 [: ;/"1 (#~ 1 e."1 [: (,/"2) 3 3&#:)) i)} a

) </lang>

Java

Translation of: Kotlin

<lang java>public class App {

   private static final int WIDTH = 81;
   private static final int HEIGHT = 5;
   private static char[][] lines;
   static {
       lines = new char[HEIGHT][WIDTH];
       for (int i = 0; i < HEIGHT; i++) {
           for (int j = 0; j < WIDTH; j++) {
               lines[i][j] = '*';
           }
       }
   }
   private static void cantor(int start, int len, int index) {
       int seg = len / 3;
       if (seg == 0) return;
       for (int i = index; i < HEIGHT; i++) {
           for (int j = start + seg; j < start + seg * 2; j++) {
               lines[i][j] = ' ';
           }
       }
       cantor(start, seg, index + 1);
       cantor(start + seg * 2, seg, index + 1);
   }
   public static void main(String[] args) {
       cantor(0, WIDTH, 1);
       for (int i = 0; i < HEIGHT; i++) {
           for (int j = 0; j < WIDTH; j++) {
               System.out.print(lines[i][j]);
           }
           System.out.println();
       }
   }

} </lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

JavaScript

Cantor: (Bool, Int) pairs

Translation of: Python

(Functional version)

Translation of: Haskell

<lang JavaScript>(() => {

   "use strict";
   // -------------- CANTOR BOOL-INT PAIRS --------------
   // cantor :: [(Bool, Int)] -> [(Bool, Int)]
   const cantor = xs => {
       const go = ([bln, n]) =>
           bln && 1 < n ? (() => {
               const x = Math.floor(n / 3);
               return [
                   [true, x],
                   [false, x],
                   [true, x]
               ];
           })() : [
               [bln, n]
           ];
       return xs.flatMap(go);
   };
   // ---------------------- TEST -----------------------
   // main :: IO ()
   const main = () =>
       cantorLines(5);


   // --------------------- DISPLAY ---------------------
   // cantorLines :: Int -> String
   const cantorLines = n =>
       take(n)(
           iterate(cantor)([
               [true, 3 ** (n - 1)]
           ])
       )
       .map(showCantor)
       .join("\n");


   // showCantor :: [(Bool, Int)] -> String
   const showCantor = xs =>
       xs.map(
           ([bln, n]) => (
               bln ? (
                   "*"
               ) : " "
           ).repeat(n)
       )
       .join("");
   // ---------------- GENERIC FUNCTIONS ----------------
   // iterate :: (a -> a) -> a -> Gen [a]
   const iterate = f =>
       // An infinite list of repeated
       // applications of f to x.
       function* (x) {
           let v = x;
           while (true) {
               yield v;
               v = f(v);
           }
       };


   // take :: Int -> [a] -> [a]
   // take :: Int -> String -> String
   const take = n =>
       // The first n elements of a list,
       // string of characters, or stream.
       xs => "GeneratorFunction" !== xs
       .constructor.constructor.name ? (
           xs.slice(0, n)
       ) : [].concat(...Array.from({
           length: n
       }, () => {
           const x = xs.next();
           return x.done ? [] : [x.value];
       }));
   // MAIN ---
   return main();

})();</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Cantor: Strings

Using strings for the model as well as the display:

Translation of: Haskell

<lang javascript>(() => {

   "use strict";
   // ----------------- CANTOR STRINGS ------------------
   // cantor :: [String] -> [String]
   const cantor = xs => {
       const go = s => {
           const
               m = Math.floor(s.length / 3),
               blocks = take(m)(s);
           return "█" === s[0] ? (
               [blocks, " ".repeat(m), blocks]
           ) : [s];
       };
       return xs.flatMap(go);
   };
   // ---------------------- TEST -----------------------
   const main = () =>
       showCantor(5);


   // --------------------- DISPLAY ---------------------
   // showCantor :: Int -> String
   const showCantor = n =>
       take(n)(
           iterate(cantor)([
               "█".repeat(3 ** (n - 1))
           ])
       )
       .map(x => x.join(""))
       .join("\n");


   // ---------------- GENERIC FUNCTIONS ----------------
   // iterate :: (a -> a) -> a -> Gen [a]
   const iterate = f =>
       // An infinite list of repeated
       // applications of f to x.
       function* (x) {
           let v = x;
           while (true) {
               yield v;
               v = f(v);
           }
       };


   // take :: Int -> [a] -> [a]
   // take :: Int -> String -> String
   const take = n =>
       // The first n elements of a list,
       // string of characters, or stream.
       xs => "GeneratorFunction" !== xs
       .constructor.constructor.name ? (
           xs.slice(0, n)
       ) : [].concat(...Array.from({
           length: n
       }, () => {
           const x = xs.next();
           return x.done ? [] : [x.value];
       }));
   // MAIN ---
   return main();

})();</lang>

Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Cantor: Rational pairs

Translation of: Haskell
Translation of: Python


Cantor ternary intervals rendered both as lists of rational pairs, and as graphic bars. In the case of languages like Javascript which lack a built-in Ratio type, or standard Fraction/Ratio library, rendering stages of the set elaboration as lists of fraction pairs may take more work than rendering them as graphic lines.

<lang javascript>(() => {

   "use strict";
   // -------------- CANTOR RATIONAL PAIRS --------------
   // cantor :: [(Rational, Rational)] ->
   //           [(Rational, Rational)]
   const cantor = xs => {
       const go = ab => {
           const [r1, r2] = Array.from(ab).map(rational);
           const third = ratioDiv(ratioMinus(r2)(r1))(3);
           return [
               Tuple(r1)(ratioPlus(r1)(third)),
               Tuple(ratioMinus(r2)(third))(r2)
           ];
       };
       return xs.flatMap(go);
   };


   // ---------------------- TEST -----------------------
   // main :: IO ()
   const main = () => {
       const
           xs = take(4)(
               iterate(cantor)([Tuple(0)(1)])
           );
       return [
               `${unlines(xs.map(intervalRatios))}\n`,
               intervalBars(xs)
           ]
           .join("\n\n");
   };


   // --------------------- DISPLAY ---------------------
   // intervalRatios :: [(Rational, Rational)] -> String
   const intervalRatios = xs => {
       const go = ab =>
           Array.from(ab).map(
               compose(showRatio, rational)
           )
           .join(", ");
       return `(${xs.map(go).join(") (")})`;
   };
   // intervalBars :: (Rational, Rational) -> String
   const intervalBars = rs => {
       const go = w => xs =>
           snd(mapAccumL(
               a => ab => {
                   const [wx, wy] = Array.from(ab).map(
                       r => ratioMult(w)(
                           rational(r)
                       )
                   );
                   return Tuple(wy)(
                       replicateString(
                           floor(ratioMinus(wx)(a))
                       )(" ") + replicateString(
                           floor(ratioMinus(wy)(wx))
                       )("█")
                   );
               }
           )(0)(xs)).join("");
       const d = maximum(
           last(rs).map(x => fst(x).d)
       );
       return unlines(rs.map(
           go(Ratio(d)(1))
       ));
   };


   // ---------------- GENERIC FUNCTIONS ----------------
   // Ratio :: Integral a => a -> a -> Ratio a
   const Ratio = a => b => {
       const go = (x, y) =>
           0 !== y ? (() => {
               const d = gcd(x)(y);
               return {
                   type: "Ratio",
                   // numerator
                   "n": Math.trunc(x / d),
                   // denominator
                   "d": Math.trunc(y / d)
               };
           })() : undefined;
       return go(a * signum(b), abs(b));
   };


   // Tuple (,) :: a -> b -> (a, b)
   const Tuple = a =>
       b => ({
           type: "Tuple",
           "0": a,
           "1": b,
           length: 2
       });


   // abs :: Num -> Num
   const abs =
       // Absolute value of a given number
       // without the sign.
       x => 0 > x ? (
           -x
       ) : x;


   // approxRatio :: Float -> Float -> Ratio
   const approxRatio = eps =>
       n => {
           const
               gcde = (e, x, y) => {
                   const _gcd = (a, b) =>
                       b < e ? (
                           a
                       ) : _gcd(b, a % b);
                   return _gcd(Math.abs(x), Math.abs(y));
               },
               c = gcde(Boolean(eps) ? (
                   eps
               ) : (1 / 10000), 1, n);
           return Ratio(
               Math.floor(n / c)
           )(
               Math.floor(1 / c)
           );
       };


   // floor :: Num -> Int
   const floor = x => {
       const
           nr = (
               "Ratio" !== x.type ? (
                   properFraction
               ) : properFracRatio
           )(x),
           n = nr[0];
       return 0 > nr[1] ? n - 1 : n;
   };


   // fst :: (a, b) -> a
   const fst = ab =>
       // First member of a pair.
       ab[0];


   // gcd :: Integral a => a -> a -> a
   const gcd = x =>
       y => {
           const zero = x.constructor(0);
           const go = (a, b) =>
               zero === b ? (
                   a
               ) : go(b, a % b);
           return go(abs(x), abs(y));
       };


   // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
   const compose = (...fs) =>
       // A function defined by the right-to-left
       // composition of all the functions in fs.
       fs.reduce(
           (f, g) => x => f(g(x)),
           x => x
       );


   // iterate :: (a -> a) -> a -> Gen [a]
   const iterate = f =>
       // An infinite list of repeated
       // applications of f to x.
       function* (x) {
           let v = x;
           while (true) {
               yield v;
               v = f(v);
           }
       };


   // last :: [a] -> a
   const last = xs =>
       // The last item of a list.
       0 < xs.length ? (
           xs.slice(-1)[0]
       ) : null;


   // lcm :: Int -> Int -> Int
   const lcm = x =>
       // The smallest positive integer divisible
       // without remainder by both x and y.
       y => (x === 0 || y === 0) ? (
           0
       ) : Math.abs(Math.floor(x / gcd(x)(y)) * y);


   // mapAccumL :: (acc -> x -> (acc, y)) ->
   // acc -> [x] -> (acc, [y])
   const mapAccumL = f =>
       // A tuple of an accumulation and a list
       // obtained by a combined map and fold,
       // with accumulation from left to right.
       acc => xs => [...xs].reduce(
           (a, x) => {
               const ab = f(a[0])(x);
               return [ab[0], a[1].concat(ab[1])];
           },
           [acc, []]
       );


   // maximum :: Ord a => [a] -> a
   const maximum = xs => (
       // The largest value in a non-empty list.
       ys => 0 < ys.length ? (
           ys.slice(1).reduce(
               (a, y) => y > a ? (
                   y
               ) : a, ys[0]
           )
       ) : undefined
   )(xs);


   // properFracRatio :: Ratio -> (Int, Ratio)
   const properFracRatio = nd => {
       const [q, r] = Array.from(quotRem(nd.n)(nd.d));
       return Tuple(q)(Ratio(r)(nd.d));
   };


   // properFraction :: Real -> (Int, Real)
   const properFraction = n => {
       const i = Math.floor(n) + (n < 0 ? 1 : 0);
       return Tuple(i)(n - i);
   };


   // quotRem :: Integral a => a -> a -> (a, a)
   const quotRem = m =>
       // The quotient, tupled with the remainder.
       n => Tuple(
           Math.trunc(m / n)
       )(
           m % n
       );


   // ratioDiv :: Rational -> Rational -> Rational
   const ratioDiv = n1 => n2 => {
       const [r1, r2] = [n1, n2].map(rational);
       return Ratio(r1.n * r2.d)(
           r1.d * r2.n
       );
   };


   // ratioMinus :: Rational -> Rational -> Rational
   const ratioMinus = n1 => n2 => {
       const [r1, r2] = [n1, n2].map(rational);
       const d = lcm(r1.d)(r2.d);
       return Ratio(
           (r1.n * (d / r1.d)) - (r2.n * (d / r2.d))
       )(d);
   };


   // ratioMult :: Rational -> Rational -> Rational
   const ratioMult = n1 => n2 => {
       const [r1, r2] = [n1, n2].map(rational);
       return Ratio(r1.n * r2.n)(
           r1.d * r2.d
       );
   };


   // ratioPlus :: Rational -> Rational -> Rational
   const ratioPlus = n1 =>
       n2 => {
           const [r1, r2] = [n1, n2].map(rational);
           const d = lcm(r1.d)(r2.d);
           return Ratio(
               (r1.n * (d / r1.d)) + (
                   r2.n * (d / r2.d)
               )
           )(d);
       };


   // rational :: Num a => a -> Rational
   const rational = x =>
       isNaN(x) ? x : Number.isInteger(x) ? (
           Ratio(x)(1)
       ) : approxRatio(undefined)(x);


   // replicateString :: Int -> String -> String
   const replicateString = n =>
       s => s.repeat(n);


   // showRatio :: Ratio -> String
   const showRatio = r =>
       "Ratio" !== r.type ? (
           r.toString()
       ) : r.n.toString() + (
           1 !== r.d ? (
               `/${r.d}`
           ) : ""
       );


   // signum :: Num -> Num
   const signum = n =>
       // | Sign of a number.
       n.constructor(
           0 > n ? (
               -1
           ) : (
               0 < n ? 1 : 0
           )
       );


   // snd :: (a, b) -> b
   const snd = ab =>
       // Second member of a pair.
       ab[1];


   // take :: Int -> [a] -> [a]
   // take :: Int -> String -> String
   const take = n =>
       // The first n elements of a list,
       // string of characters, or stream.
       xs => "GeneratorFunction" !== xs
       .constructor.constructor.name ? (
           xs.slice(0, n)
       ) : [].concat(...Array.from({
           length: n
       }, () => {
           const x = xs.next();
           return x.done ? [] : [x.value];
       }));


   // unlines :: [String] -> String
   const unlines = xs =>
       // A single string formed by the intercalation
       // of a list of strings with the newline character.
       xs.join("\n");


   // MAIN ---
   return main();

})();</lang>

Output:
(0, 1)
(0, 1/3) (2/3, 1)
(0, 1/9) (2/9, 1/3) (2/3, 7/9) (8/9, 1)
(0, 1/27) (2/27, 1/9) (2/9, 7/27) (8/27, 1/3) (2/3, 19/27) (20/27, 7/9) (8/9, 25/27) (26/27, 1)

███████████████████████████
█████████         █████████
███   ███         ███   ███
█ █   █ █         █ █   █ █

jq

<lang jq># cantor(width; height) def cantor($w; $h):

 def init: [range(0; $h) | [range(0; $w) | "*"]];
 def cantor($start; $leng; $ix):
   ($leng/3|floor) as $seg
   | if $seg == 0 then .
     else reduce range($ix; $h) as $i (.;
            reduce range($start+$seg; $start + 2*$seg) as $j (.; .[$i][$j] = " "))
     | cantor($start; $seg; $ix+1)
     | cantor($start + 2*$seg; $seg; $ix+1)
   end ;
 init | cantor(0; $w; 1);
   

def pp: .[] | join("");

cantor($width; $height) | pp</lang>

Output:

With the above in a file, cantor.jq, the following incantation yields the same output as shown e.g. under `awk`, `julia`, etc.

   jq -nr --argjson width 81 --argjson height 5 -f cantor.jq


Julia

Translation of: AWK

<lang julia>const width = 81 const height = 5

function cantor!(lines, start, len, idx)

   seg = div(len, 3)
   if seg > 0
       for i in idx+1:height, j in start + seg + 1: start + seg * 2
           lines[i, j] = ' '
       end
       cantor!(lines, start, seg, idx + 1)
       cantor!(lines, start + 2 * seg, seg, idx + 1)
   end

end

lines = fill(UInt8('#'), height, width) cantor!(lines, 0, width, 1)

for i in 1:height, j in 1:width

   print(Char(lines[i, j]), j == width ? "\n" : "")

end

</lang>

Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

Kotlin

Simple terminal drawing. <lang scala>// Version 1.2.31

const val WIDTH = 81 const val HEIGHT = 5

val lines = List(HEIGHT) { CharArray(WIDTH) { '*' } }

fun cantor(start: Int, len: Int, index: Int) {

   val seg = len / 3
   if (seg == 0) return
   for (i in index until HEIGHT) {
       for (j in start + seg until start + seg * 2) lines[i][j] = ' '
   }
   cantor(start, seg, index + 1)
   cantor(start + seg * 2, seg, index + 1)

}

fun main(args: Array<String>) {

   cantor(0, WIDTH, 1)
   lines.forEach { println(it) }

}</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Lua

Translation of: python

<lang lua>local WIDTH = 81 local HEIGHT = 5 local lines = {}

function cantor(start, length, index)

   -- must be local, or only one side will get calculated
   local seg = math.floor(length / 3)
   if 0 == seg then
       return nil
   end
   -- remove elements that are not in the set
   for it=0, HEIGHT - index do
       i = index + it
       for jt=0, seg - 1 do
           j = start + seg + jt
           pos = WIDTH * i + j
           lines[pos] = ' '
       end
   end
   -- left side
   cantor(start,           seg, index + 1)
   -- right side
   cantor(start + seg * 2, seg, index + 1)
   return nil

end

-- initialize the lines for i=0, WIDTH * HEIGHT do

   lines[i] = '*'

end

-- calculate cantor(0, WIDTH, 1)

-- print the result sets for i=0, HEIGHT-1 do

   beg = WIDTH * i
   for j=beg, beg+WIDTH-1 do
       if j <= WIDTH * HEIGHT then
           io.write(lines[j])
       end
   end
   print()

end</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Mathematica/Wolfram Language

<lang Mathematica>Graphics[MeshPrimitives[CantorMesh[#],1]/.{x_}:>{x,-0.05#}&/@Range[5],ImageSize->600]</lang>

Output:

A graphic of a Cantor set is shown

Modula-2

Translation of: Kotlin

<lang modula2>MODULE Cantor; FROM Terminal IMPORT Write,WriteLn,ReadChar;

CONST

   WIDTH = 81;
   HEIGHT = 5;

VAR

   lines : ARRAY[0..HEIGHT] OF ARRAY[0..WIDTH] OF CHAR;

PROCEDURE Init; VAR i,j : CARDINAL; BEGIN

   FOR i:=0 TO HEIGHT DO
       FOR j:=0 TO WIDTH DO
           lines[i,j] := '*'
       END
   END

END Init;

PROCEDURE Cantor(start,len,index : CARDINAL); VAR i,j,seg : CARDINAL; BEGIN

   seg := len DIV 3;
   IF seg=0 THEN RETURN END;
   FOR i:=index TO HEIGHT-1 DO
       j := start+seg;
       FOR j:=start+seg TO start+seg*2-1 DO
           lines[i,j] := ' '
       END
   END;
   Cantor(start, seg, index+1);
   Cantor(start+seg*2, seg, index+1)

END Cantor;

PROCEDURE Print; VAR i,j : CARDINAL; BEGIN

   FOR i:=0 TO HEIGHT-1 DO
       FOR j:=0 TO WIDTH-1 DO
           Write(lines[i,j])
       END;
       WriteLn
   END

END Print;

BEGIN

   Init;
   Cantor(0,WIDTH,1);
   Print;
   ReadChar;

END Cantor.</lang>

Nim

Translation of: Kotlin

<lang Nim>import strutils

const

 Width = 81
 Height = 5

var lines: array[Height, string] for line in lines.mitems: line = repeat('*', Width)

proc cantor(start, length, index: Natural) =

 let seg = length div 3
 if seg == 0: return
 for i in index..<Height:
   for j in (start + seg)..<(start + seg * 2):
     lines[i][j] = ' '
 cantor(start, seg, index + 1)
 cantor(start + seg * 2, seg, index + 1)

cantor(0, Width, 1) for line in lines:

 echo line</lang>
Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Objeck

Translation of: Java

<lang objeck>class CantorSet {

 WIDTH : static : Int;
 HEIGHT : static : Int;
 lines : static : Char[,];

 function : Init() ~ Nil {
   WIDTH := 81;
   HEIGHT := 5;
   lines := Char->New[HEIGHT, WIDTH];
   
   each(i : HEIGHT) {
     each(j : WIDTH) {
       lines[i,j] := '*';
     };
   };
 }
 function : Cantor(start : Int, len : Int, index : Int) ~ Nil {
   seg : Int := len / 3;
   if(seg = 0) {
     return;
   };
   for(i := index; i < HEIGHT; i += 1;) {
     for(j := start + seg; j < start + seg * 2; j += 1;) {
       lines[i,j] := ' ';
     };
   };
   Cantor(start, seg, index + 1);
   Cantor(start + seg * 2, seg, index + 1);
 }
 function : Main(args : String[]) ~ Nil {
   Init();
   Cantor(0, WIDTH, 1);
   each(i : HEIGHT) {
     each(j : WIDTH) {
       lines[i,j]->Print();
     };
     ""->PrintLine();
   };
 }

}</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Perl

Translation of: Raku

<lang Perl>use strict; use feature 'say';

sub cantor {

   our($height) = @_;
   my $width = 3 ** ($height - 1);
   our @lines = ('#' x $width) x $height;
   sub trim_middle_third {
       my($len, $start, $index) = @_;
       my $seg = int $len / 3
           or return;
       for my $i ( $index .. $height - 1 ) {
         for my $j ( 0 .. $seg - 1 ) {
           substr $lines[$i], $start + $seg + $j, 1, ' ';
         }
       }
       trim_middle_third( $seg, $start + $_, $index + 1 ) for 0, $seg * 2;
   }
   trim_middle_third( $width, 0, 1 );
   @lines;

}

say for cantor(5);</lang>

Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

regex version

<lang perl>#!/usr/bin/perl -l

use strict; # https://rosettacode.org/wiki/Cantor_set use warnings;

$_ = '#' x 81;

1 while print, s/(#+)\1\1/ $1 . $1 =~ tr!#! !r . $1 /ge;</lang>

Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

Phix

Based on Algol 68, but even simpler, shorter, and sweeter!

integer n = 5,
        w = power(3,n-1),
        len = w
string line = repeat('#',w)&"\n"
 
while 1 do
    puts(1,line)
    if len=1 then exit end if
    len /= 3
    integer pos = 1
    while pos<(w-len) do
        pos += len
        line[pos..pos+len-1] = ' '
        pos += len
    end while
end while
Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

Phixmonti

<lang Phixmonti>include ..\Utilitys.pmt

5 >ps 3 tps 1 - power var w "#" 1 get nip w repeat var line

ps> for

   3 swap 1 - power
   w over / int var step
   true >ps
   for var j
   	tps not if
           step for var k
           	line 32 j 1 - step * k + set var line
           endfor
       endif
       ps> not >ps
   endfor
   cps
   line ?

endfor</lang> Other solution <lang Phixmonti>include ..\Utilitys.pmt

5 >ps 3 tps 1 - power var w "#" 1 get nip w repeat var line

( 2 ps> ) for

   line ?
   3 swap 1 - power
   w over / int var step
   2 swap 2 3 tolist for var j
       step for var k
           line 32 j 1 - step * k + set var line
       endfor
   endfor

endfor line ?</lang>

Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

=== Press any key to exit ===

Processing

<lang java> //Aamrun, 1st July 2022

void cantorSet(int x1,int y1,int x2,int y2,int strWt,int gap,int n){

   strokeWeight(strWt);
   line(x1,y1,x2,y2);
 if(n>0){
   cantorSet(x1,gap + y1,(2*x1+x2)/3,gap + (2*y1+y2)/3,strWt,gap,n-1);
   cantorSet((2*x2+x1)/3,gap + (2*y2+y1)/3,x2,gap + y2,strWt,gap,n-1);
 }

}

void setup(){

 size(1000,1000);
 cantorSet(100,10,900,10,1,10,5);

} </lang>

Python

Imperative

<lang python>WIDTH = 81 HEIGHT = 5

lines=[] def cantor(start, len, index):

   seg = len / 3
   if seg == 0:
       return None
   for it in xrange(HEIGHT-index):
       i = index + it
       for jt in xrange(seg):
           j = start + seg + jt
           pos = i * WIDTH + j
           lines[pos] = ' '
   cantor(start,           seg, index + 1)
   cantor(start + seg * 2, seg, index + 1)
   return None

lines = ['*'] * (WIDTH*HEIGHT) cantor(0, WIDTH, 1)

for i in xrange(HEIGHT):

   beg = WIDTH * i
   print .join(lines[beg : beg+WIDTH])</lang>
Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Functional

Separating (Bool, Int) model from String display:

Works with: Python version 3.7

<lang python>Cantor set – separating model from display

from functools import (reduce) import itertools


  1. cantor :: [(Bool, Int)] -> [(Bool, Int)]

def cantor(xs):

   A Cantor segmentation step.
   def go(tpl):
       (bln, n) = tpl
       m = n // 3
       return [
           (True, m), (False, m), (True, m)
       ] if bln and (1 < n) else [tpl]
   return concatMap(go)(xs)


  1. cantorLines :: Int -> String

def cantorLines(n):

   A text block display of n
      Cantor-segmented lines.
   
   m = n - 1
   repeat = itertools.repeat
   return '\n'.join(
       [showCantor(x) for x in (
           reduce(
               lambda a, f: a + [f(a[-1])],
               repeat(cantor, m),
               (True, 3 ** m)
           )
       )]
   )


  1. showCantor :: [(Bool, Int)] -> String

def showCantor(xs):

   A text block display of a list of
      Cantor line segments.
   
   return .join(
       concatMap(lambda tpl: tpl[1] * ('█' if tpl[0] else ' '))(
           xs
       )
   )


  1. main :: IO ()

def main():

   Testing to depth 5
   print(
       cantorLines(5)
   )


  1. GENERIC -------------------------------------------------------------
  1. concatMap :: (a -> [b]) -> [a] -> [b]

def concatMap(f):

   A concatenated list over which a function has been mapped.
      The list monad can be derived by using a function f which
      wraps its output in a list,
      (using an empty list to represent computational failure).
   chain = itertools.chain
   return lambda xs: list(
       chain.from_iterable(map(f, xs))
   )


  1. MAIN ---

if __name__ == '__main__':

   main()</lang>
Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Or, using strings for both model and display:

Translation of: JavaScript
Translation of: Haskell
Works with: Python version 3.7

<lang python>Cantor set – strings as both model and display.

from itertools import (chain, islice)


  1. cantorLines :: Int -> String

def cantorLines(n):

   N levels of cantor segmentation,
      obtained and displayed in the
      form of lines of block characters.
   
   return '\n'.join(
       [.join(x) for x in islice(
           iterate(cantor)(
               [3 ** (n - 1) * '█']
           ), n
       )]
   )


  1. cantor :: [String] -> [String]

def cantor(xs):

   A cantor line derived from its predecessor.
   def go(s):
       m = len(s) // 3
       blocks = s[0:m]
       return [
           blocks, m * ' ', blocks
       ] if '█' == s[0] else [s]
   return concatMap(go)(xs)


  1. MAIN ----------------------------------------------------
  2. main :: IO ()

def main():

   Testing cantor line generation to level 5
   print(
       cantorLines(5)
   )
  1. GENERIC -------------------------------------------------


  1. concatMap :: (a -> [b]) -> [a] -> [b]

def concatMap(f):

   A concatenated list over which a function has been mapped.
      The list monad can be derived by using a function f which
      wraps its output in a list,
      (using an empty list to represent computational failure).
   return lambda xs: list(
       chain.from_iterable(map(f, xs))
   )


  1. iterate :: (a -> a) -> a -> Gen [a]

def iterate(f):

   An infinite list of repeated
      applications of f to x.
   
   def go(x):
       v = x
       while True:
           yield v
           v = f(v)
   return lambda x: go(x)


  1. MAIN ---

if __name__ == '__main__':

   main()</lang>
Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Dual representations – fractional and graphic

Works with: Python version 3.7

<lang python>A Cantor set generator, and two different

  representations of its output.

from itertools import (islice, chain) from fractions import Fraction from functools import (reduce)


  1. ----------------------- CANTOR SET -----------------------
  1. cantor :: Generator (Fraction, Fraction)

def cantor():

   A non-finite stream of successive Cantor
      partitions of the line, in the form of
      lists of fraction pairs.
   
   def go(xy):
       (x, y) = xy
       third = Fraction(y - x, 3)
       return [(x, x + third), (y - third, y)]
   return iterate(
       concatMap(go)
   )(
       [(0, 1)]
   )


  1. fractionLists :: [(Fraction, Fraction)] -> String

def fractionLists(xs):

   A fraction pair representation of a
      Cantor-partitioned line.
   
   def go(xy):
       return ', '.join(map(showRatio, xy))
   return ' '.join('(' + go(x) + ')' for x in xs)


  1. intervalBars :: [(Fraction, Fraction)] -> String

def intervalBars(w):

   A block diagram representation of a
      Cantor-partitioned line.
   
   def go(xs):
       def show(a, tpl):
           [x, y] = [int(w * r) for r in tpl]
           return (
               y,
               (' ' * (x - a)) + ('█' * (y - x))
           )
       return mapAccumL(show)(0)(xs)
   return lambda xs: .join(go(xs)[1])


  1. -------------------------- TEST --------------------------
  2. main :: IO ()

def main():

   Testing the generation of successive
      Cantor subdivisions of the line, and
      displaying them both as lines of fraction
      pairs and as graphic interval bars.
   
   xs = list(islice(cantor(), 4))
   w = max(xy[1].denominator for xy in xs[-1])
   print(
       '\n'.join(map(fractionLists, xs)),
       '\n'
   )
   print(
       '\n'.join(map(intervalBars(w), xs))
   )


  1. ------------------------ GENERIC -------------------------
  1. concatMap :: (a -> [b]) -> [a] -> [b]

def concatMap(f):

   A concatenated list over which a function has been mapped.
      The list monad can be derived by using a function f which
      wraps its output in a list,
      (using an empty list to represent computational failure).
   return lambda xs: list(
       chain.from_iterable(map(f, xs))
   )


  1. iterate :: (a -> a) -> a -> Gen [a]

def iterate(f):

   An infinite list of repeated
      applications of f to x.
   
   def go(x):
       v = x
       while True:
           yield v
           v = f(v)
   return go


  1. mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])

def mapAccumL(f):

   A tuple of an accumulation and a list derived by a
      combined map and fold,
      with accumulation from left to right.
   
   def go(a, x):
       tpl = f(a[0], x)
       return (tpl[0], a[1] + [tpl[1]])
   return lambda acc: lambda xs: (
       reduce(go, xs, (acc, []))
   )


  1. showRatio :: Ratio -> String

def showRatio(r):

   String representation of the ratio r.
   d = r.denominator
   return str(r.numerator) + (
       '/' + str(d) if 1 != d else 
   )


  1. MAIN ---

if __name__ == '__main__':

   main()</lang>
Output:
(0, 1)
(0, 1/3) (2/3, 1)
(0, 1/9) (2/9, 1/3) (2/3, 7/9) (8/9, 1)
(0, 1/27) (2/27, 1/9) (2/9, 7/27) (8/27, 1/3) (2/3, 19/27) (20/27, 7/9) (8/9, 25/27) (26/27, 1) 

███████████████████████████
█████████         █████████
███   ███         ███   ███
█ █   █ █         █ █   █ █

QB64

Translation of: FreeBASIC

Note: Other languages will need to zero out the a() array. In QB64 all arrays are initialized to zero at program start.

<lang qb64>_Title "Cantor Set"

Dim Shared As Integer sw, sh, wide, high sw = 800: sh = 200: wide = 729: high = 7 Dim Shared As Integer a(wide, high)

Screen _NewImage(sw, sh, 8) Cls , 15: Color 0

Call calc(0, wide, 1) Call CantorSet

Sleep System

Sub calc (start As Integer, length As Integer, index As Integer)

   Dim As Integer i, j, newLength
   newLength = length \ 3
   If newLength = 0 Then Exit Sub
   For j = index To high - 1
       For i = start + newLength To start + newLength * 2 - 1
           a(i, j) = 1
       Next
   Next
   Call calc(start, newLength, index + 1)
   Call calc(start + newLength * 2, newLength, index + 1)

End Sub

Sub CantorSet

   Dim As Integer i, j, x, y
   For y = 0 To high - 1
       j = y + 1
       For x = 0 To wide - 1
           i = x + 34
           If a(x, y) = 0 Then Line (i, j * 24 - 5)-(i, j * 24 + 17)
       Next
   Next

End Sub</lang>

Quackery

Using an L-System

<lang Quackery> [ $ "" swap witheach

     [ nested quackery join ] ] is expand (   $ --> $ )

 [ $ "ABA" ]                    is A      (   $ --> $ )

 [ $ "BBB" ]                    is B      (   $ --> $ )
 
 [ char A = iff
     [ char q ] else space
   swap of echo$ ]              is draw   ( n c --> $ )

 81 $ "A"
 5 times
   [ dup witheach
       [ dip over draw ]
     cr
     i if
       [ expand
         dip [ 3 / ] ] ]
 2drop

</lang>

Output:
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
qqqqqqqqqqqqqqqqqqqqqqqqqqq                           qqqqqqqqqqqqqqqqqqqqqqqqqqq
qqqqqqqqq         qqqqqqqqq                           qqqqqqqqq         qqqqqqqqq
qqq   qqq         qqq   qqq                           qqq   qqq         qqq   qqq
q q   q q         q q   q q                           q q   q q         q q   q q


R

Attribution-NonCommercial-ShareAlike 4.0 International, CC BY-NC-SA 4.0 to neonira@gmail.com

<lang R> cantorSet <- function() {

 depth <- 6L
 cs <- vector('list', depth)
 cs1L <- c(0, 1)
 for(k in seq_len(depth)) {
   csk + 1L <- unlist(sapply(seq_len(length(csk) / 2L), function(j) {
     p <- csk[2L] / 3
     h <- 2L * (j - 1L)
     c(
       csk[h + 1L] + c(0, p),
       csk[h + 2L] - c(p, 0)
     )
   }, simplify = FALSE))
 }
 cs

}

cantorSetGraph <- function() {

 cs <- cantorSet()
 u <- unlist(cs)
 df <- data.frame(
   x_start = u[seq_along(u) %% 2L == 1L],
   x_end   = u[seq_along(u) %% 2L == 0L],
   depth   = unlist(lapply(cs, function(e) {
     l <- length(e)
     n <- 0
     while(l > 1) {
       n <- n + 1L
       l <- l / 2
     }
     rep(n, length(e) / 2)
   }))
 )
 require(ggplot2)
 g <- ggplot(df, aes_string(x = 'x_start', y = 'depth')) +
   geom_segment(aes_string(xend = 'x_end', yend = 'depth', size = 3)) +
   scale_y_continuous(trans = "reverse") +
   theme(
     axis.title = element_blank(),
     axis.line = element_blank(),
     axis.text = element_blank(),
     axis.ticks = element_blank(),
     legend.position = 'none',
     aspect.ratio = 1/5
   )
   list(graph = g, data = df, set = cs)

} </lang>

Output:

Currently, RosettaCode does not seem to accept image upload anymore. So, you will have to run the program under R or RStudio IDE to get the diagram result.

Racket

Translation of: Kotlin

<lang racket>#lang racket/base

{trans|Kotlin}}

(define current-width (make-parameter 81))

(define current-height (make-parameter 5))

(define (Cantor_set (w (current-width)) (h (current-height)))

 (define lines (build-list h (λ (_) (make-bytes w (char->integer #\#)))))
 (define (cantor start len index)
   (let* ((seg (quotient len 3))
          (seg-start (+ start seg))
          (seg-end (+ seg-start seg)))
     (unless (zero? seg)
       (for* ((i (in-range index h))
              (j (in-range seg-start seg-end)))
         (bytes-set! (list-ref lines i) j (char->integer #\space)))
       (cantor start seg (add1 index))
       (cantor seg-end seg (add1 index)))))
 (cantor 0 w 1)
 lines)

(module+ main

 (for-each displayln (Cantor_set)))

</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Raku

(formerly Perl 6)

Translation of: Kotlin

<lang perl6>sub cantor ( Int $height ) {

   my $width = 3 ** ($height - 1);
   my @lines = ( "\c[FULL BLOCK]" x $width ) xx $height;
   my sub _trim_middle_third ( $len, $start, $index ) {
       my $seg = $len div 3
           or return;
       for ( $index ..^ $height ) X ( 0 ..^ $seg ) -> ( $i, $j ) {
           @lines[$i].substr-rw( $start + $seg + $j, 1 ) = ' ';
       }
       _trim_middle_third( $seg, $start + $_, $index + 1 ) for 0, $seg * 2;
   }
   _trim_middle_third( $width, 0, 1 );
   return @lines;

}

.say for cantor(5);</lang>

Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

REXX

<lang rexx>/*REXX program displays an ASCII diagram of a Canter Set as a set of (character) lines. */ w= linesize() /*obtain the width of the display term.*/ if w==0 then w= 81 /*Can't obtain width? Use the default.*/

                  do lines=0;   _ = 3 ** lines  /*calculate powers of three  (# lines).*/
                  if _>w  then leave            /*Too large?  We passed the max value. */
                  #=_                           /*this value of a width─of─line is OK. */
                  end   /*lines*/               /* [↑]  calculate a useable line width.*/

w= # /*use the (last) useable line width. */ $= copies('■', #) /*populate the display line with blocks*/

                  do j=0  until #==0            /*show Cantor set as a line of chars.  */
                  if j>0  then do k=#+1  by  #+#  to w         /*skip 1st line blanking*/
                               $= overlay( left(, #), $, k)  /*blank parts of a line.*/
                               end   /*j*/
                  say $                         /*display a line of the Cantor Set.    */
                  #= # % 3                      /*the part (thirds) to be blanked out. */
                  end   /*j*/                   /*stick a fork in it,  we're all done. */</lang>

This REXX program makes use of   linesize   REXX program (or BIF) which is used to determine the screen width (or linesize) of the terminal (console).

Some REXXes don't have this BIF, so the   linesize.rex   REXX program is included here   ──►   LINESIZE.REX.

output   when using the default size of the terminal width of 100:

(Shown at half size.)

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■                           ■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■         ■■■■■■■■■                           ■■■■■■■■■         ■■■■■■■■■
■■■   ■■■         ■■■   ■■■                           ■■■   ■■■         ■■■   ■■■
■ ■   ■ ■         ■ ■   ■ ■                           ■ ■   ■ ■         ■ ■   ■ ■
output   when using the default size of the terminal width of 250:

(Shown at half size.)

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■                                                                                 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■                           ■■■■■■■■■■■■■■■■■■■■■■■■■■■                                                                                 ■■■■■■■■■■■■■■■■■■■■■■■■■■■                           ■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■         ■■■■■■■■■                           ■■■■■■■■■         ■■■■■■■■■                                                                                 ■■■■■■■■■         ■■■■■■■■■                           ■■■■■■■■■         ■■■■■■■■■
■■■   ■■■         ■■■   ■■■                           ■■■   ■■■         ■■■   ■■■                                                                                 ■■■   ■■■         ■■■   ■■■                           ■■■   ■■■         ■■■   ■■■
■ ■   ■ ■         ■ ■   ■ ■                           ■ ■   ■ ■         ■ ■   ■ ■                                                                                 ■ ■   ■ ■         ■ ■   ■ ■                           ■ ■   ■ ■         ■ ■   ■ ■

Ring

<lang ring>

  1. Project : Cantor set

load "guilib.ring" paint = null

new qapp

       {
       win1 = new qwidget() {
                 setwindowtitle("")
                 setgeometry(100,100,800,600)
                 label1 = new qlabel(win1) {
                             setgeometry(10,10,800,600)
                             settext("")
                 }
                 new qpushbutton(win1) {
                         setgeometry(150,500,100,30)
                         settext("draw")
                         setclickevent("draw()")
                 }
                 show()
       }
       exec()
       }

func draw

       p1 = new qpicture()
              color = new qcolor() {
              setrgb(0,0,255,255)
       }
       pen = new qpen() {
                setcolor(color)
                setwidth(10)
       }
       paint = new qpainter() {
                 begin(p1)
                 setpen(pen)
       cantor(10,20,600)
       endpaint()
       }
       label1 { setpicture(p1) show() }
       return

func cantor(x,y,lens)

       if lens >= 10
          paint.drawline(x,y,x+lens,y)
          y = y + 20
          cantor(x,y,floor(lens/3))
          cantor(x+floor(lens*2/3),y,floor(lens/3))
       ok

</lang> Output image:

Cantor set

Ruby

This works by numbering the segments (starting with 0) in base 3. Print whitespace if this number contains the digit 1; a black square otherwise. <lang ruby>lines = 5

(0..lines).each do |exp|

 seg_size = 3**(lines-exp-1)
 chars = (3**exp).times.map{ |n| n.digits(3).any?(1) ? " " : "█"}
 puts chars.map{ |c| c * seg_size }.join

end </lang>

Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Rust

<lang rust> use convert_base::Convert; use std::fmt;

struct CantorSet {

   cells: Vec<Vec<bool>>,

} fn number_to_vec(n: usize) -> Vec<u32> {

   // for the conversion we need the digits in reverse order
   // i.e the least significant digit in the first element of the vector
   n.to_string()
       .chars()
       .rev()
       .map(|c| c.to_digit(10).unwrap())
       .collect()

}

impl CantorSet {

   fn new(lines: usize) -> CantorSet {
       // Convert from base 10- to base 3
       let mut base = Convert::new(10, 3);
       let mut cells: Vec<Vec<bool>> = vec![];
       for line in 0..lines {
           // calculate how many repeating sequence will be in the given line
           let segment_size = 3_usize.pow((lines - line - 1) as u32);
           let segment: Vec<bool> = (0..3_usize.pow(line as u32))
               .map(|n| {
                   let output = base.convert::<u32, u32>(&number_to_vec(n));
                   // return false in case the base 3 number contains at least one "1"
                   // otherwise return true
                   !output.contains(&1)
               })
               .collect();
           // copy the segment "segment_size" time
           let mut accum: Vec<bool> = Vec::with_capacity(segment.len() * segment_size);
           for c in segment.iter() {
               accum.extend(std::iter::repeat(*c).take(segment_size))
           }
           cells.push(accum);
       }
       CantorSet { cells }
   }

}

impl fmt::Display for CantorSet {

   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
       for line in self.cells.iter() {
           for c in line {
               write!(f, "{}", if *c { "█" } else { " " })?
           }
           writeln!(f)?;
       }
       Ok(())
   }

} fn main() {

   let cs = CantorSet::new(5);
   println!("Cantor set:");
   println!("{}", cs);

}

</lang>

Output:
Cantor set:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Scala

Imperative Programming (Q&D)

<lang Scala>object CantorSetQD extends App {

 val (width, height) = (81, 5)
 val lines = Seq.fill[Array[Char]](height)(Array.fill[Char](width)('*'))
 def cantor(start: Int, len: Int, index: Int) {
   val seg = len / 3
   println(start, len, index)
   if (seg != 0) {
     for (i <- index until height;
          j <- (start + seg) until (start + seg * 2)) lines(i)(j) = ' '
     cantor(start, seg, index + 1)
     cantor(start + seg * 2, seg, index + 1)
   }
 }
 cantor(0, width, 1)
 lines.foreach(l => println(l.mkString))

}</lang>

Output:

See it in running in your browser by (JavaScript)

or by Scastie (JVM).

Functional Programming (Recommended)

<lang Scala>object CantorSetFP extends App {

 val (width, height) = (81, 5)
 def lines = (1 to height).map(_ => (0 until width).toSet)
 def cantorSet(pre: Seq[Set[Int]], start: Int, len: Int, index: Int): Seq[Set[Int]] = {
   val seg = len / 3
   def cantorSet1(pre: Seq[Set[Int]], start: Int, index: Int): Seq[Set[Int]] = {
     def elementsStuffing(pre: Set[Int], start: Int): Set[Int] =
       pre -- ((start + seg) until (start + seg * 2))
     for (n <- 0 until height)
       yield if (index to height contains n) elementsStuffing(pre(n), start)
       else pre(n)
   }
   if (seg == 0) pre
   else {
     def version0 = cantorSet1(pre, start, index)
     def version1 = cantorSet(cantorSet1(pre, start, index), start, seg, index + 1)
     cantorSet(version1, start + seg * 2, seg, index + 1)
   }
 }
 def output: Seq[Set[Int]] = cantorSet(lines, 0, width, 1)
 println(
   output.map(l => (0 to width).map(pos => if (l contains pos) '*' else ' ').mkString)
     .mkString("\n"))

}</lang>

Output:

See it in running in your browser by (JavaScript)

or by Scastie (JVM).

Sidef

Translation of: Raku

<lang ruby>func cantor (height) {

   var width = 3**(height - 1)
   var lines = height.of { "\N{FULL BLOCK}" * width }
   func trim_middle_third (len, start, index) {
       var seg = (len // 3) || return()
       for i, j in ((index ..^ height) ~X (0 ..^ seg)) {
           lines[i].replace!(Regex("^.{#{start + seg + j}}\\K."), ' ')
       }
       [0, 2*seg].each { |k|
           trim_middle_third(seg, start + k, index + 1)
       }
   }
   trim_middle_third(width, 0, 1)
   return lines

}

cantor(5).each { .say }</lang>

Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █

Smalltalk

Works with: GNU Smalltalk

Smalltalk represents Intervals' start and stop values as Fraction, so precision is kept for quilte a while. <lang Smalltalk>Object subclass: CantorSet [

   | intervals |
   CantorSet class >> new
       [^self basicNew
           initialize;
           yourself]
   initialize
       [intervals := Array with: (CantorInterval
           from: 0
           to: 1)]
   split
       [intervals := intervals gather: [:each | each split]]
   displayOn: aStream atScale: aNumber
       [| current |
       current := 0.
       intervals do:
           [:each |
           (each start - current) * aNumber timesRepeat: [aStream space].
           each length * aNumber timesRepeat: [aStream nextPut: $#].
           current := each stop].
       aStream nl]

]

Interval subclass: CantorInterval [

   split
       [| oneThird left right |
       oneThird := self length / 3.
       left := self class
           from: start
           to: start + oneThird.
       right := self class
           from: stop - oneThird
           to: stop.
       ^Array
           with: left
           with: right]
   start  [^start]
   stop   [^stop]
   length [^stop - start]
   printOn: aStream
       [aStream << ('%1[%2,%3]' % {self class name. start. stop})]

]

Object subclass: TestCantor [

   TestCantor class >> iterations: anInteger
       [| cantorset scale count |
       scale := 3 raisedTo: anInteger. "Make smallest interval 1"
       count := 0.
       cantorset := CantorSet new.
       [cantorset
           displayOn: Transcript
           atScale: scale.
       count < anInteger] whileTrue:
           [cantorset split.
           count := count + 1]]

]

TestCantor iterations: 4.</lang> Output:

#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

Visual Basic .NET

Translation of: C#

<lang vbnet>Module Module1

   Const WIDTH = 81
   Const HEIGHT = 5
   Dim lines(HEIGHT, WIDTH) As Char
   Sub Init()
       For i = 0 To HEIGHT - 1
           For j = 0 To WIDTH - 1
               lines(i, j) = "*"
           Next
       Next
   End Sub
   Sub Cantor(start As Integer, len As Integer, index As Integer)
       Dim seg As Integer = len / 3
       If seg = 0 Then
           Return
       End If
       For i = index To HEIGHT - 1
           For j = start + seg To start + seg * 2 - 1
               lines(i, j) = " "
           Next
       Next
       Cantor(start, seg, index + 1)
       Cantor(start + seg * 2, seg, index + 1)
   End Sub
   Sub Main()
       Init()
       Cantor(0, WIDTH, 1)
       For i = 0 To HEIGHT - 1
           For j = 0 To WIDTH - 1
               Console.Write(lines(i, j))
           Next
           Console.WriteLine()
       Next
   End Sub

End Module</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

Vlang

Translation of: Go

<lang vlang>const (

   width = 81
   height = 5

)

fn cantor(mut lines [][]u8, start int, len int, index int) {

   seg := len / 3
   if seg == 0 {
       return
   }
   for i in index.. height {
       for j in start + seg..start + 2 * seg {
           lines[i][j] = ' '[0]
       }
   }
   cantor(mut lines, start, seg, index + 1)
   cantor(mut lines, start + seg * 2, seg, index + 1)

}

fn main() { mut lines := [][]u8{len:height, init: []u8{len:width, init:'*'[0]}}

   cantor(mut lines, 0, width, 1)
   for line in lines {
       println(line.bytestr())
   }

}</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *


Wren

Translation of: Kotlin

<lang ecmascript>var width = 81 var height = 5

var lines = [[]] * height for (i in 0...height) lines[i] = ["*"] * width

var cantor // recursive so need to declare variable first cantor = Fn.new { |start, len, index|

   var seg = (len/3).floor
   if (seg == 0) return
   for (i in index...height) {
       for (j in (start+seg)...(start+seg*2)) lines[i][j] = " "
   }    
   cantor.call(start, seg, index + 1)
   cantor.call(start + seg*2, seg, index + 1)

}

cantor.call(0, width, 1) for (i in 0...height) System.print(lines[i].join())</lang>

Output:
*********************************************************************************
***************************                           ***************************
*********         *********                           *********         *********
***   ***         ***   ***                           ***   ***         ***   ***
* *   * *         * *   * *                           * *   * *         * *   * *

XPL0

<lang XPL0>proc Cantor(N, LineSeg, Len); \Delete middle third of LineSeg int N; char LineSeg; int Len, Third, I; [if N>0 and Len>1 then

   [Third:= Len/3;
   for I:= Third, 2*Third-1 do LineSeg(I):= ^ ;
   Cantor(N-1, LineSeg, Third);
   Cantor(N-1, LineSeg+2*Third, Third);
   ];

];

char LineSeg, N; [LineSeg:= "################################################################################# "; for N:= 0 to 4 do

   [Cantor(N, LineSeg, 81);
   Text(0, LineSeg);
   ];

]</lang>

Output:
#################################################################################
###########################                           ###########################
#########         #########                           #########         #########
###   ###         ###   ###                           ###   ###         ###   ###
# #   # #         # #   # #                           # #   # #         # #   # #

zkl

<lang zkl>const WIDTH=81, HEIGHT=5; var lines=HEIGHT.pump(List,List.createLong(WIDTH,"\U2588;").copy); // full block

fcn cantor(start,len,index){

  (seg:=len/3) or return();
  foreach i,j in ([index..HEIGHT-1], [start + seg .. start + seg*2 - 1]){
     lines[i][j]=" ";
  }
  cantor(start, seg, index + 1);
  cantor(start + seg*2, seg, index + 1);

}(0,WIDTH,1);

lines.pump(Console.println,"concat");</lang>

Output:
█████████████████████████████████████████████████████████████████████████████████
███████████████████████████                           ███████████████████████████
█████████         █████████                           █████████         █████████
███   ███         ███   ███                           ███   ███         ███   ███
█ █   █ █         █ █   █ █                           █ █   █ █         █ █   █ █