25
\$\begingroup\$

You're given a hexagonal grid of the characters . and #, like this:

 . . . . . . . .
. . . . # . . . 
 . # . . . # . .
. . . # . . . . 
 . . . . . # . .
. . . . . . . . 

Your task is to fill the entire axis-aligned bounding box of the # with further #:

 . . . . . . . .
. . # # # # . . 
 . # # # # # . .
. . # # # # # . 
 . . # # # # . .
. . . . . . . . 

The axis-aligned bounding box is the smallest convex hexagonal shape which contains all the #. Note that in the case of the hexagonal grid, there are three axes to consider (W/E, SW/NE, NW/SE):

enter image description here

Here is another example to show that in some cases, one or more sides will contain only one #:

. . . . . . . .         . . . . . . . . 
 . # . . . . . .         . # # # # . . .
. . . . . # . .         . . # # # # . . 
 . . # . . . . .         . . # # # . . .
. . . . . . . .         . . . . . . . . 

You can either view these as hexagons with degenerate sides, or you can draw the bounding box around them, like I have done above, in which case they are still hexagons:

enter image description here

Too hard? Try Part I!

Rules

You may use any two distinct non-space printable ASCII characters (0x21 to 0x7E, inclusive), in place of # and .. I'll continue referring to them as # and . for the remainder of the specification though.

Input and output may either be a single linefeed-separated string or a list of strings (one for each line), but the format has to be consistent.

You may assume that the input contains at least one # and all lines are the same length. Note that there are two different "kinds" of lines (starting with a space or a non-space) - you may not assume that the input always starts with the same type. You may assume that the bounding box always fits inside the grid you are given.

You may write a program or a function and use any of the our standard methods of receiving input and providing output.

You may use any programming language, but note that these loopholes are forbidden by default.

This is , so the shortest valid answer – measured in bytes – wins.

Test Cases

Each test case has input and output next to each other.

#    #

 . .      . . 
# . #    # # #
 . .      . . 

 . #      . # 
. . .    . # .
 # .      # . 

 # .      # . 
. . .    . # .
 . #      . # 

 # .      # . 
# . .    # # .
 . #      # # 

 . #      # # 
# . .    # # #
 . #      # # 

. . #    . # #
 . .      # # 
# . .    # # .

# . .    # # .
 . .      # # 
. . #    . # #

. . . . . . . .         . . . . . . . . 
 . . # . # . . .         . . # # # . . .
. . . . . . . .         . . . # # . . . 
 . . . # . . . .         . . . # . . . .

. . . . . . . .         . . . . . . . . 
 . . # . . . # .         . . # # # # # .
. . . . . . . .         . . . # # # # . 
 . . . # . . . .         . . . # # # . .

. . . . . . . .         . . . . . . . . 
 . # . . . . . .         . # # # # . . .
. . . . . # . .         . . # # # # . . 
 . . . . . . . .         . . . . . . . .

. . . . . . . .         . . . . . . . . 
 . # . . . . . .         . # # # # . . .
. . . . . # . .         . . # # # # . . 
 . . # . . . . .         . . # # # . . .

. . . . # . . .         . . # # # # . . 
 . # . . . # . .         . # # # # # . .
. . . # . . . .         . . # # # # # . 
 . . . . . # . .         . . # # # # . .
\$\endgroup\$
19
  • 1
    \$\begingroup\$ My head is spinning trying to find any obvious pattern. You said 'hexagonal' but there are only two inputs form into hexagons in the test cases. I'm lost. \$\endgroup\$ Aug 29, 2016 at 12:04
  • 1
    \$\begingroup\$ @Anastasiya-Romanova秀 If you picture the shape as going through the centres of the outer characters, then yes some hexagons will have degenerate sides (like in the rectangular grid, where you can get cases where the rectangle reduces to a line). However, if you draw the rectangle around the characters (as I have done in the diagram), all the examples are hexagons (some of which have very short sides). \$\endgroup\$ Aug 29, 2016 at 12:06
  • 1
    \$\begingroup\$ @Anastasiya-Romanova秀 Does the new diagram help? \$\endgroup\$ Aug 29, 2016 at 12:13
  • 3
    \$\begingroup\$ I! looks like II if I have the wrong glasses on.. \$\endgroup\$
    – Neil
    Aug 29, 2016 at 12:17
  • 1
    \$\begingroup\$ @Neil Or, you know, too much alcohol ;) \$\endgroup\$
    – ThreeFx
    Aug 29, 2016 at 12:35

8 Answers 8

6
\$\begingroup\$

Pyth, 82 71 bytes

L,hbebMqH@S+GH1KhMyJs.e,Lkfq\#@bTUb.zA,ySm-FdJySsMJj.es.eXW&&gKkgG-kYgH+kYZ\.\#b.z
MqH@S[hGHeG)1j.es.eXW&&ghMJs.e,Lkfq\#@bTUb.zkgSm-FdJ-kYgSsMJ+kYZ\.\#b.z

Try it online!

Explanation

  • Let A be the point with the lowest y-coordinate and B the point with the highest y-coordinate.

  • Let C be the point with the lowest (x-value minus y-value) and D the point with the highest.

  • Let E be the point with the lowest (x-value plus y-value) and F the point with the highest.

Then it is equivalent to finding the coordinates which the y-coordinate is between A and B, the x-value minus y-value is between C and D, and the x-value plus y-value is between E and F.

\$\endgroup\$
3
  • \$\begingroup\$ the first time when I could post a solution earlier, if only the SE android app could correctly handle tab characters (for some reason they disappeared when pasted) :/ \$\endgroup\$ Aug 29, 2016 at 15:35
  • \$\begingroup\$ @SargeBorsch I'm sorry :( \$\endgroup\$
    – Leaky Nun
    Aug 29, 2016 at 15:41
  • \$\begingroup\$ haha why, it's SE Android app that made me fail :D \$\endgroup\$ Aug 29, 2016 at 15:52
5
\$\begingroup\$

Haskell, 256 254 243 bytes

import Data.List
f=z(\l->(,).(,))[0..]l)[0..]
q l=m(m(\e->min(snd e).(".#"!!).fromEnum.and.z($)(m(\x y->y>=minimum x&&y<=maximum x).transpose.m b.filter((==)'#'.snd).concat$l)$b e))l
b=(m uncurry[const,(-),(+)]<*>).pure.fst
z=zipWith
m=map
q.f

Thanks @Damien for golfing f!

Input is taken as list of list of chars, output is provided the same way.

Soo this was a beast to write. It's based on LeakyNun's idea using a maximum and minimum based filtering on the coordinates of the items.

I'm really surprised by the fact that m=map actually saves bytes since it seems so costly.


Explanation:

Here's a slightly less butchered version (emphasis on slightly):

import Data.List
f=zipWith(\y l->zipWith(\x e->((y,x),e))[0..]l)[0..]
p=map(\x y->y>=minimum x&&y<=maximum x).transpose.map b.filter((==)'#'.snd).concat
q l=map(map(\e->min(snd e).(".#"!!).fromEnum.and.zipWith($)(p$l)$b e))l
b=(map uncurry[const,(-),(+)]<*>).pure.fst
  • f is a function which assigns each char an index (y-index, x-index) while preserving the original structure of the list.

  • b: Given an item of the indexed list, b computes [y-index, y - x, y + x].

  • p: Given the indexed field, return 3 functions Int -> Bool, the first of which is the check of the y-index, the second of the difference and the third of the sum. min(snd e) takes care of the spaces (a space is smaller than both). This function is inlined in the golfed code.

  • q given the indexed field, change all necessary . to # by checking if that specific field return True to every test function.

The final solution is then the composition of q and f.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ f=z(\y->z((,).(,)y)[0..])[0..] \$\endgroup\$
    – Damien
    Sep 1, 2016 at 7:06
  • \$\begingroup\$ or h x=z x[0..] f=h$h.curry(,) \$\endgroup\$
    – Damien
    Sep 1, 2016 at 7:22
5
\$\begingroup\$

Jelly, 45 3513 42 41 bytes

Ṁ€»\
ṚÇṚ«Çṁ"
ŒDṙZL$ÇṙL’$ŒḌ«Ç
ṚÇṚ«Ç
n⁶aÇo⁶

This is a list of links; the last one has to be called on the input to produce the output.

I/O is in form of string arrays, where . indicates empty and @ indicates filled.

Try it online! or verify all test cases.

Background

Let's consider the following example.

. . . . . . . . 
 . @ . . . . . .
. . . . . @ . . 
 . . @ . . . . .

By drawing a pair or parallel lines – the closest pair that encloses all filled positions – in each of the three directions, we can determine the hexagonal bounding box.

In the implementation, we replace all characters between those two lines with @, and everything outside these lines with ., with the possible exception of diagonals that only contains spaces).

For the horizontal axis, this gives

................
@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@

for the falling diagonal axis, it gives

..@@@@@@@......
...@@@@@@@......
....@@@@@@@.....
 ....@@@@@@@....

and for the raising diagonal axis, it gives

....@@@@@@@@@...
...@@@@@@@@@....
..@@@@@@@@@....
.@@@@@@@@@.... .

By taking the character-wise minimum of all three, since . < @, we get

...............
...@@@@@@@......
....@@@@@@@....
 ....@@@@@.... .

All that's left to do is restoring the spaces.

How it works

n⁶aÇo⁶           Main link. Argument: A (array of strings)

n⁶               Not-equal space; yield 0 for spaces, 1 otherwise.
  aÇ             Take the logical AND with the result the 4th helper link.
                 This will replace 1's (corresponding to non-space characters) with
                 the corresponding character that result from calling the link.
    o⁶           Logical OR with space; replaces the 0's with spaces.
ṚÇṚ«Ç            4th helper link. Argument: A

Ṛ                Reverse the order of the strings in A.
 Ç               Call the 3rd helper link.
  Ṛ              Reverse the order of the strings in the resulting array.
    Ç            Call the 3rd helper link with argument A (unmodified).
   «             Take the character-wise minimum of both results.
ŒDṙZL$ÇṙL’$ŒḌ«Ç  3rd helper link. Argument: L (array of strings)

ŒD               Yield all falling diagonals of L. This is a reversible operation,
                 so it begins with the main diagonal.
   ZL$           Yield the length of the transpose (number of columns).
  ṙ              Shift the array of diagonals that many units to the left.
                 This puts the diagonals in their natural order.
      Ç          Call the helper link on the result.
        L’$      Yield the decremented length (number of columns) of L.
       ṙ         Shift the result that many units to the left.
                 This puts the changed diagonals in their original order.
           ŒḌ    Undiagonal; reconstruct the string array.
              Ç  Call the 2nd helper link with argument L (unmodified).
             «   Take the character-wise minimum of both results.
ṚÇṚ«Çṁ"          2nd helper link. Argument: M (array)

Ṛ                Reverse the rows of M.
 Ç               Call the 1st helper link on the result.
  Ṛ              Reverse the rows of the result.
    Ç            Call the 1nd helper link with argument M (unmodified).
   «             Take the minimum of both results.
     ṁ"          Mold zipwith; repeat each character in the result to the left
                 as many times as needed to fill the corresponding row of M.
Ṁ€»\             1st helper link. Argument: N (array)

Ṁ€               Take the maximum of each row of N.
  »\             Take the cumulative maxima of the resulting characters.
\$\endgroup\$
4
\$\begingroup\$

Python 3, 380 378 348 346 bytes

Note that the indentation is with tabs, not spaces.

Golfed version:

def s(i):
    L=i.splitlines();E=enumerate;A=lambda x,y:(y,x+y,x-y);N=(2**64,)*3;X=(-2**64,)*3
    for y,l in E(L):
        for x,c in E(l):
            if c=='#':p=A(x,y);X=tuple(map(max,X,p));N=tuple(map(min,N,p))
    R=''
    for y,l in E(L):
        for x,c in E(l):
            if c!='.':R+=c
            else:p=A(x,y);f=all(N[j]<=p[j]<=X[j]for j in range(0,3));R+='.#'[f]
        R+='\n'
    return R

Test it on Ideone

Explanation (for ungolfed version below):

All processing is done without any conversion, space characters are simply skipped.
Function axes_pos calculates 3-tuple of imaginary "3D" coordinates, they are accumulated into (element wise) minimum and maximum 3-tuples (bmin, bmax) for all # characters.

Coordinates are calculated in def axes_pos(x, y): return y, x + y, lc - y + x;
where X counts from 0 to right, and Y counts from 0 to bottom (from first line to last).
First imaginary coordinate is basically Y, because it's obvious why. Its axe is orthogonal to green bounds (in the OP's pictures)
Second is orthogonal to red bounds, and third is orthogonal to blue bounds.

In the second pass, replacement is done for all . characters which "3D" coordinates fall into bmin..bmax range, element wise — this is checked in this expression all(bmin[j] <= p[j] <= bmax[j] for j in range(0, 3)).

Ungolfed version with tests, also on Ideone:

def solve(i):
    ls = i.splitlines()
    lc = len(ls)

    def axes_pos(x, y):
        return y, x + y, lc - y + x

    I = 2 ** 64
    bmin = (I, I, I)
    bmax = (0, 0, 0)

    for y, line in enumerate(ls):
        for x, char in enumerate(line):
            if char != '#': continue
            p = axes_pos(x, y)
            bmax = tuple(map(max, bmax, p))
            bmin = tuple(map(min, bmin, p))

    result = ''
    for y, line in enumerate(ls):
        for x, char in enumerate(line):
            if char != '.':
                result += char
            else:
                p = axes_pos(x, y)
                f = all(bmin[j] <= p[j] <= bmax[j] for j in range(0, 3))
                result += '#' if f else char
        result += '\n'

    return result


def run_test(a, b):
    result = solve(a)
    if result != b:
        raise AssertionError('\n' + result + '\n\nshould be equal to\n\n' + b)


def run_tests():
    run_test(
        "#\n",

        "#\n")

    run_test(
        " . . \n"
        "# . #\n"
        " . . \n",

        " . . \n"
        "# # #\n"
        " . . \n")

    run_test(
        " . # \n"
        ". . .\n"
        " # . \n",

        " . # \n"
        ". # .\n"
        " # . \n")

    run_test(
        " # . \n"
        ". . .\n"
        " . # \n",

        " # . \n"
        ". # .\n"
        " . # \n")

    run_test(
        " # . \n"
        "# . .\n"
        " . # \n",

        " # . \n"
        "# # .\n"
        " # # \n")

    run_test(
        " . # \n"
        "# . .\n"
        " . # \n",

        " # # \n"
        "# # #\n"
        " # # \n")

    run_test(
        ". . . . . . . . \n"
        " . . # . # . . .\n"
        ". . . . . . . . \n"
        " . . . # . . . .\n",

        ". . . . . . . . \n"
        " . . # # # . . .\n"
        ". . . # # . . . \n"
        " . . . # . . . .\n")

    run_test(
        ". . . . . . . . \n"
        " . . # . . . # .\n"
        ". . . . . . . . \n"
        " . . . # . . . .\n",

        ". . . . . . . . \n"
        " . . # # # # # .\n"
        ". . . # # # # . \n"
        " . . . # # # . .\n")

    run_test(
        ". . . . . . . . \n"
        " . # . . . . . .\n"
        ". . . . . # . . \n"
        " . . . . . . . .\n",

        ". . . . . . . . \n"
        " . # # # # . . .\n"
        ". . # # # # . . \n"
        " . . . . . . . .\n")

    run_test(
        ". . . . . . . . \n"
        " . # . . . . . .\n"
        ". . . . . # . . \n"
        " . . # . . . . .\n",

        ". . . . . . . . \n"
        " . # # # # . . .\n"
        ". . # # # # . . \n"
        " . . # # # . . .\n")

    run_test(
        ". . . . # . . . \n"
        " . # . . . # . .\n"
        ". . . # . . . . \n"
        " . . . . . # . .\n",

        ". . # # # # . . \n"
        " . # # # # # . .\n"
        ". . # # # # # . \n"
        " . . # # # # . .\n")


if __name__ == '__main__':
    run_tests()
Update 1:

Removed unnecessary -1 for third imaginary coordinate, because it doesn't change anything

Update 2,3:

Partially implemented improvements suggested by Leaky Nun + my own too.

\$\endgroup\$
12
  • \$\begingroup\$ Do we basically use the same algorithm? Could you append an explanation? \$\endgroup\$
    – Leaky Nun
    Aug 29, 2016 at 15:42
  • 1
    \$\begingroup\$ def A(x,y):return y,x+y,len(L)-1-y+x -> A=lambda x,y:(y,x+y,len(L)-1-y+x) \$\endgroup\$
    – Leaky Nun
    Aug 29, 2016 at 15:49
  • \$\begingroup\$ Also, list comprehensions could help you golf some bytes off. \$\endgroup\$
    – Leaky Nun
    Aug 29, 2016 at 15:49
  • 1
    \$\begingroup\$ I think you can turn len(L)-y+x into x-y \$\endgroup\$
    – Leaky Nun
    Aug 29, 2016 at 16:44
  • 1
    \$\begingroup\$ You can take in list of lines \$\endgroup\$
    – Leaky Nun
    Aug 29, 2016 at 17:14
2
\$\begingroup\$

Python, 237 230 bytes

7 bytes thanks to Dennis.

def f(a):i=range(len(a[0]));j=range(len(a));b,c,d=map(sorted,zip(*[[x,x+y,x-y]for y in i for x in j if"?"<a[x][y]]));return[[[a[x][y],"#"][(a[x][y]>" ")*(b[0]<=x<=b[-1])*(c[0]<=x+y<=c[-1])*(d[0]<=x-y<=d[-1])]for y in i]for x in j]

Port of my answer in Pyth.

Takes array of lines as input, outputs 2D array of characters.

\$\endgroup\$
0
2
\$\begingroup\$

Perl, 128 126 bytes

Includes +6 for -0F\n

Run with input on STDIN. Use 1 for filled, 0 for empty. Lines do not have to be padded with spaces at the end:

perl -M5.010 hexafill.pl
 0 0 0 0 0 0 0 0
0 0 1 1 1 1 0 0 
 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 
 0 0 1 1 1 1 0 0
0 0 0 0 0 0 0 0 
^D

hexafill.pl

#!/usr/bin/perl -0F\n
$-=map{s%$=%$=^!map{/$/;grep{pos=$`;$=?$_|="!"x$`.1:!/\b.*\G./}${--$@}}@F-$-+pos,$-+pos,$-%eeg;--$-;$=||say}@F while$=--

Uses cube coordinates. Determine maximum and minimum during the $= == 1 loop and fills coordinates between these bounds during the $= == 0 loop. The first 58 loops are pointless and are only there to fill $- with the number of lines

\$\endgroup\$
1
\$\begingroup\$

TSQL, 768 bytes

I wrote a query to solve this - which I found quite difficult. It is not able to compete with all the excellent shorter answer. But wanted to post it anyway for those interested. Sorry about the length of the answer - hoping codegolf is also about different approachs.

Golfed:

DECLARE @ varchar(max)=
'
. . . . # . . . 
 . # . . . # . .
. . . # . . . . 
 . . . . . # . .
. . . . . . . . 
'

;WITH c as(SELECT cast(0as varchar(max))a,x=0,y=1,z=0UNION ALL SELECT SUBSTRING(@,z,1),IIF(SUBSTRING(@,z,1)=CHAR(10),1,x+1),IIF(SUBSTRING(@,z,1)=CHAR(10),y+1,y),z+1FROM c WHERE LEN(@)>z)SELECT @=stuff(@,z-1,1,'#')FROM c b WHERE((exists(SELECT*FROM c WHERE b.y=y and'#'=a)or exists(SELECT*FROM c WHERE b.y<y and'#'=a)and exists(SELECT*FROM c WHERE b.y>y and'#'=a))and a='.')and(exists(SELECT*FROM c WHERE b.x<=x-ABS(y-b.y)and'#'=a)or exists(SELECT*FROM c WHERE b.x<=x+y-b.y and a='#'and b.y<y)and exists(SELECT*FROM c WHERE b.x<=x+b.y-y and a='#'and b.y>y))and(exists(SELECT*FROM c WHERE b.x>=x+ABS(y-b.y)and'#'=a)or exists(SELECT*FROM c WHERE b.x>=x-y+b.y and b.y<y and'#'=a)and exists(SELECT*FROM c WHERE b.x>=x-b.y+y and a='#'and b.y>y))OPTION(MAXRECURSION 0)PRINT @

Ungolfed:

DECLARE @ varchar(max)=
'
. . . . # . . . 
 . # . . . # . .
. . . # . . . . 
 . . . . . # . .
. . . . . . . . 
'
;WITH c as
(
  SELECT 
    cast(0as varchar(max))a,x=0,y=1,z=0
  UNION ALL
  SELECT
    SUBSTRING(@,z,1),IIF(SUBSTRING(@,z,1)=CHAR(10),1,x+1),
    IIF(SUBSTRING(@,z,1)=CHAR(10),y+1,y),
    z+1
  FROM c
  WHERE LEN(@)>z
)
SELECT @=stuff(@,z-1,1,'#')FROM c b
WHERE((exists(SELECT*FROM c WHERE b.y=y and'#'=a)
or exists(SELECT*FROM c WHERE b.y<y and'#'=a)
and exists(SELECT*FROM c WHERE b.y>y and'#'=a)
)and a='.')
and 
(exists(SELECT*FROM c WHERE b.x<=x-ABS(y-b.y)and'#'=a)
or exists(SELECT*FROM c WHERE b.x<=x+y-b.y and a='#'and b.y<y)
and exists(SELECT*FROM c WHERE b.x<=x+b.y-y and a='#'and b.y>y))
and(exists(SELECT*FROM c WHERE b.x>=x+ABS(y-b.y)and'#'=a)
or exists(SELECT*FROM c WHERE b.x>=x-y+b.y and b.y<y and'#'=a)
and exists(SELECT*FROM c WHERE b.x>=x-b.y+y and a='#'and b.y>y))
OPTION(MAXRECURSION 0) 
PRINT @

Fiddle ungolfed

\$\endgroup\$
1
\$\begingroup\$

GNU Octave, 212, 196 bytes

Maybe not really a golfer's favourite choice language, but that's what makes the challenge, isn't it? Assuming m is taken as a char matrix: 178 bytes stand alone and 196 if stuffed into a function.

golfed:

function k=f(m)[a,b]=size(m);[y,x]=ndgrid(1:a,1:b);t={y,y+x,x-y};k=m;s=x>0;for j=1:3l{j}=unique(sort(vec(t{j}.*(m==['#']))))([2,end]);s&=(l{j}(1)<=t{j})&(l{j}(2)>=t{j});endk(s&mod(x+y,2))=['#']end

ungolfed:

function k=f(m)
[a,b]=size(m);[y,x]=ndgrid(1:a,1:b);t={y,y+x,x-y};k=m;s=x>0;
for j=1:3
  l{j}=unique(sort(vec(t{j}.*(m==['#']))))([2,end]);
  s&=(l{j}(1)<=t{j})&(l{j}(2)>=t{j});
end
k(s&mod(x+y,2))=['#']
end

Explanation : we build a coordinate system, 3 axes - orthogonal to the hexagons sides, find max and min of each coordinate, then build a logical mask starting with 1 everywhere and logically and:ing each coordinate max and min constraint, finally re-setting each remaining "true" position to "#" char.

If you want to test it, you can just create m matrix like this:

m = [' . . . . . . . .. . . . # . . .  . # . . . # . .. . . # . . . .  . . . . . # . .. . . . . . . . ']; m = reshape(m,[numel(m)/6,6])';

and then call the f(m) and compare with m by building a matrix with both of them in:

['     before           after      ';m,ones(6,1)*'|',f(m)]
\$\endgroup\$
2
  • 1
    \$\begingroup\$ (Belated) Welcome to PPCG! Octave answers are more than welcome. :) Two things though: 1) please include the code that you've actually counted(without unnecessary whitespace), so that people can check the score more easily. You can include a readable version separately. 2) It appears that your submission is a snippet which assumes the input to be stored in m and the output to be stored in k. Answers should always be full programs or callable functions. \$\endgroup\$ Aug 30, 2016 at 14:53
  • \$\begingroup\$ Thanks! Yes you are right, I have embedded k and m in a function f now and added a snippet constructing a first test m for validation. \$\endgroup\$ Aug 30, 2016 at 15:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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