Выделите ограничивающий прямоугольник, часть II: шестиугольная сетка


24

Вам дана гексагональная сетка символов .и #вот так:

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

Ваша задача состоит в том, чтобы заполнить всю выровненный по осям ограничивающего прямоугольника #с последующим #:

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

Выравнивающая ось ограничивающая рамка является самой маленькой выпуклой шестиугольной формой, которая содержит все #. Обратите внимание, что в случае гексагональной сетки необходимо рассмотреть три оси (W / E, SW / NE, NW / SE):

введите описание изображения здесь

Вот еще один пример, чтобы показать, что в некоторых случаях одна или несколько сторон будут содержать только одну #:

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

Вы можете рассматривать их как шестиугольники с вырожденными сторонами, или вы можете нарисовать ограничивающий прямоугольник вокруг них, как я делал выше, и в этом случае они все еще являются шестиугольниками:

введите описание изображения здесь

Слишком сложно? Попробуйте Часть I!

правила

Вы можете использовать любые два различных непечатаемых символа ASCII (от 0x21 до 0x7E включительно) вместо #и .. Я буду продолжать ссылаться на них как #и .для остальной части спецификации, хотя.

Ввод и вывод могут быть либо строкой, разделенной переводом строки, либо списком строк (по одной на каждую строку), но формат должен быть согласованным.

Вы можете предположить, что вход содержит как минимум одну #и все строки одинаковой длины. Обратите внимание, что есть два разных «вида» строк (начиная с пробела или не пробела) - вы не можете предполагать, что ввод всегда начинается с одного и того же типа. Вы можете предположить, что ограничивающий прямоугольник всегда вписывается в заданную вами сетку.

Вы можете написать программу или функцию и использовать любой из наших стандартных методов получения ввода и предоставления вывода.

Вы можете использовать любой язык программирования , но учтите, что эти лазейки по умолчанию запрещены.

Это , поэтому самый короткий действительный ответ - измеренный в байтах - выигрывает.

Тестовые случаи

Каждый тестовый пример имеет вход и выход рядом друг с другом.

#    #

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

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

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

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

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

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

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

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

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

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

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

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

1
Моя голова кружится, пытаясь найти какой-либо очевидный образец. Вы сказали «гексагональный», но в тестовых примерах есть только две входные формы в шестиугольники. Я заблудился.
Анастасия-Романова

1
@ Anastasiya-Romanova 秀 Если вы представляете фигуру как проходящую через центры внешних символов, то да, у некоторых шестиугольников будут вырожденные стороны (как в прямоугольной сетке, где вы можете получить случаи, когда прямоугольник сводится к линии). Однако, если вы рисуете прямоугольник вокруг символов (как я сделал на диаграмме), все примеры являются шестиугольниками (некоторые из которых имеют очень короткие стороны).
Мартин Эндер

1
@ Анастасия-Романова 秀 Помогает ли новая диаграмма?
Мартин Эндер

3
Я! выглядит как II, если у меня не те очки ...
Нил

1
@Neil Или, вы знаете, слишком много алкоголя;)
ThreeFx

Ответы:


7

Pyth , 82 71 байт

L, hbebMqH @ S + GH1KhMyJs.e, Lkfq \ # @ bTUb.zA, YSM-FdJySsMJj.es.eXW && gKkgG-kYgH + Кыз \. \ # BZ
MQH @ S [hGHeG) 1j.es.eXW && ghMJs.e, Lkfq \ # @ bTUb.zkgSm-FdJ-kYgSsMJ + Кыз \. \ # BZ

Попробуйте онлайн!

объяснение

  • Пусть A - точка с наименьшей координатой y, а B - точка с наивысшей координатой y.

  • Пусть C будет точкой с самым низким (значение x минус значение y), а D будет точкой с самым высоким.

  • Пусть E будет точкой с самым низким (значение x плюс значение y), а F будет точкой с самым высоким.

Тогда это эквивалентно нахождению координат, у которых координата y находится между A и B, значение x минус значение y находится между C и D, а значение x плюс значение y находится между E и F.


в первый раз, когда я мог опубликовать решение раньше, если бы только приложение для Android Android могло правильно обрабатывать символы табуляции (по какой-то причине они исчезали при вставке): /
Sarge Borsch

@SargeBorsch Я извиняюсь :(
Утренняя монахиня

хаха, почему, это приложение для Android, которое заставило меня потерпеть неудачу: D
Sarge Borsch

6

Haskell, 256 254 243 байта

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

Спасибо @Damien за игру в гольф f!

Ввод принимается как список списка символов, вывод осуществляется аналогично.

Су это зверь, чтобы написать. Он основан на идее LeakyNun, использующей максимальную и минимальную фильтрацию на основе координат элементов.

Я действительно удивлен тем, что на m=mapсамом деле экономит байты, так как это кажется таким дорогостоящим.


Объяснение:

Вот немного менее резкая версия (акцент на немного ):

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это функция, которая присваивает каждому символу индекс (y-index, x-index), сохраняя при этом исходную структуру списка.

  • b: Учитывая элемент из индексированного списка, bвычисляет [y-index, y - x, y + x].

  • p: С учетом индексированного поля вернуть 3 функции Int -> Bool, первая из которых - проверка y-индекса, вторая из разности и третья сумма. min(snd e)заботится о пробелах (пробел меньше обоих). Эта функция указана в коде гольфа.

  • qучитывая проиндексировано поле, изменить все необходимое , .чтобы #, проверяя , если это специальное поле возврата Trueк каждой тестовой функции.

Окончательное решение тогда состав qи f.


1
f=z(\y->z((,).(,)y)[0..])[0..]
Дэмиен

илиh x=z x[0..] f=h$h.curry(,)
Дэмиен

5

Python 3, 380 378 348 346 байт

Обратите внимание, что отступы - это табуляция, а не пробелы.

Гольф версия:

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

Проверьте это на Ideone

Пояснение (для негольфированной версии ниже):

Вся обработка выполняется без преобразования, пробелы просто пропускаются.
Функция axes_posвычисляет 3 кортежа мнимых «трехмерных» координат, они накапливаются в (поэлементно) минимальный и максимальный 3 кортежа ( bmin, bmax) для всех #символов.

Координаты рассчитываются в def axes_pos(x, y): return y, x + y, lc - y + x;
где X отсчитывает от 0 до правого, а Y отсчитывает от 0 до нижнего (от первой строки до последней).
Первая мнимая координата в основном Y, потому что очевидно почему. Его топор ортогонален зеленым границам (на фотографиях ОП).
Второй - ортогональный красным границам, а третий - ортогональный голубым границам.

Во втором проходе, замена производится для всех .символов, координаты «3D» попадают в bmin.. bmaxдиапазоне, поэлементны - это проверяемый в этом выражении all(bmin[j] <= p[j] <= bmax[j] for j in range(0, 3)).

Версия Ungolfed с тестами, также на 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()
Обновление 1:

Удалил ненужные -1для третьей мнимой координаты, потому что это ничего не меняет

Обновление 2,3:

Частично реализованные улучшения, предложенные Leaky Nun+ моим тоже.


Используем ли мы в основном один и тот же алгоритм? Не могли бы вы добавить объяснение?
Утренняя монахиня

1
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)
Дрянная Монахиня

Кроме того, списки могут помочь вам отыграть несколько байтов.
Утренняя монахиня

1
Я думаю, что вы можете превратиться len(L)-y+xвx-y
Leaky Nun

1
Вы можете взять в список строк
Leaky Nun

5

Желе , 45 35 13 42 41 байт

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

Это список ссылок; последний должен быть вызван на входе, чтобы произвести вывод.

Ввод / вывод осуществляется в виде строковых массивов, где .указывает на пустое и @указывает на заполненный.

Попробуйте онлайн! или проверьте все контрольные примеры .

Задний план

Давайте рассмотрим следующий пример.

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

Рисуя пару или параллельные линии - ближайшую пару, охватывающую все заполненные позиции - в каждом из трех направлений мы можем определить шестиугольную ограничивающую рамку.

В реализации мы заменяем все символы между этими двумя строками @и все, что находится за этими строками ., с возможным исключением диагоналей, которые содержат только пробелы).

Для горизонтальной оси это дает

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

для падающей диагональной оси это дает

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

и для повышения диагональной оси, это дает

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

Взяв символьный минимум из всех трех, так как .< @, мы получаем

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

Все, что осталось сделать, это восстановить пространство.

Как это работает

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 байт

7 байтов благодаря Денису.

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]

Порт моего ответа в Pyth .

Принимает массив строк в качестве входных данных, выводит двумерный массив символов.


2

Perl, 128, 126 байт

Включает +6 для -0F\n

Запустить с вводом на STDIN. Используйте 1для заполненных, 0для пустых. Строки не должны быть дополнены пробелами в конце:

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

Использует кубические координаты. Определите максимум и минимум во время $= == 1цикла и заполните координаты между этими границами во время $= == 0цикла. Первые 58 циклов бессмысленны и заполнены $-только количеством строк


1

TSQL, 768 байт

Я написал запрос для решения этой проблемы, который мне показался довольно сложным. Он не в состоянии конкурировать со всеми отличными короткими ответами. Но все равно хотел опубликовать его для тех, кто заинтересован. Извините за длину ответа - надеюсь, что Codegolf также о разных подходах.

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 @

Скрипка разгульная


1

GNU Octave, 212 , 196 байтов

Может быть, это не самый любимый язык для игры в гольф, но это то, что делает вызов, не так ли? Предполагая, что m взято в качестве матрицы символов: 178 байтов отдельно и 196, если они вставлены в функцию .

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

Пояснение : мы строим систему координат, 3 оси - ортогональные сторонам шестиугольника, находим max и min каждой координаты, затем строим логическую маску, начинающуюся с 1 везде и логически и: в каждой координате max и min ограничение, наконец переустанавливая каждая оставшаяся "истинная" позиция до "#" символа.

Если вы хотите проверить это, вы можете просто создать m матрицу следующим образом:

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

а затем вызовите f (m) и сравните с m, построив матрицу с ними обоими в:

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

1
(Запоздало) Добро пожаловать в PPCG! Ответы октавы более чем приветствуются. :) Две вещи, хотя: 1) пожалуйста, включите код, который вы на самом деле посчитали (без лишних пробелов), чтобы люди могли легче проверить счет. Вы можете включить читаемую версию отдельно. 2) Похоже, что ваша отправка представляет собой фрагмент, который предполагает, что вход будет сохранен, mа выход сохранен k. Ответы всегда должны быть полными программами или вызываемыми функциями.
Мартин Эндер

Благодарность! Да, вы правы, я теперь встроил k и m в функцию f и добавил фрагмент, создающий первый тест m для проверки.
mathreadler
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.