Перевірте рішення Ханойської вежі


29

Якщо ви не знаєте, що таке Ханойська вежа , я поясню це коротко: Є три стрижні та кілька дисків, кожен з яких має різний розмір. На початку всі диски знаходяться на першій вежі в упорядкованому порядку: Найбільший - внизу, найменший вгорі. Мета - піднести всі диски до третього стрижня. Звучить легко? Ось привід: ви не можете розмістити диск поверх диска, меншого за інший диск; ви можете тримати лише один диск у руці за один раз, щоб перемістити їх на інший стрижень, і ви можете помістити диск тільки на прути, а не на стіл, підлий ублюдок.

Приклад рішення ascii:

  A      B      C
  |      |      |      
 _|_     |      |      
__|__    |      |


  A      B      C
  |      |      |      
  |      |      |      
__|__   _|_     |


  A      B      C
  |      |      |      
  |      |      |      
  |     _|_   __|__


  A      B      C
  |      |      |      
  |      |     _|_     
  |      |    __|__      

Виклик

Існують три стрижні, що називаються A, B і C. (Ви також можете їх назвати 1,2 і 3 відповідно, якщо це допомагає) На початку всі n диски знаходяться на стрижні A (1).

Ваше завдання - перевірити рішення для башти Ханой. Вам потрібно буде переконатися, що:

  1. Зрештою всі n диски знаходяться на стрижні C (3).
  2. Для будь-якого диска в будь-якому даному стані немає меншого диска під ним.
  3. Ніяких очевидних помилок, як спроба виймати диски з порожнього стрижня або переміщення дисків на неіснуючі стрижні.

(рішення не повинно бути оптимальним.)

Вхідні дані

Ваша програма отримає два входи:

  1. Кількість дисків n (ціле число)
  2. Зроблені ходи, які будуть складатися з набору кортежів: (башта, щоб взяти з цього самого верхнього диска), (башта, для того, щоб взяти цей диск), де кожен кортеж відноситься до ходу. Ви можете вибрати, як вони представлені. Наприклад, щось на кшталт наступних способів подання рішення для n = 2, які я намалював у ascii вище. (Я буду використовувати перший в тестових випадках, тому що це легко на очах):

    "A-> B; A-> C; B-> C"

    [("A", "B"), ("A", "C"), ("B", "C")]

    [(1,2), (1,3), (2,3)]

    "ABACBC"

    [1,2,1,3,2,3]

Вихід

  • Truthy, якщо умови, які можна знайти під "викликом", дотримуються.

  • Фальсі, якщо вони цього не роблять.

Тестові приклади:

Правда:

n=1, "A->C"

n=1, "A->B ; B->C"

n=2, "A->B ; A->C ; B->C"

n=2, "A->C ; C->B ; A->C ; B->C"

n=2, "A->C ; A->B ; C->B ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C ; B->C"

Помилковий:

Третій запропонував @MartinEnder, 7-й - @Joffan

n=1, "A->B"

n=1, "C->A"

n=2, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=2, "A->B ; A->C ; C->B"

n=2, "A->C ; A->B ; C->B ; B->A"

n=2, "A->C ; A->C"

n=3, "A->B ; A->D; A->C ; D->C ; A->C"

n=3, "A->C ; A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->B ; B->C ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; C->B"

n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C"

n=4, "A->B ; A->B ; A->B ; A->C ; B->C ; B->C ; B->C"

Це кодовий гольф , виграє найкоротше рішення. Застосовуються стандартні правила та лазівки. Батареї не включені.


Це також добре , якщо другий вхід може бути представлений з допомогою методу, але з використанням цифр замість букв (наприклад A=1, B=2, C=3і т.д.)?
Р. Кап

1
Чи можу я нульовий показник вхідних даних?
Rohan Jhunjhunwala

1
Чи добре, якщо помилка видається, коли диск виймається з порожнього або неіснуючого стрижня?
Р. Кап

1
Чи можемо ми припустити, що не буде таких кроків A->A?
Мартін Ендер

2
@Kobi вам доведеться перевірити, moving discs to nonexistant rods.звичайно, так, цеD
edc65

Відповіді:


7

Сітківка , 84 80 байт

-5 байт завдяки Мартіну Ендеру

~
 ~$'
$
ABC
{`^(.)(.*)( ~+)\1
$3$2$1
}`^(\W+)(\w)(.*)(?<=\1~+|\w)\2
$3$1$2
^AB 

Спробуйте в Інтернеті! (плюс 5 байт для рядкових тестів)

Код імітує повну гру.

  • Вхід подається як ACABCBACBABCAC~~~.
    ~~~означає три диски.
  • Перші чотири рядки перетворять вхідні дані в формат гри: ACABCBACBABCAC ~~~ ~~ ~ABC.
    На початку A стрижень має всі 3 диски, а B і C стрижні порожні.
  • Далі у нас є петля з двох кроків:
    • У рядку візьміть першу букву, яка вказує на наступний стрижень джерела. Знайдіть цю паличку і візьміть останній диск. Вийміть букву і перемістіть диск на пучок (підберіть його).
      У нашому прикладі, після першого кроку, текст буде виглядати наступним чином : ~CABCBACBABCAC ~~~ ~~ABC.
    • На другому етапі знаходимо цільовий стрижень і переміщуємо туди диск. Ми перевіряємо шток порожній, або має більший диск у верхній частині: ABCBACBABCAC ~~~ ~~AB ~C.
  • Нарешті ми підтверджуємо, що стрижні A і B порожні - це означає, що всі диски знаходяться на C (в останньому рядку є додаткове місце).

Нічого собі, вражаюче
Рохан Джунжунвала

17

Сітківка , 167 165 157 150 123 байт

Це абсолютно схоже на виклик, який слід вирішити за допомогою одного регексу ... (незважаючи на заголовок "Retina", це просто ванільний .NET-регулярний вираз, що відповідає дійсним вводум).

^(?=\D*((?=(?<3>1+))1)+)((?=A(?<1-3>.+)|B(?<1-4>.+)|C(?<1-5>.+)).(?=A.*(?!\3)(\1)|B.*(?!\4)(\1)|C.*(?!\5)(\1)).)+(?!\3|\4)1

Формат введення - це список інструкцій форми AB, за якими слід nодинаково використовувати цифру 1. Розділювачів немає. Вихідні дані є 1дійсними та 0недійсними.

Спробуйте в Інтернеті! (Перші два символи включають тестовий набір, відокремлений підводним рядком.)

Альтернативне рішення, кількість байтів:

^(?=\D*((?=(?<3>1+))1)+)((?=A(?<1-3>.+)|B(?<1-4>.+)|C(?<1-5>.+)).(?=A.*(?!\3)(\1)|B.*(?!\4)(\1)|C.*(?!\5)(\1)).)+(?<-5>1)+$

Це може можливо бути скорочено шляхом використання 1, 11і 111замість того , щоб A, Bі , Cале я повинен буду дивитися на це пізніше. Можливо, також буде коротше розділити програму на кілька етапів, але де проблема в цьому? ;)

Пояснення

Це рішення широко використовує балансуючі групи .NET. Для повного пояснення дивіться мою публікацію про Stack Overflow , але суть полягає в тому, що захоплення груп у .NET - це стеки, де кожне нове захоплення виштовхує іншу підрядку і де також можливо знову вискакувати з такого стека. Це дозволяє рахувати різні кількості в рядку. У цьому випадку це дозволяє нам реалізувати три стрижні безпосередньо як три різні групи захоплення, де кожен диск представлений захопленням.

Для переміщення дисків між стрижнями ми використовуємо дивну химерність (?<A-B>...)синтаксису. Зазвичай, це спливає захоплення зі стека Bі натискає на стек Aміж рядком, що з'явився, і початком цієї групи. Таким чином, (?<A>a).(?<B-A>c)відповідність abcзалишила б Aпорожнім і Bз b(на відміну від c). Однак, завдяки огляду .NET змінної довжини ззаду можливі захоплення (?<A>...)та (?<B-A>...)перекриття. З будь-якої причини, якщо це так, перетин двох груп висувається на B. Я детально це поведінка в «просунутої секції» на балансуванні груп в цій відповіді .

Про регекс. Стрижні A, Bі Cвідповідають групам 3, 4і 5в регексі. Почнемо з ініціалізації стрижня A:

^                 # Ensure that we start at the beginning of the input.
(?=               # Lookahead so that we don't actually move the cursor.
  \D*             # Skip all the instructions by matching non-digit characters.
  (               # For each 1 at the end of the input...
    (?=(?<3>1+))  # ...push the remainder of the string (including that 1)
                  # onto stack 3.
  1)+
)

Так, наприклад, якщо вхід закінчується 111, то група 3 / стрижень Aтепер буде містити список захоплень [111, 11, 1](верхній знаходиться праворуч).

Наступний біт коду має таку структуру:

(
  (?=A...|B...|C...).
  (?=A...|B...|C...).
)+

Кожна ітерація цього циклу обробляє одну інструкцію. Перше чергування витягує диск із заданого стрижня (на тимчасову групу), друге чергування кладе цей диск на інший даний стрижень. Ми побачимо через мить, як це працює і як ми гарантуємо, що перехід дійсний.

Спочатку, знявши диск із стрижня джерела:

(?=
  A(?<1-3>.+)
|
  B(?<1-4>.+)
|
  C(?<1-5>.+)
)

Для цього використовується дивна поведінка перетину групи, яку я описав вище. Зауважте, що група 3, 4і 5завжди буде містити підрядки 1s в кінці рядка, довжина якого відповідає розміру диска. Тепер ми використовуємо, (?<1-N>.+)щоб вискочити верхній диск із стека Nта натиснути на перетині цієї підрядки зі збігом .+на стек 1. Оскільки .+завжди обов'язково охоплює весь вискочений захоплення N, ми знаємо, що це просто переміщує захоплення.

Далі ми ставимо цей диск із стека 1на стек, що відповідає другому стрижня:

(?=
  A.*(?!\3)(\1)
|
  B.*(?!\4)(\1)
|
  C.*(?!\5)(\1)
)

Зауважте, що нам не потрібно очищати стек 1, ми можемо просто залишити диск там, оскільки перед тим, як знову використовувати стек, ми поставимо новий. Це означає, що ми можемо уникати (?<A-B>...)синтаксису і просто скопіювати рядок на (\1). Щоб переконатися, що переміщення є дійсним, ми використовуємо негативний підказки (?!\N). Це гарантує, що з позиції, де ми хочемо відповідати поточному диску, неможливо зіставити диск, який вже знаходиться у стеці N. Це може статися, лише якщо а) \Nніколи не збігатиметься, тому що стек повністю порожній або б) the disc on top of stackN is larger than the one we're trying to match with\ 1`.

Нарешті, все, що залишилося - це гарантувати, що а) ми зіставили всі інструкції та б) стрижні Aта Bпорожні, щоб усі диски були переміщені на C.

(?!\3|\4)1

Ми просто перевірити , що ні \3ні \4не може відповідати (що тільки в тому випадку , якщо обидва порожні, тому що будь-який фактичний диск буде відповідати) , і що ми можемо Збіг 1так , що ми не опущені ніяких вказівок.


14

Java "тільки" 311 272 263 261 260 259 256 байт

Збережено 39 незліченних байтів через те, що @Frozn помітив старішу функцію налагодження, а також деякі хитрі трюки з гольфу.

Гольф-версія

int i(int n,int[]m){int j=0,k=0,i=n;Stack<Integer>t,s[]=new Stack[3];for(;j<3;)s[j++]=new Stack();for(;i-->0;)s[0].push(i);for(;k<m.length;k+=2)if((t=s[m[k+1]]).size()>0&&s[m[k]].peek()>t.peek())return 0;else t.push(s[m[k]].pop());return s[2].size()<n?0:1;}

непільгований із поясненнями та симпатичними друкованими стеками на кожному кроці

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package codegolf;

/**
 *
 * @author rohan
 */
import java.util.Arrays;
import java.util.Stack;
public class CodeGolf {
    //golfed version
    int i(int n,int[]m){int j=0,k=0,i=n;Stack<Integer>[] s=new Stack[3];for(;j<3;j++)s[j]=new Stack();for(;i-->0;)s[0].push(i);for(;k<m.length;System.out.println(Arrays.toString(s)),k+=2)if(!s[m[k+1]].isEmpty()&&s[m[k]].peek()>s[m[k+1]].peek())return 0;else s[m[k+1]].push(s[m[k]].pop());return s[2].size()==n?1:0;}
    /** Ungolfed
        * 0 as falsy 1 as truthy
        * @param n the number of disks
        * @param m represents the zero indexed stacks in the form of [from,to,from,to]
        * @return 0 or 1 if the puzzle got solved, bad moves result in an exception
        */
    int h(int n, int[] m) {
        //declarations
        int j = 0, k = 0, i = n;
        //create the poles
        Stack<Integer>[] s = new Stack[3];
        for (; j < 3; j++) {
            s[j] = new Stack();
        }
        //set up the first tower using the "downto operator
        for (; i-- > 0;) {
            s[0].push(i);
        }
    //go through and perform all the moves
        for (; k < m.length; System.out.println(Arrays.toString(s)), k += 2) {
            if (!s[m[k + 1]].isEmpty() && s[m[k]].peek() > s[m[k + 1]].peek()) {
                return 0;//bad move
            } else {
                s[m[k + 1]].push(s[m[k]].pop());
            }
        }
        return s[2].size() == n ? 1 : 0;// check if all the disks are done
    }
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
    //test case
        System.out.println( new CodeGolf().h(3,new int[]{0,2,0,1,2,1,0,2,1,0,1,2,0,2})==1?"Good!":"Bad!");
    }

}

У безгольової версії є функція, де вона буде роздруковувати, як виглядають стеки на кожному кроці так ...

[[2, 1], [], [0]]
[[2], [1], [0]]
[[2], [1, 0], []]
[[], [1, 0], [2]]
[[0], [1], [2]]
[[0], [], [2, 1]]
[[], [], [2, 1, 0]]
Good!

Що робить System.out.println(Arrays.toString(s))?
Фрозн

Він досить роздрукує стеки. Так [[2,1,0], [] []]
Rohan Jhunjhunwala

Уопс @Frozn, що тепер була видалена функція налагодження
Rohan Jhunjhunwala

Я знаю, просто цікаво , чому це там :) Ви можете також замінити &&з &.
Frozn

@Frozn Я не можу це сумно замінити, тому що я покладався на поведінку короткого замикання, щоб уникнути спроб зазирнути у порожній стек. Дякую за зменшення на 39 байт
Рохан Джунхунвалала,

9

Python 2, 186 167 158 135 127 115 110 102 байт

n,m=input()
x=[range(n),[],[]]
for a,b in m:p=x[a].pop();e=x[b];e and 1/(p>e[-1]);e+=p,
if x[0]+x[1]:_

Вводить дані STDIN у такому форматі:

(1,[(0,1),(1,2)])

Тобто, набір Python кількості дисків та список кортежів Python (from_rod,to_rod). Як і в Python, довколишні дужки не є обов'язковими. Стрижні нульові.

Наприклад, цей тестовий випадок:

n=2; "A->B ; A->C ; B->C"

буде надано як:

(2,[(0,1),(0,2),(1,2)])

Якщо рішення є дійсним, виводить нічого та виходить із кодом виходу 0. Якщо він недійсний, викидає виняток та виходить із кодом виходу 1. Закидає, IndexErrorякщо переходить на неіснуючий стрижень або намагається зняти диск із стрижень, на якому немає дисків, a ZeroDivisionErrorякщо диск розміщений поверх меншого диска, або NameErrorякщо на першому чи другому стержнях залишилися диски.

Збережено 13 байт завдяки @KarlKastor!

Збережено 8 байт завдяки @xnor!


1
Перевірка того, що кожна купа сортується, здається занадто складною. Чи не можете ви просто перевірити, чи переміщений диск більший за верхній диск, на який він переміщений?
xnor

@xnor Спасибі, що має працювати. Додавання зараз.
Мідь

5

Python 2.7, 173 158 138 130 127 123 байт:

r=range;a,b=input();U=[r(a,0,-1),[],[]]
for K,J in b:U[J]+=[U[K].pop()]if U[J]<[1]or U[K]<U[J]else Y
print U[-1]==r(a,0,-1)

Вводиться через stdin у форматі, (<Number of Discs>,<Moves>)де <Moves>подається у вигляді масиву, що містить кортежі, відповідні кожному ходу, кожен з яких містить пару цілих чисел, розділених комами. Наприклад, тестовий випадок:

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C" 

Повідомлення вказується як:

(3,[(0,2),(0,1),(2,1),(0,2),(1,0),(1,2),(0,2)]) 

до моєї програми. Виводить a, IndexErrorякщо третя умова не виконана, a NameErrorякщо 2-я умова не виконується, а Falseякщо перша умова не виконується. Інакше виводи True.


дві речі: змінна Yніколи не визначається у вашому коді (я думаю, що це має бути J) і U[J]+=[Y,[U[K].pop()]][U[J]<[1]or U[K]<U[J]]коротша на 3 символи, щоstmt1 if cond else stmt2
jermenkoo

@jermenkoo Ну, я використовую таку Yзмінну, як, щоб підняти, NameErrorколи 2-я умова не виконується. Якби я змінити , Yщоб J, то NameErrorне буде поставлене. З цієї причини, я не можу зробити , U[J]+=[Y,[U[K].pop()]][U[J]<[1]or U[K]<U[J]]тому що це підняло б на NameError весь час , а НЕ тільки , коли друга умова не виконана.
Р. Кап

добре, дякую за ваше пояснення!
jermenkoo

5

VBA, 234 217 213 196 байт

Function H(N,S)
ReDim A(N)
While P<Len(S)
P=P+2:F=1*Mid(S,P-1,1):T=1*Mid(S,P,1)
E=E+(T>2):L=L+T-F
For i=1 To N
If A(i)=F Then A(i)=T:Exit For
E=E+(A(i)=T)+(i=N)
Next
Wend
H=L+9*E=2*N
End Function

Формат введення для рухів - це рядок з парною кількістю цифр (012). Виклик у таблиці, = H ([кількість дисків], [перемістити рядок])

Масив A утримує положення стрижня різних дисків. Переміщення - це просто оновлення першого появи номера стрижня "Від" на номер стрижня "До". Якщо ви спочатку зіткнулися з стрижневим диском "До", або немає "З" стрижневого диска, це недійсний хід. Загальне "значення стержня" A утримується в L, яке потрібно закінчити на рівні 2N. Помилки накопичуються як від’ємне число в Е.

Як і в інших рішеннях, «переміщення» диска з вежі до тієї ж вежі не заборонено. Я міг би заборонити це ще на 6 байт.

Результати

Результат функції в першому стовпці (останній n = 3 випадок - це моє додавання за допомогою додаткового стрижня).

TRUE    1   02
TRUE    1   0112
TRUE    2   010212
TRUE    2   02210212
TRUE    2   020121101202
TRUE    3   02012102101202
TRUE    4   010212012021010212102012010212

FALSE   1   01
FALSE   1   20
FALSE   2   02012102101202
FALSE   2   010221
FALSE   2   02012110
FALSE   2   0202
FALSE   3   0202012102101202
FALSE   3   0201210112101202
FALSE   3   02012102101221
FALSE   3   0103023212
FALSE   4   0102120120210102121020120102
FALSE   4   01010102121212

2

php, 141 байт

<?php $a=$argv;for($t=[$f=range($a[++$i],1),[],[]];($r=array_pop($t[$a[++$i]]))&&$r<(end($t[$a[++$i]])?:$r+1);)$t[$a[$i]][]=$r;echo$t[2]==$f;

Сценарій командного рядка приймає введення як висоту, а потім ряд індексів масиву (0 індексовано), наприклад, 1 0 2 або 2 0 1 0 2 1 2 для найкоротших тестових випадків висоти 1 або 2.
Відлуння 1 про справжні випадки і нічого на помилкових.
Надає 2 повідомлення та 1 попередження, тому його потрібно виконувати в оточенні, яке замовчує їх.


1

JavaScript (ES6), 108

n=>s=>!s.some(([x,y])=>s[y][s[y].push(v=s[x].pop())-2]<v|!v,s=[[...Array(s=n)].map(_=>s--),[],[]])&s[2][n-1]

Формат введення: функція з 2 аргументами

  • arg 1, числовий, кількість кілець
  • arg 2, масив рядків, кожен рядок 2 символи '0', '1', '2'

Вихід: повернути 1, якщо добре, 0, якщо недійсний, виняток, якщо немає стрижня

Менше гольфу і пояснюється

n=>a=>(
  // rods status, rod 0 full with an array n..1, rod 1 & 2 empty arrays
  s = [ [...Array(t=n)].map(_=>t--), [], [] ],
  // for each step in solution, evaluate function and stop if returns true
  err = a.some( ([x,y]) => {
    v = s[x].pop(); // pull disc from source rod
    // exception is s[x] is not defined
    if (!v) return 1; // error source rod is empty
    l = s[y].push(v); // push disc on dest rod, get number of discs in l
    // exception is s[y] is not defined
    if(s[y][l-2] < v) return 1; // error if undelying disc is smaller
  }),
  err ? 0 // return 0 if invalid move
  : s[2][n-1]; // il all moves valid, ok if the rod 2 has all the discs
)

Примітка тесту : перший рядок функції Test потрібен для перетворення формату введення, заданого у запитанні, на вхід, очікуваний моєю функцією

F=
n=>s=>!s.some(([x,y])=>s[y][s[y].push(v=s[x].pop())-2]<v|!v,s=[[...Array(s=n)].map(_=>s--),[],[]])&s[2][n-1]

Out=x=>O.textContent+=x+'\n'

Test=s=>s.split`\n`.map(r=>[+(r=r.match(/\d+|.->./g)).shift(),r.map(x=>(parseInt(x[0],36)-10)+''+(parseInt(x[3],36)-10))])
.forEach(([n,s],i)=>{
  var r
  try {
    r = F(+n)(s);
  } 
  catch (e) {
    r = 'Error invalid rod';
  }
  Out(++i+' n:'+n+' '+s+' -> '+r)
})

Out('OK')
Test(`n=1, "A->C"
n=1, "A->B ; B->C"
n=2, "A->B ; A->C ; B->C"
n=2, "A->C ; C->B ; A->C ; B->C"
n=2, "A->C ; A->B ; C->B ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C ; B->C"`)

Out('\nFail')
Test( `n=1, "A->B"
n=1, "C->A"
n=2, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=2, "A->B ; A->C ; C->B"
n=2, "A->C ; A->B ; C->B ; B->A"
n=2, "A->C ; A->C"
n=3, "A->B ; A->D; A->C ; D->C ; A->C"
n=3, "A->C ; A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->B ; B->C ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; C->B"
n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C"
n=4, "A->B ; A->B ; A->B ; A->C ; B->C ; B->C ; B->C"`)
<pre id=O></pre>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.