Is this square symmetrical?

23

1

Write a program or function that takes in a 4×4 text grid consisting of exactly 4 A's, 4 B's, 4 C's, and 4 D's, such as:

ACDC
BBCA
BADD
ABCD

The ABCD's may be in any arrangement but there will always be 4 of each. You can assume the input is valid. If desired you can also assume it has a trailing newline and/or that it comes as one line in reading order, e.g. ACDCBBCABADDABCD. You may also replace the characters ABCD with 0123 or 1234 respectively, if desired (but that's all).

Output a truthy value if the text grid has any form of reflective or rotational symmetry. Specifically:

  • If there is a central horizontal line of symmetry. e.g.

    BACD
    BACD 
    BACD \___ bottom mirrors top
    BACD /
    
  • If there is a central vertical line of symmetry. e.g.

    BCCB
    DAAD
    CAAC
    BDDB
      \/___ right mirrors left
    
  • If there is a diagonal line of symmetry (in either direction). e.g.

         ___ diagonally mirrored
        /
    ABDC
    BACD
    DCAB
    CDBA
        \___ diagonally mirrored
    
  • If there is 90° rotational symmetry. e.g.

    BDAB
    ACCD    same if rotated 90 degrees (or 180 or 270)
    DCCA
    BADB
    
  • If there is 180° rotational symmetry. e.g.

    DBCA
    BDCA    same if rotated 180 degrees
    ACDB
    ACBD
    

(Note that translational symmetry doesn't come into play here.)

Output a falsy value if the grid doesn't have one of the symmetries mentioned above. e.g. the very first example grid.

The shortest code in bytes wins.

Calvin's Hobbies

Posted 2016-08-18T07:54:55.510

Reputation: 84 000

Can we take a list of four strings as input? – Martin Ender – 2016-08-18T08:11:14.827

@MartinEnder Yes, alright. – Calvin's Hobbies – 2016-08-18T08:13:59.060

4I just read that and thought "nope" lol – Shaun Wild – 2016-08-18T08:22:56.467

Had you thought to tile the square then you could have taken translational symmetry into account too. – Neil – 2016-08-18T10:10:55.280

May I take input as an enclosed array? I.e. a single element, whose value is a 4×4 table? – Adám – 2016-08-18T12:04:50.150

1@Adám No. No more input formats. I feel I shouldn't really have allowed Martin's. – Calvin's Hobbies – 2016-08-18T12:31:10.713

@HelkaHomba Can you put up test cases in an easy-to-copy format? – mbomb007 – 2016-08-18T18:15:01.150

Answers

17

CJam, 16 bytes

{{z_W%_}4*;])e=}

An unnamed block which expects the input as a list of four strings on top of the stack and leaves a 0 (falsy) for asymmetric inputs and a positive integer (truthy) for symmetric inputs.

Test it here. Or run a full test suite.

Explanation

The symmetries of the square are the elements of the dihedral group of order 8 (which are just the 4 rotations of the square and the same 4 rotations of some reflected version of the square). It's not possible to generate this group from repeated application of a single permutation. But two reflections always give some rotation. Hence, the entire group can be generated by alternating between two reflections four times. (We just need to make sure that the two reflections give the 90 degree or 270 degree rotation, not 0 or 180.)

The challenge asks whether the input square is equal to any of the other 7 symmetries. So this answer just generates all of them and then checks whether the input is among the others.

{      e# Run this block 4 times.
  z_   e# Transpose the grid and duplicate it. (This is one type of reflection.)
  W%_  e# Reverse the lines and duplicate it. (This is another type of
       e# reflection. Together they rotate the grid by 90 degrees.)
}4*    e# The last element will be the original grid again.
;      e# Discard one copy of that original grid.
]      e# Wrap all symmetries in a list.
)      e# Pull off the original grid.
e=     e# Count how many times it appears among the other symmetries.

To see how the repeated application of z and W% generates all symmetries, have a look at this "diagram":

     0123
     4567
     89ab
     cdef

     original

 z   048c       W%       37bf
-->  159d  ----------->  26ae
     26ae                159d
     37bf                048c

     diag. refl.         rot. 90 degrees ccw

 z   3210       W%       fedc
-->  7654  ----------->  ba98
     ba98                7654
     fedc                3210

     vert. refl.        rot. 180 degrees

 z   fb73       W%       c840
-->  ea62  ----------->  d951
     d951                ea62
     c840                fb73

     antidiag. refl.     rot. 270 degrees ccw

 z   cdef       W%       0123
-->  89ab  ----------->  4567
     4567                89ab
     0123                cdef

     horiz. refl.        original

Martin Ender

Posted 2016-08-18T07:54:55.510

Reputation: 184 808

Wow, can you explain this? Do you have a built-in for all rotations/flips? – Adám – 2016-08-18T08:25:08.233

@Adám I'll add a full explanation in a bit, but z is transpose and W% reverses the lines, so I'm just generating all symmetries by repeated application of those. – Martin Ender – 2016-08-18T08:30:01.443

4There is, of course, nothing special about the first value, but sadly the more purist approach of counting whether you get 8 distinct values costs one char more. – Peter Taylor – 2016-08-18T10:54:29.443

8

Pyth, 11 bytes

<7.u?%Y2CN_

Test suite

This uses Martin's transpose and reverse technique, but with a twist. While other solutions have explicitly generated all 8 symmetries, then counted the number of appearances of the original, this program uses Pyth's .u function.

The .u function is the "Apply until repeat is found". In this case, we alternately transpose and reverse until a repetition happens, then accumulate the results into a list. Then, I remove the last 7 values, so there will only be a value remaining if there were no symmetries, and the first repetition happened after all 8 reflections and repetitions were generated.

Explanation:

<7.u?%Y2CN_
<7.u?%Y2CN_NQ    Implicit variables
                 Q = eval(input())
  .u        Q    Starting with Q, apply the following until a repeat occurs, 
                 then accumulate all values into a list.
    ?%Y2         If the iteration number is odd
        CN       Transpose
          _N     Else reverse
<7               Remove the last 7 results

isaacg

Posted 2016-08-18T07:54:55.510

Reputation: 39 268

5

05AB1E, 13 bytes

4Fø€JÂD}\\)¹å

Explanation

Uses the method expertly explained by Martin in his CJam answer.

4F     }       # 4 times do:
  ø€J          # zip and join each
     ÂD        # bifurcate, duplicate
        \\     # delete the top 2 items on the stack
          )    # wrap stack in list
           ¹å  # check if input is in that list

Try it online

Emigna

Posted 2016-08-18T07:54:55.510

Reputation: 50 798

4

Dyalog APL, 37 19 17 bytes

@ngn reduced it by 20 bytes!

8>≢(∪⊢,⌽¨,⍉¨)⍣≡⊂⎕

TryAPL online!

Adám

Posted 2016-08-18T07:54:55.510

Reputation: 37 779

⍉¨ instead of ⌽∘⍉¨ works too. – ngn – 2016-08-20T23:16:05.470

@ngn Done. Thanks. See you tomorrow. – Adám – 2016-08-21T08:19:18.517

4

Perl, 61 60 bytes

Includes +3 for -p0a

Give input square on STDIN, prints 0 for no symmetry, otherwise some positive number

./symmetry.pl
DBCA
BDCA
ACDB
ACBD
^D

symmetry.pl:

#!/usr/bin/perl -p0a
s,.,chop$F[$i++/($j-4?1:4)%4],eg;$\+=$$_++;++$j<8&&redo}{

Ton Hospel

Posted 2016-08-18T07:54:55.510

Reputation: 14 114

3

Brachylog, 38 36 bytes

@eL:1fbeL
(L;?:raL)(.;L$\.;L$/.;Lr.)

Try it online!

This expects a list of strings as input. This prints either true. or false..

Explanation

  • Main predicate:

    @eL    Split each line into a list of chars ; call that list of lists L
    :1f    Find all symmetries
    b      Remove the first one (the identity)
    eL     L is an element of that list of symmetries
    
  • Predicate 1: Output is one of the 8 symmetries of the input.

    (
        L             L = Input
    ;             Or
        ?:raL         L = reverse all lines of the input
    )
    (
        .             Output = L
    ;             Or
        L$\.          Output = transpose of L    
    ;             Or
        L$/.          Output = antitranspose of L
    ;             Or
        Lr.           Output = reverse of L
    )
    

Fatalize

Posted 2016-08-18T07:54:55.510

Reputation: 32 976

3

TSQL, 229 225 bytes

Be aware TSQL has no build-in for rotating, so this is part the code.

Returning -1 for true 0 for false

Golfed:

DECLARE @1 char(16)='BCCBDAADCAACBDDB'

DECLARE @i INT=0,@ char(16)=0WHILE @i<16SELECT
@=substring(@1,@i*4%16+@i/4+1,1)+@,@i+=1SELECT~count(*)/2FROM (SELECT
left(x,8)a,right(x,4)b,substring(x,9,4)c
FROM(values(@1),(@))x(x))x
WHERE a in(reverse(b+c),b+c,reverse(c+b))

Ungolfed:

DECLARE @1 char(16)='BCCBDAADCAACBDDB'

DECLARE @i INT=0,@ char(16)=0
WHILE @i<16
  SELECT @=substring(@1,@i*4%16+@i/4+1,1)+@,@i+=1
SELECT~count(*)/2
FROM
  (SELECT left(x,8)a,right(x,4)b,substring(x,9,4)c
   FROM(values(@1),(@))x(x))x
WHERE a in(reverse(b+c),b+c,reverse(c+b))

Fiddle

t-clausen.dk

Posted 2016-08-18T07:54:55.510

Reputation: 2 874

2

Python 2, 154 146 bytes

Checks if any of the necessary transformations is equivalent to the original using numpy arrays. Input is taken as a list of four strings.

from numpy import*
A=array(map(list,input()))
R=rot90
T=transpose(A)
print any([all(A==Z)for Z in(A[:,::-1],A[::-1],R(A),R(A,2),R(A,3),T,R(T,2))])

Try it online

Taking input as a single string is one char longer, with A=array(list(input())).reshape(4,4). A[:,::-1] is the same as fliplr(A). A[::-1] is the same as flipud(A).

mbomb007

Posted 2016-08-18T07:54:55.510

Reputation: 21 944

Maybe use map(list,input()) instead of [list(r)for r in input()] – Cyoce – 2016-08-18T18:35:48.510

@Cyoce Thanks. Idk how I missed that. – mbomb007 – 2016-08-18T22:00:17.893

any takes a generator expression, so you can save a few bytes by dropping the outer pair of square brackets. – TheBikingViking – 2016-08-19T20:17:04.727

@TheBikingViking I had already tried that. If you pass a generator, then it returns a generator, making the print statement not work. Try forking my online code and running it that way to see. – mbomb007 – 2016-08-19T20:32:52.990

Ah, ok. I didn't realise that it would break the print. – TheBikingViking – 2016-08-19T20:36:40.413

2

Python 3, 99 bytes

def f(x):
 t=x;c=[]
 for i in range(7):*t,=[map(''.join,zip(*t)),t[::-1]][i%2];c+=t,
 return x in c

A function that takes input, via argument, of a list of strings and returns True or False as relevant.

This uses the same approach as @MartinEnder's answer.

How it works

def f(x)                Function with input list of strings x
 t=x;c=[]               Initilaise temporary square t and combination list c
 for i in range(7):...  For i in range [0,6]:
 [...][i%2]              If i is even:
 zip(*t)                  transpose(t)
 *t,=map(''.join,...)     t = t with each line concatenated (such that t is in the same
                          format as x)
                         Else:
 *t,=t[::-1]              t = reverse(t)
 c+=t,                   Append t to c
 return x in c           Return True if x is in c else return False

Try it on Ideone

TheBikingViking

Posted 2016-08-18T07:54:55.510

Reputation: 3 674

2

JavaScript (ES6), 131 bytes

s=>[...`0101010`].map(c=>+c?``+b.reverse():`${b=b.map((s,i)=>s.replace(/./g,(_,j)=>b[j][i]))}`,b=a=s.match(/..../g)).includes(``+a)

17 bytes could be removed if you pass an array of 4 strings directly. I tried bit-twiddling (input in "0123301223011230" format) but that took me 199 bytes:

s=>[...'0101010'].map(c=>r=+c?r<<24&255<<24|r<<8&255<<16|r>>8&255<<8|r>>>24:r&0xc0300c03|r<<6&806093568|r<<12&3075<<16|r<<18&3<<24|r>>6&0xc0300c|r>>12&49200|r>>18&192,r=n=parseInt(s,4)|0).includes(n)

Neil

Posted 2016-08-18T07:54:55.510

Reputation: 95 035