Write A Program That Outputs Its Mirror Level

31

3

There are 95 printable ASCII characters:

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

In the Consolas font (the Stack Exchange code block default), some of the characters have mirrors around a vertical axis of symmetry:

  • These pairs of characters are mirrors of each other: () [] {} <> /\
  • These characters are mirrors of themselves: ! "'*+-.8:=AHIMOTUVWXY^_ovwx| (Note that space is one.)
  • These do not have mirrors: #$%&,012345679;?@BCDEFGJKLNPQRSZ`abcdefghijklmnpqrstuyz~

(i, l, 0, #, and probably other characters are their own mirrors in some fonts but we'll stick to the Consolas shapes.)

A string is said to be a mirror of itself if it is made with only the 39 mirror characters, arranged such that the string has a central vertical line of symmetry. So ](A--A)[ is a mirror of itself but ](A--A(] is not.

Write a one-line even-length program that is a mirror of itself. When N copies of its left half have been prepended to it and N copies of its right half have been appended to it, it should output N+1. N is a non-negative integer.

For example, if the program was ](A--A)[ (left half: ](A-, right half: -A)[), then:

  • Running ](A--A)[ should output 1. (N = 0)
  • Running ](A-](A--A)[-A)[ should output 2. (N = 1)
  • Running ](A-](A-](A--A)[-A)[-A)[ should output 3. (N = 2)
  • Running ](A-](A-](A-](A--A)[-A)[-A)[-A)[ should output 4. (N = 3)
  • . . .
  • Running ](A-](A-](A-](A-](A-](A-](A-](A-](A-](A--A)[-A)[-A)[-A)[-A)[-A)[-A)[-A)[-A)[-A)[ should output 10. (N = 9)
  • etc.

Rules

  • Output to stdout or your language's closest alternative. There may be an optional trailing newline. No input should be taken.
  • The process should theoretically work for N up to 215-1 or beyond, given enough memory and computing power.
  • A full program is required, not just a REPL command.

The shortest initial program (N = 0 case) in bytes wins.

Calvin's Hobbies

Posted 2015-05-28T15:49:30.797

Reputation: 84 000

In some fonts, # is its own relection as well, but, you're right, not in consolas. – SuperJedi224 – 2015-05-28T17:53:19.453

1Using repls is allowed? In other words: should we write a complete valid program or an expression is enough? I'm thinking about the Haskell entry which would work when running in ghci but isn't a valid complete program. – Bakuriu – 2015-05-29T04:34:04.697

@Bakuriu A full program is required. The Haskell answer is invalid. – Calvin's Hobbies – 2015-05-29T09:20:17.523

4Why aren't 'b' and 'd' mirrors of eachother? It makes my plan impossible :P – Thijs ter Haar – 2015-05-29T11:21:14.687

1@ThijsterHaar I actually didn't consider that but it looks like their Consolas shapes are just slightly different, sorry :P – Calvin's Hobbies – 2015-05-29T11:25:15.680

Would var rav;;var rav qualify for this? It would output nothing. In Javascript, null, undefined, false, 0 and '' all are 'the same'. So, technically, this would be outputting it's mirror level. If that qualified, would ;; qualify as well? – Ismael Miguel – 2015-05-29T23:14:23.497

@Calvin'sHobbies What do you mean? – Ismael Miguel – 2015-05-29T23:28:45.653

But 0 is positive AND negative... – Ismael Miguel – 2015-05-29T23:31:35.110

Let us continue this discussion in chat.

– Calvin's Hobbies – 2015-05-29T23:39:52.933

Answers

20

Pip, 12 8 4 bytes

Now with 66% fewer bytes!

x++x
  • x is a variable, preinitialized to "". In numeric context, this becomes 0.
  • The first half, sans the final +, makes an expression of the form x+x+...+x. This is a valid statement that does nothing.
  • The second half, including the final + from the first half, makes an expression of the form ++x+x+...+x. ++x increments x to 1, and the rest adds it to itself N times. Because expressions are evaluated left-to-right in Pip, the increment is guaranteed to happen first, and the result is equal to the number of mirror levels.
  • At the end, the value of this expression is auto-printed.

Unfortunately, Pip doesn't do well processing huge expressions: this solution causes a maximum recursion depth exceeded error for N above 500 or so. Here's a previous solution that doesn't, for 8 bytes:

x++oo++x

More on Pip

DLosc

Posted 2015-05-28T15:49:30.797

Reputation: 21 213

OK, unless somebody posts a 2 byte answer, looks like you got this one in the bag. By the way, I don't know if you've run it with N = 32767, but the actual output is Fatal error: maximum recursion depth exceeded while calling a Python object. – Dennis – 2015-05-30T18:19:52.237

@Dennis Yeah, I ran into that actually--it starts quite early, around 600 if not before. The reason is that expressions are evaluated by recursively evaluating all subexpressions first, so x+x+...+x generates O(N) depth of recursion. Maybe that invalidates this answer. I'll add a note. – DLosc – 2015-05-30T18:40:19.543

The recursion limit is easily adjustable in Python, isn't it? – Dennis – 2015-05-30T18:56:49.343

@Dennis Yes, although there are dire warnings about what it'll do to your system if you set it too high, so I've never tried it. ;) Besides, configuring it isn't possible from within Pip, so it seems kinda like cheating to me. If you want to try it, though, I'd be interested in hearing the results.

– DLosc – 2015-05-30T19:05:38.697

I've tried. On my machine, increasing the recursion limit to 20000 already segfaults. But since the answer has to work only given enough memory and computing power, that shouldn't be a problem. – Dennis – 2015-05-30T19:43:05.353

Ah, that's helpful. I hadn't noticed theoretically in the problem description. – DLosc – 2015-05-30T19:46:40.017

@DLosc About those dire warnings - Pyth's recursion limit is purposefully set high enough that it regularly segfaults. I've never noticed any other issue. – isaacg – 2015-05-31T10:36:00.493

@isaacg Good to know. ("Dire" was a bit of hyperbole on my part. I just didn't know if it would cause system instability or not.) – DLosc – 2015-05-31T23:07:24.143

Fun fact: trying to evaluate an expression that long even crashes Python. – DLosc – 2015-05-31T23:12:32.577

34

GolfScript, 10 bytes

!:{)::(}:!

Try it online with Web Golfscript: N = 0, N = 1, N = 2, N = 3, N = 41

Web GolfScript has a 1024 character limit, but the Ruby interpreter handles N = 32767 perfectly:

$ curl -Ss http://www.golfscript.com/golfscript/golfscript.rb > golfscript.rb
$ echo '"!:{):"32768*":(}:!"32768*' | ruby golfscript.rb > mirror-level-32767.gs
$ ruby golfscript.rb mirror-level-32767.gs
32768

How it works

Without any input, GolfScript initially has an empty string on the stack.

In the first left half, the following happens:

  • ! applies logical NOT to the empty string. This pushes 1.

  • :{ saves the integer on the stack in the variable {.

    Yes, that is a valid identifier, although there's no way to retrieve the stored value.

  • ) increments the integer on the stack.

  • : is an incomplete instruction.

The the subsequent left halves, the following happens:

  • :! (where : is a leftover from before) saves the integer on the stack in the variable !.

    Yes, that is also a valid identifier. This breaks the ! command, but we don't use it anymore.

  • :{, ) and : work as before.

In the first right half, the following happens:

  • :: (where : is a leftover from before) saves the integer on the stack in the variable :.

    Yes, even that is valid identifier. As with {, there's no way to retrieve the stored value.

  • ( decrements the integer on the stack, yielding the number of left halves.

  • }, since it is unmatched and terminates execution immediately.

    This is an undocumented feature. I call them supercomments.

The remaining code is simply ignored.

Dennis

Posted 2015-05-28T15:49:30.797

Reputation: 196 637

It seems really weird to have an unmatched } in the 2nd half of your code in a mirror competition. – ballesta25 – 2015-05-28T20:19:09.010

It's a common trick in palindrome variants. In "\""/", the fourth double quote would be unmatched as well since the second one has been escaped. – Dennis – 2015-05-28T20:35:39.100

27

Z80 Machine Code, 8 6 bytes*

<8ww8> * Assumes certain conditions by entering from Amstrad BASIC

<   INC A         // A=A+1
8w  JR C, #77     ## C is unset unless A has overflowed, does nothing

w   LD (HL), A    // Saves A to memory location in HL (randomly initialised)
8>  JR C, #3E     ## C is still unset, does nothing

A is initially 0 when entered from BASIC. It increments A n times, then writes it n times to the same memory location (which is set to a slightly random location by BASIC)! The JR Jump Relative operation never does anything since the C flag is always unset, so is used to "comment out" the following byte! This version is slightly cheating by assuming certain entry conditions, namely entering from BASIC guarantees that A is always 0. The location of (HL) is not guaranteed to be safe, and in fact, is probably a dangerous location. The below code is much more robust which is why it's so much longer.

Z80 Machine Code, 30 bytes

As ASCII: o!.ww.!>A=o>{))((}<o=A<!.ww.!o

Basically, the first half guarantees creation of a zero value and the second half increments it and writes it to memory. In the expanded version below ## denotes code that serves no purpose in its half of the mirror.

o   LD L, A       ## 
!.w LD HL, #772E  // Load specific address to not corrupt random memory!
w   LD (HL), A    ## Save random contents of A to memory
.!  LD L, #21     ## 
>A  LD A, #41     // A=#41
=   DEC A         // A=#40
o   LD L, A       // L=#40
>{  LD A, #7B     ## 
)   ADD HL, HL    // HL=#EE80
)   ADD HL, HL    // HL=#DD00. L=#00 at this point

((  JR Z, #28     ## 
}   LD A, L       // A=L
<   INC A         // A=L+1
o   LD L, A       // L=L+1
=   DEC A         // A=L
A   LD B, C       ## 
<   INC A         // A=L+1
!.w LD HL, #772E  // Load address back into HL
w   LD (HL), A    // Save contents of A to memory
.!  LD L, #21     ## 
o   LD L, A       // L=A

Breakdown of allowed instructions:

n   op    description
--  ----  -----------
28  LD    LoaD 8-bit or 16-bit register
3   DEC   DECrement 8-bit or 16-bit register
1   INC   INCrement 8-bit or 16-bit register
1   ADD   ADD 8-bit or 16-bit register

Available but useless instructions:
3   JR    Jump Relative to signed 8-bit offset
1   DAA   Decimal Adjust Accumulator (treats the A register as two decimal digits
          instead of two hexadecimal digits and adjusts it if necessary)
1   CPL   1s ComPLement A
1   HALT  HALT the CPU until an interrupt is received

Out of the 39 instructions allowed, 28 are load operations (the block from 0x40 to 0x7F are all single byte LD instructions), most of which are of no help here! The only load to memory instruction still allowed is LD (HL), A which means I have to store the value in A. Since A is the only register left with an allowed INC instruction this is actually quite handy!

I can't load A with 0x00 to start with because ASCII 0x00 is not an allowed character! All the available values are far from 0 and all mathematical and logical instructions have been disallowed! Except... I can still do ADD HL, HL, add 16-bit HL to itself! Apart from directly loading values (no use here!), INCrementing A and DECrementing A, L or HL this is the only way I have of changing the value of a register! There is actually one specialised instruction that could be helpful in the first half but a pain to work around in the second half, and a ones-complement instruction that is almost useless here and would just take up space.

So, I found the closest value to 0 I could: 0x41. How is that close to 0? In binary it is 0x01000001. So I decrement it, load it into L and do ADD HL, HL twice! L is now zero, which I load back into A! Unfortunately, the ASCII code for ADD HL, HL is ) so I now need to use ( twice. Fortunately, ( is JR Z, e, where e is the next byte. So it gobbles up the second byte and I just need to make sure it doesn't do anything by being careful with the Z flag! The last instruction to affect the Z flag was DEC A (counter-intuitively, ADD HL, HL doesn't change it) and since I know that A was 0x40 at that point it's guaranteed that Z is not set.

The first instruction in the second half JR Z, #28 will do nothing the first 255 times because the Z flag can only be set if A has overflowed from 255 to 0. After that the output will be wrong, however since it's only saving 8-bits values anyway that shouldn't matter. The code shouldn't be expanded more than 255 times.

The code has to be executed as a snippet since all available ways of returning cleanly have been disallowed. All the RETurn instructions are above 0x80 and the few Jump operations allowed can only jump to a positive offset, because all 8-bit negative values have been disallowed too!

CJ Dennis

Posted 2015-05-28T15:49:30.797

Reputation: 4 104

6WHAT. WHAT IS THIS. – cloudfeet – 2015-05-29T16:28:47.070

4Now I've seen this answer, using GolfScript/J/etc. just seems like cheating. :p – cloudfeet – 2015-05-29T16:29:28.050

Are there Z80-compatible processors with a 16-bit A register? I'm asking since the question requires the code has to work for N = 32767. – Dennis – 2015-05-29T17:12:06.580

1@Dennis For a program to work for N = 32767 it must be at least 2 x 32767 or 65534 bytes long. The Z80 can only address 65536 bytes of memory, making this an impossible task since I don't believe I can make the program smaller than 6 bytes! The A register is always 8 bits, otherwise the processor would not be compatible with the Z80. I would say that given enough memory and computing power has been covered off here! – CJ Dennis – 2015-05-30T02:22:39.523

16-bit version using forbidden instruction # (INC HL): o#8"}88{"8#o (12 bytes). Can fit into memory 5461 times but due to safe memory restrictions can actually only go 2800 times. – CJ Dennis – 2015-05-30T03:20:17.690

It was more of a curiosity. I'm not the one to judge this answer's validity and it's certainly ingenious, but you'll have to admit that using machine code that, by definition, cannot work for a processor that understands it is a little bold. Then again, this is code golf, where rules are meant to be bent. – Dennis – 2015-05-30T19:40:53.657

@Dennis What do you mean by code that doesn't work for the processor? All the code I've posted runs just fine on a Z80. If you're referring to "a 16-bit A register" then that is the register pair HL. The only solutions I've found using HL (for a 16-bit version) either use instructions forbidden by this contest or output more than just the number required, which I believe would make it invalid. I believe my answer fully complies with the spirit of this question, even if not entirely the letter. – CJ Dennis – 2015-05-31T00:47:59.427

I meant that, while your code works for small values of N and could in theory work for larger ones, the compatible processors are incapable of handling the largest test case. I fully agree wirh your last sentence. – Dennis – 2015-05-31T01:26:11.343

1@Dennis You understand why no Z80 compatible processor will have an A register of anything other than 8 bits? Changing it to 16-bits would break code relying on 255+1=0 for example. You would have to invent a CPU, lets call it the Z160, that uses a default 16-bit register but still uses the same 8-bit instruction set from the Z80. Weird! – CJ Dennis – 2015-05-31T02:01:04.330

19

J, 16 14 bytes

(_=_)]++[(_=_)

Usages:

   (_=_)]++[(_=_)
1
   (_=_)]+(_=_)]++[(_=_)+[(_=_)
2
   (_=_)]+(_=_)]+(_=_)]++[(_=_)+[(_=_)+[(_=_)
3

Explanation:

  • J evaluates from right to left.

  • (_=_) is inf equals inf which is true, has a value of 1, so the expression becomes 1+]...[+1. ((8=8) would also work but this looks cooler. :))

  • [ and ] return their the left and right arguments respectively if they have 2 arguments. If they get only 1 they return that.

  • + adds the 2 arguments. If it gets only 1 it returns that.

Now let's evaluate a level 3 expression (going from right to left):

(_=_)]+(_=_)]++[(_=_)+[(_=_)  NB. (_=_) is 1

1]+1]+1]++[1+[1+[1  NB. unary [, binary +

1]+1]+1]++[1+[2  NB. unary [, binary +

1]+1]+1]++[3  NB. unary [, unary +

1]+1]+1]+3  NB. unary +, binary ]

1]+1]+3  NB. unary +, binary ]

1]+3  NB. unary +, binary ]

3

As we see the right half of the 1's gets added and the left side of the 1's gets omitted resulting in the desired integer N, the mirror level.

Try it online here.

randomra

Posted 2015-05-28T15:49:30.797

Reputation: 19 909

12

Haskell, 42 bytes

(8-8)-(-(8-8)^(8-8))--((8-8)^(8-8)-)-(8-8)

Luckily a line comment in Haskell (-> --) is mirrorable and half of it (-> -) is a valid function. The rest is some math to get the numbers 0 and 1. Basically we have (0)-(-1) with a comment for N=0 and prepend (0)-(-1)- in each step.

If floating point numbers are allowed for output, we can build 1 from 8/8 and get by with 26 bytes:

Haskell, 26 bytes

(8-8)-(-8/8)--(8\8-)-(8-8)

Outputs 1.0, 2.0, etc.

nimi

Posted 2015-05-28T15:49:30.797

Reputation: 34 639

2This is technically invalid since it must be a full program. – Calvin's Hobbies – 2015-05-29T12:48:39.990

@Calvin'sHobbies: I see. Do we have consensus on the minimum requirements for a full program? I've searched meta, but only found a discussion about functions, not programs. Depending on the definition of a full program I might be able to fix my solution. – nimi – 2015-05-29T15:05:21.073

I'd call it a full program if you can save it in a file program.hs and then run $ runhaskell program.hs from the command line and see the output. I don't know Haskell so I can't say exactly what needs to change. – Calvin's Hobbies – 2015-05-29T15:13:13.270

2@Calvin'sHobbies: runhaskell is a shell script that sets up some environment and finally calls ghc, the Haskell compiler. You can run my code directly with ghc: ghc -e "(8-8)-(-8/8)--(8\8-)-(8-8)". This launches ghc which evaluates the code provided as an argument, prints the result and exits. No REPL, no interaction. Of course this would add +1 to the byte count for -e. – nimi – 2015-05-29T15:26:51.890

@nimi: -e doesn't contribute to the score in this case. We don't count bytes for perl -E or gcc -std=c99 either. – Dennis – 2015-05-29T16:57:34.167

11

CJam, 14 bytes

]X+:+OooO+:+X[

Try it online in the CJam interpreter: N = 0, N = 1, N = 2, N = 3, N = 41

Note that this code finishes with an error message. Using the Java interpreter, that error message can be suppressed by closing or redirecting STDERR.1

How it works

In the left halves, the following happens:

  • ] wraps the entire stack in an array.

  • X appends 1 to that array.

  • :+ computes the sum of all array elements.

  • Oo prints the contents of empty array (i.e., nothing).

In the first right half, the following happens:

  • o prints the integer on the stack, which is the desired output.

  • O+ attempts to append an empty array to the topmost item of the stack.

    However, the stack was empty before pushing O. This fails and terminates the execution of the program.

The remaining code is simply ignored.

1According to the meta poll Should submissions be allowed to exit with an error?, this is allowed.

Dennis

Posted 2015-05-28T15:49:30.797

Reputation: 196 637

I'd be skeptical on accepting this due to the error thing, but since its not winning I'm not too concerned. – Calvin's Hobbies – 2015-05-28T20:41:16.797

Tasks like this one are suprisingly hard in CJam, considering that it is a golfing language. Even a syntax error (e.g., an undefined operator) in a unexecuted block will prevent the entire program from executing. I'm still trying to get rid of the error. – Dennis – 2015-05-28T21:04:32.243