Destaque a caixa delimitadora, parte II: grade hexagonal


24

Você recebe uma grade hexagonal dos personagens .e #, assim:

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

Sua tarefa é preencher toda a caixa delimitadora alinhado ao eixo da #com ainda mais #:

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

A caixa delimitadora alinhada ao eixo é a menor forma hexagonal convexa que contém todas as #. Observe que, no caso da grade hexagonal, existem três eixos a serem considerados (L / E, SW / NE, NW / SE):

insira a descrição da imagem aqui

Aqui está outro exemplo para mostrar que, em alguns casos, um ou mais lados conterão apenas um #:

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

Você pode visualizá-los como hexágonos com lados degenerados ou pode desenhar a caixa delimitadora ao redor deles, como eu fiz acima, caso em que ainda são hexágonos:

insira a descrição da imagem aqui

Demasiado difícil? Tente a Parte I!

Regras

Você pode usar quaisquer duas distintas não espaciais caracteres imprimíveis ASCII (0x21 a 0x7E, inclusive), no lugar de #e .. Continuarei me referindo a eles como #e .pelo restante da especificação.

Entrada e saída podem ser uma única sequência separada por avanço de linha ou uma lista de sequências (uma para cada linha), mas o formato precisa ser consistente.

Você pode assumir que a entrada contém pelo menos uma #e todas as linhas têm o mesmo comprimento. Observe que existem dois "tipos" de linhas diferentes (começando com um espaço ou um não-espaço) - você pode não assumir que a entrada sempre começa com o mesmo tipo. Você pode assumir que a caixa delimitadora sempre se encaixa dentro da grade que você recebe.

Você pode escrever um programa ou função e usar qualquer um dos nossos métodos padrão de recebimento de entrada e saída.

Você pode usar qualquer linguagem de programação , mas observe que essas brechas são proibidas por padrão.

Isso é , então a resposta mais curta e válida - medida em bytes - vence.

Casos de teste

Cada caso de teste tem entrada e saída próximos um do outro.

#    #

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

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

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

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

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

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

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

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

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

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

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

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

11
Minha cabeça está girando, tentando encontrar algum padrão óbvio. Você disse 'hexagonal', mas há apenas duas entradas formadas em hexágonos nos casos de teste. Estou perdido.
Anastasiya-Romanova秀

11
@ Anastasiya-Romanova 秀 Se você imaginar a forma passando pelos centros dos caracteres externos, sim, alguns hexágonos terão lados degenerados (como na grade retangular, onde você pode obter casos em que o retângulo se reduz a uma linha). No entanto, se você desenhar o retângulo ao redor dos caracteres (como fiz no diagrama), todos os exemplos serão hexágonos (alguns dos quais têm lados muito curtos).
Martin Ender

11
@ Anastasiya-Romanova the O novo diagrama ajuda?
Martin Ender

3
EU! olhares como II se eu tenho os óculos errados sobre ..
Neil

11
@ Neil Ou, você sabe, muito álcool;)
ThreeFx

Respostas:


7

Pyth , 82 71 bytes

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

Experimente online!

Explicação

  • Seja A o ponto com a coordenada y mais baixa e B o ponto com a coordenada y mais alta.

  • Seja C o ponto com o mais baixo (valor x menos valor y) e D o ponto com o mais alto.

  • Seja E o ponto com o mais baixo (valor x mais valor y) e F o ponto com o mais alto.

Então é equivalente a encontrar as coordenadas em que a coordenada y está entre A e B, o valor x menos o valor y está entre C e D, e o valor x mais o valor y está entre E e F.


a primeira vez em que eu poderia postar uma solução mais cedo, se apenas o SE android aplicativo pode lidar corretamente com caracteres de tabulação (por alguma razão eles desapareceram quando colado): /
Sarge Borsch

@SargeBorsch Sinto muito :(
Leaky Nun

haha por que, é o aplicativo SE Android que me fez falhar: D
Sarge Borsch

6

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

Obrigado @ Damien por jogar golfe f!

A entrada é tomada como lista de lista de caracteres, a saída é fornecida da mesma maneira.

Então isso era uma fera para escrever. É baseado na idéia de LeakyNun, usando uma filtragem máxima e mínima nas coordenadas dos itens.

Estou realmente surpreso com o fato de m=maprealmente salvar bytes, pois parece muito caro.


Explicação:

Aqui está uma versão um pouco menos massacrada (ênfase em um pouco ):

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é uma função que atribui a cada caracter um índice (y-index, x-index), preservando a estrutura original da lista.

  • b: Dado um item da lista indexada, bcalcula [y-index, y - x, y + x].

  • p: Dado o campo indexado, retorne 3 funções Int -> Bool, a primeira das quais é a verificação do índice y, a segunda da diferença e a terceira da soma. min(snd e)cuida dos espaços (um espaço é menor que ambos). Esta função está embutida no código do golfe.

  • qdado o campo indexado, mudar tudo necessário .para #verificando se esse campo específico de retorno Truepara cada função de teste.

A solução final é então a composição de qe f.


11
f=z(\y->z((,).(,)y)[0..])[0..]
Damien

ouh x=z x[0..] f=h$h.curry(,)
Damien

5

Python 3, 380 378 348 346 bytes

Observe que o recuo é com guias, não espaços.

Versão Golfed:

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

Teste no Ideone

Explicação (para versão não destruída abaixo):

Todo o processamento é feito sem nenhuma conversão; os caracteres de espaço são simplesmente ignorados.
A função axes_poscalcula três tuplas de coordenadas "3D" imaginárias, elas são acumuladas em (três elementos) mínimo e máximo de três tuplas ( bmin, bmax) para todos os #caracteres.

As coordenadas são calculadas em def axes_pos(x, y): return y, x + y, lc - y + x;
onde X conta de 0 para a direita e Y conta de 0 para baixo (da primeira linha à última).
A primeira coordenada imaginária é basicamente Y, porque é óbvio o porquê. Seu machado é ortogonal a verde (nas fotos do OP) O
segundo é ortogonal a vermelho e o terceiro é ortogonal a azul.

Na segunda passagem, a substituição é feita por todos os .caracteres cujas coordenadas "3D" se enquadram em bmin.. bmaxrange, element wise - isso é verificado nesta expressão all(bmin[j] <= p[j] <= bmax[j] for j in range(0, 3)).

Versão ungolfed com testes, também em 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()
Atualização 1:

Removido desnecessário -1para a terceira coordenada imaginária, porque não altera nada

Atualização 2,3:

Melhorias parcialmente implementadas sugeridas por Leaky Nun+ meu próprio também.


Basicamente, usamos o mesmo algoritmo? Você poderia acrescentar uma explicação?
Leaky Nun

11
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)
Leaky Nun

Além disso, a compreensão da lista pode ajudá-lo a obter alguns bytes de folga.
Leaky Nun

11
Eu acho que você pode se transformar len(L)-y+xemx-y
Freira Furada

11
Você pode ver a lista de linhas
Leaky Nun

5

Geléia , 45 35 13 42 41 bytes

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

Esta é uma lista de links; o último deve ser chamado na entrada para produzir a saída.

A E / S está na forma de matrizes de cadeias, onde .indica vazio e @indica preenchido.

Experimente online! ou verifique todos os casos de teste .

fundo

Vamos considerar o seguinte exemplo.

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

Ao desenhar um par ou linhas paralelas - o par mais próximo que envolve todas as posições preenchidas - em cada uma das três direções, podemos determinar a caixa delimitadora hexagonal.

Na implementação, substituímos todos os caracteres entre essas duas linhas por @e tudo fora dessas linhas por ., com a possível exceção de diagonais que contêm apenas espaços).

Para o eixo horizontal, isso fornece

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

para o eixo diagonal em queda, fornece

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

e para o eixo diagonal de elevação, fornece

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

Tomando o mínimo de caráter dos três, já que .< @, obtemos

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

Tudo o que resta a fazer é restaurar os espaços.

Como funciona

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.

2

Python, 237 230 bytes

7 bytes graças a 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]

Porto da minha resposta em Pyth .

Pega a matriz de linhas como entrada, gera uma matriz de caracteres 2D.


2

Perl, 128 126 bytes

Inclui +6 para -0F\n

Execute com entrada no STDIN. Use 1para preenchido, 0para vazio. As linhas não precisam ser preenchidas com espaços no final:

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$=--

Usa coordenadas do cubo. Determine o máximo e o mínimo durante o $= == 1loop e preenche as coordenadas entre esses limites durante o $= == 0loop. Os primeiros 58 loops são inúteis e existem apenas para serem preenchidos $-com o número de linhas


1

TSQL, 768 bytes

Eu escrevi uma consulta para resolver isso - o que achei bastante difícil. Não é capaz de competir com toda a excelente resposta mais curta. Mas queria publicá-lo de qualquer maneira para os interessados. Desculpe o tamanho da resposta - esperando que o codegolf também seja sobre abordagens diferentes.

Golfe:

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 @

Violino sem Golfe


1

Oitava GNU, 212 , 196 bytes

Talvez não seja realmente o idioma preferido do jogador, mas é isso que faz o desafio, não é? Assumindo que m é tomado como uma matriz de caracteres: 178 bytes independentes e 196 se inseridos em uma função .

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

Explicação : construímos um sistema de coordenadas, 3 eixos - ortogonais aos lados dos hexágonos, localizamos max e min de cada coordenada e, em seguida, construímos uma máscara lógica começando com 1 em todos os lugares e logicamente e: cada restrição de max e min de coordenadas, finalmente redefinindo cada posição "verdadeira" restante para "#" char.

Se você quiser testá-lo, basta criar uma matriz m assim:

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

e, em seguida, chame f (m) e compare com m construindo uma matriz com os dois em:

['     before           after      ';m,ones(6,1)*'|',f(m)]

11
Bem-vindo ao PPCG! Respostas oitavas são mais que bem-vindas. :) Porém, duas coisas: 1) inclua o código que você realmente contou (sem espaço em branco desnecessário), para que as pessoas possam verificar a pontuação mais facilmente. Você pode incluir uma versão legível separadamente. 2) Parece que seu envio é um trecho que assume que a entrada a ser armazenada me a saída a ser armazenada k. As respostas devem sempre ser programas completos ou funções que podem ser chamadas.
Martin Ender

Obrigado! Sim, você está certo, eu incorporei k em uma função f agora e adicionei um trecho construindo um primeiro teste m para validação.
mathreadler
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.