Знайдіть стовпці, де всі символи однакові


18

Я натрапив на це питання на SO та подумав, що це зробить приємне завдання для гольфу. Отже ось це:

Виклик:

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

Вхід і вихід:

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

Приклад введення (безсоромно викрадений із питання про ПІ):

abcdefg
avcddeg
acbdeeg

Прочитавши дані, ваша програма повинна надрукувати положення кожного відповідного стовпця та символи, які вони містять. (Ваша програма може, але не потрібно, перестати читати подальше введення, якщо вона рано визначить, що немає відповідних стовпців.) Дозволений будь-який розумний формат виводу; зокрема, ви можете використовувати індексацію на основі 0 або 1 на основі позицій.

Приклад виводу для вищевказаного вводу (з використанням 0-індексування):

0: a
3: d
6: g

Оцінка:

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

  • −½ символів для правильної обробки вхідних ліній неоднакової довжини. (Вихідні дані не повинні містити позицій, розташованих в кінці найкоротшого рядка введення.)
  • −¼ символи для правильної обробки даних, що складаються з довільних символів Unicode, кодованих UTF-8.

Для натхнення ви можете знайти кілька незворушних рішень у питанні про питання SO (див. Вище).

Роз'яснення:

  • Просто об'єднання позицій і символів, як і в 0a3d6g, не вважається "розумним результатом". Вам слід надати якийсь роздільник (наприклад, пробіл) між кожним елементом виводу, щоб його можна було однозначно проаналізувати.

  • Введення буде надано у стандартному потоці введення ( stdin) або за допомогою будь-якого механізму введення текстового файлу, який є найбільш природним для вашої мови на вибір. (Якщо вибрана мова не має природного механізму для введення файлів, виконайте все, що здається найближчим за духом.)

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

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


Так чи чистий рядок закінчується введенням?
Стівен Румбальський

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

@StevenRumbalski: введення закінчується, коли більше немає для читання даних; Я думаю, що я можу дозволити пропуск порожнього рядка, якщо у вашій мові є проблеми з виявленням EOF. І так, пробіл - це ідеально хороший роздільник.
Ільмарі Каронен

Чи можемо ми мати зразок коду Unicode символів, кодованих UTF-8?
користувач невідомий

Відповіді:


12

APL, 25 символів

∩/{0=⍴⍵:⍬⋄(⊂⍵,⍨¨⍳⍴⍵),∇⍞}⍞

Я використовував Dyalog APL (версія 13) в якості свого перекладача. Він обробляє обидва входи неоднакової довжини та символи Unicode (UTF-8).

Приклади:

      ∩/{0=⍴⍵:⍬⋄(⊂⍵,⍨¨⍳⍴⍵),∇⍞}⍞
abcdefg
avcddeg
acbdeeg

  1 a  4 d  7 g  

      ∩/{0=⍴⍵:⍬⋄(⊂⍵,⍨¨⍳⍴⍵),∇⍞}⍞
test日本
blat日本国foo

  4 t  5 日  6 本 

Пояснення, дещо справа наліво:

  • Основним фрагментом цієї відповіді є пряма функція (в основному, анонімна функція), визначена в фігурних дужках. Його правильний аргумент визначається за допомогою .
    • 0=⍴⍵:⍬є нашим першим виразом, і він перевіряє, чи ми отримали порожній рядок (тобто, ми закінчили). Він використовує охорону (звичну конструкцію для багатьох функціональних програмістів) для умовного виконання виразу праворуч від товстої кишки. У цьому випадку, якщо 0 дорівнює формі / довжині ( ) правильного аргументу, повертаємо порожній набір ( ).
    • розділяє два вирази в межах функції. Якщо попередній вираз не був оцінений (і, таким чином, нічого не повернув), ми переходимо до наступного виразу.
    • Рекурсивно викликаємо функцію за допомогою функції самовідсилання ( ). Аргумент функції - це рядок неоцінюваного введення користувача, заданий quo-quad ( ).
    • ⊂⍵,⍨¨⍳⍴⍵ створює пари для кожного символу в рядку, де перший елемент кожної пари - це його позиція в рядку, а другий елемент - символ.
    • ⍳⍴⍵дає вектор від 1 до ⍴⍵, або довжина вхідного рядка.
    • ⍵,⍨¨ застосовує змінену функцію конкатенації (,⍨ ) до кожного ( ¨) елемента ліворуч ( у цьому випадку введення користувача) та праворуч. Зміна функції конкатенації призводить до заміни лівих та правих аргументів.
    • Нарешті, додаємо результат, використовуючи , щоб ми могли розмежовувати між рядками введення.
  • Ми спочатку подаємо нашу функцію за допомогою вводу користувача ( ).
  • Нарешті, ми зменшуємо ( /) наш отриманий вектор векторів пар за допомогою функції перетину ( ), отримуючи пари, які знаходяться у всіх підвекторах.

Без будь-яких вагомих причин у мене є вісцеральна негативна реакція, коли я бачу APL, що у мене немає для J або GolfScript. Але +1 все одно за відмінне рішення.
Стівен Румбальський

Я насправді думав про перехід на J ... Я візьму це за свій список причин. :)
Dillon Cower

12

Гольфскрипт (28 символів)

n/zip:^,,{.^=.&.,1>{;;}*}%n*

Існують проблеми із набором символів при проходженні Unicode через, тому жодного бонусу на чверть балів немає.


1
+1. Це не повинно мати менше результатів, ніж моя відповідь.
Стівен Румбальський

9

J, 57 51 44 40 символів

,.&.>y;y{{.z[y=.I.*/2=/\]z=.];._2]1!:1]3

Я туди потрапляю повільно, але впевнено. Це все ще далеко від ідеального, хоча я думаю.

Я впевнений, що відповідь на гачок - це відповідь, але, на жаль, не (44 символи):

,.&.>((];({{.)~)([:I.[:*/2=/\]))];._2]1!:1]3

Мені може знадобитися зовсім інший метод, щоб скоротити його.


1
+1. Але так, я очікую кращого від Дж.
Стівен Румбальський

Це не повинно мати менше результатів, ніж моя відповідь.
Стівен Румбальський

1
@StevenRumbalski Результати не завжди відображають відносні розміри коду. Він іноді стає змаганням з популярності мови. Я погоджуюсь, що відповідь на гольфскрип повинен бути там із APL, але, на жаль, я вже дав свою нагороду і не можу допомогти просунути її далі.
Гарет

8

Хаскелл, 64 символи

main=interact$show.foldl1(filter.flip elem).map(zip[0..]).lines

Обробляє лінії неоднакової довжини. Підтримка Unicode залежить від поточних налаштувань мови.

Приклад виводу:

[(0,'a'),(3,'d'),(6,'g')]

+1. Це не повинно мати менше результатів, ніж моя відповідь.
Стівен Румбальський

7

Python 2, оцінка 81,5 ( 116 94 86 83 82 байт мінус бонус)

import sys
i=0
for x in zip(*sys.stdin)[:-1]:
 i+=1
 if len(set(x))<2:print i,x[0]

+1 за приємний гольф Python, але ви можете втратити цілих ЧЕТВОРИХ символів: [:-1]не потрібно, якщо не викреслити сторонній новий рядок в кінці входу (про що навіть не йдеться в питанні).
ChristopheD

@ChristopheD: Власне, результат zip(*sys.stdin)є [('a', 'a', 'a'), ('b', 'v', 'c'), ('c', 'c', 'b'), ('d', 'd', 'd'), ('e', 'd', 'e'), ('f', 'e', 'e'), ('g', 'g', 'g'), ('\n', '\n', '\n')]. Я не бачу способу уникнути зняття останнього кордону нових рядків. Будь ласка, виправте мене, якщо я неправильно зрозумів. Дякую за підсумки.
Стівен Румбальський

Якщо ви видалите останній новий рядок у вашому файлі даних, кортеж для цього рядка не є повним (пропускає один '\ n', тому zip розглядає та повертає лише ті дані, які ми шукаємо, дозволяючи видалити [:-1]. Наприклад,zip([1,2,3,4],[1,2,3])=> [(1, 1), (2, 2), (3, 3)]
ChristopheD

@ChristopheD: За специфікацією, "вхід складається з [...] рядків [...], за кожним з них новий рядок."
Ільмарі Каронен

1
Чи пояснив би той, хто спростував цю відповідь?
Стівен Румбальський

5

(Баш) Оболонка сценаріїв, 105 символів

Якщо хтось має ще кілька хитрощів для цього, будь ласка, заповніть безкоштовно коментар!

for((i=1;i<`tail -1 $1|wc -c`;i++))do
x="cut -c$i $1";y=`$x`;[ `$x|uniq|wc -l` = 1 ]&& echo $i ${y:3};done

Результат:

1 а
4 д
7 г.

У мене виникають труднощі з тим, щоб це працювати; запустивши це на вхідному прикладі, виводиться ряд помилок як /tmp/cols.sh: line 2: [1: command not foundі нічого іншого.
Ільмарі Каронен

@Ilmari Karonen: це було протестовано на Mac (сніговий барс, 10.6.2), але він повинен працювати в іншому місці. Я бачу, щоб його було виправлено на Linux завтра (має бути невелике виправлення)
ChristopheD

2
ormaaj не вистачало представника, але хотів прокоментувати: він руйнується для Ілмарі через відсутність місця після [; і $ {y: 3} призведе до того, що він буде працювати лише з трьома рядками введення. Виправлення та оптимізація врожайності (100 знаків) while((++i%`tail -1 $1|wc -c`));do x=`cut -c$i $1`;((`uniq|wc -l`==1))<<<"$x"&&echo $i ${x: -1};doneта використання значень за замовчуванням повинні дозволяти зберегти ще одну, for((;++i<`tail -1 $1|wc -c`;))doале помилка в bash є нефіксованою.
Пітер Тейлор

4

Perl, 87 символів (−½ бонус за тай-брейк)

Ось гольф-версія мого власного рішення з потоку SO :

chomp($a=$b=<>);$a&=$_,$b|=$_ for<>;@$_=$$_=~/./sgfor a,b;$b[$i++]eq$_&&say"$i:$_"for@a

На відміну від версії SO, для виведення використовується 1-заснований індекс. Він використовує функцію Perl 5.10 say, тому її потрібно запускати з perl -M5.010(або з perl -E).

Як варіант SO, цей код ручки змінної довжини лінії, і буде обробляти довільний введення Unicode , якщо стандартний вхід і вихід були в режимі UTF-8. На жаль, за замовчуванням їх немає, якщо тільки не вказано невільний -CS перемикач командного рядка. Таким чином, він отримує ½ ½ бонусного бонуса, але не −¼.

Редагувати: +1 char для виправлення помилки: лише тому, що вхідні рядки не містять каналів рядків, це не означає, що вони не можуть закінчитися $a(наприклад "+" & "J" eq "\n").


1
Ви можете зберегти 1 char за допомогою, chopа не chomp.
Toto

@ M42: Хороший момент, хоча мені більше подобається надійність поточної версії. Я думаю, що я буду тримати mзараз, це не так, як це має значення для рейтингу на даний момент. :)
Ільмарі Каронен

3

T-SQL

SELECT N.number, letter = MIN(SUBSTRING(L.line, N.number, 1))
FROM Lines AS L
INNER JOIN master.dbo.spt_values AS N ON N.type = 'P'
WHERE N.number BETWEEN 1 AND (SELECT MAX(LEN(L2.line)) FROM Lines AS L2)
GROUP BY N.number
HAVING COUNT(DISTINCT SUBSTRING(L.line, N.number, 1)) = 1
ORDER BY N.number

2

Scala 115 107: (−¼ для обробки UTF-8)

io.Source.stdin.getLines.map(_.zipWithIndex).toList.flatten.groupBy(_._2).map(_._2.toSet).filter(_.size==1)

безгольовий, а Source.fromFile ("f")замість stdinкращої доцільності:

io.Source.fromFile ("f").
  getLines.map (_.zipWithIndex).
    toList.flatten.groupBy (_._2). 
      map (_._2.toSet).
        filter (_.size==1)

Результат:

List(Set((a,0)), Set((g,6)), Set((d,3)))

Завдяки Гарет за зменшення розміру 8 для використання stdin.


Не можете використовувати stdinзамість цього, fromFile("f")щоб зберегти 8 символів?
Гарет

2

VBA ( 307,25 284 - 0,75 бонус = 283,25)

Я знаю, що це вже виграно, але ось мій знімок (не читання файлу, а лише рядок - потрібно додати io). Мені подобається, що я маю використовувати l()рекурсивно. У моєму реальному програмуванні зазвичай не потрібно рекурсії. Я лише зробив стільки тестувань, але я вважаю, що це стосується умови бонусного бала за Unicode. Він також передбачає vbCr, що лінійний термінатор. Це може не перевестись у всі системи через це.

Код:

Function a(i)
b=Split(Left(i,Len(i)-1),vbCr):c=UBound(b):For q=1 To Len(b(c)):d=Mid(b(c),q,1):If l(b,c,q,d) Then a=a & q & ": " & d & vbCr:Next
End Function
Function l(m, n, o, p)
If n+1 Then l=IIf(o<=Len(m(n)),Mid(m(n),o,1)=p,0) And l(m,n-1,o,p) Else l=Mid(m(n+1),o,1)=p
End Function

Приклад введення / виводу:

Debug.Print a("abcdefghijklmnop" & vbCr & "abcdefg" & vbCr & "abcabcghijkl" & vbCr)

1: a
2: b
3: c
7: g

2

Q, 32

{a!((*:)x)a:(&)1=(#:')(?:')(+)x}

використання

q){a!((*:)x)a:(&)1=(#:')(?:')(+)x}[("abcdefg";"avcddeg";"acbdeeg")]
0| a
3| d
6| g

К, 22

Вищевказане рішення можна зменшити до 22, записавши його повністю в K, а не передаючи функції K інтерпретатору Q, зменшуючи кількість необхідних дужок.

{a!@[*x]a:&1=#:'?:'+x}

1

PHP, 123 127 :(

Я не задоволений цим (там, можливо, будуть вдосконалення), але ось що:

<?$a=$b=trim(fgets(STDIN));while($l=fgets(STDIN)){$a&=$l;$b|=$l;}$n=-1;while(@$a[++$n]){echo$a[$n]!=$b[$n]?'':"$n:{$a[$n]}\n";}

Доказ це працює.

Якщо хтось може придумати більш розумний спосіб ініціалізації $ a та $ b, будь ласка, дайте мені знати. Спочатку у мене було $a=$b=$n=''і $ b врешті-решт було правильно, але [empty] & [anything] == [empty], так що $ a ніколи не було вмісту.


Редагувати: довелося виправити обробку нового рядка (+6), але випав закриваючий тег (-2).


Мені цікаво, чому ви робите більшість своїх відповідей вікі спільноти?
Гарет

Я не мав на увазі цього. Давно, коли я вперше приєднався до CodeGolf, хтось сказав мені, що це стандарт. Доведеться порушити звичку. Неможливо скасувати його зараз. codegolf.stackexchange.com/a/2249/1419 (див. коментарі)
Містер Лама

Я думаю, це був стандартний спосіб робити речі на SO для питань з кодом про гольф, але не тут інакше ніхто не матиме репутації взагалі. :-)
Гарет

Можливо, ви можете позначити їх і попросити модератора скасувати їх . Просто поясніть, що це була помилка.
Ільмарі Каронен

Ви можете зберегти два знаки, залишивши його ?>. Однак я щойно помітив, що у вашому коді є помилка: він друкує додаткову відповідність, якщо всі рядки містять нижній рядок, як зазначено.
Ільмарі Каронен

1

JavaScript (125 134 140 )

for(i=s=[];I=s[++i]=prompt(o='');S=I);for(c=-1;w=r=++c<S.length;o+=r?c+':'+C+'\n':'')for(C=S[c];w<i;)r&=s[w++][c]==C;alert(o)

Демонстрація: http://jsfiddle.net/Fv7kY/4/

Редагувати 1 : Переставляйте петлі, щоб уникнути дужок. Ініціалізуйте i з, []щоб поєднати s. Перемістіть wприріст у вираз.

Редагувати 2 : Встановити S=Iдля захоплення останнього введеного слова та зберегти за допомогою s[1]. Об’єднайте r=1і ++c<S.length. Встановіть C=s[c]внутрішній цикл і порівняйте Cзамість попередніх та наступних слів, щоб скоротити вираз s[w][c]==s[w++][c]до просто s[w++][c]==C. Збережено всього 9 символів. Також встановити, w=r=...тому що коли це правда w=1, саме з цим нам потрібно ініціалізувати w.


1

Рубін (71)

a,*s=*$<.lines
(a.size-1).times{|i|s.all?{|t|t[i]==a[i]}&&p([i,a[i]])}

вихід:

[0, "a"]
[3, "d"]
[6, "g"]

Примітка: схоже, потрібен Ruby 1.9; для сумісності Ruby 1.8 замініть t[i]на t[i,1].
Ільмарі Каронен

1

Лисп звичайний, 183 165 символів

(let((l(loop for m =(read-line)until(equal m "")collect m)))(loop for c across(car l)for i from 0 if(null(remove c(mapcar(lambda(y)(char y i))l)))collect(list i c)))

Формат для читання:

(let ((l (loop for m = (read-line) until (equal m "") collect m)))
  (loop for c across (car l)
        for i from 0 
        if (null (remove c 
                         (mapcar (lambda(y) (char y i))l)))
        collect(list i c)))

Введіть це безпосередньо в REPL і введіть рядки, закінчуючи порожнім рядком.


1

C, 126 символів

char a[999],b[999];main(i){for(gets(a);gets(b);)for(i=0;b[i];++i)a[i]^b[i]?a[i]=0:0;
while(i--)a[i]&&printf("%d:%c\n",i,a[i]);}

Я дивився на це, але просто не можу зробити його меншим. Може знадобитися новий підхід.

(Без бонусних очок; він обробляє лише лінії різного розміру, якщо перший рядок коротший.)


0

C # з .NET 4 (280)

using c=System.Console;class P{static void Main(){char[]a=c.ReadLine().ToCharArray();int r,i,l=a.Length;m:i=0;n:r=c.Read();if(r>0&&r!=10&&r!=13){if((int)a[i]!=r)a[i]='\0';i++;goto n;}for(;i>0&&i<l;)a[i++]='\0';if(r>0)goto m;for(i=0;i<l;i++)if(a[i]!='\0')c.WriteLine(i+":"+a[i]);}}
  • 1 рядок, 280 символів
  • Включає всі необхідні з використанням операторів та основного методу.
  • Програма не вимагає порожнього рядка в кінці, але прийме його
  • Порожні рядки ігноруються
  • Обробляє вхідні рядки будь-якої довжини.
  • Зарезервує вихід до кінця (тоді як вихідна відповідь передбачала поступовий вихід)

Читаема версія

    char[]a=c.ReadLine().ToCharArray();
    int r,i,l=a.Length;
    m:
    i=0;
    n:
    r=c.Read();
    if(r>0&&r!=10&&r!=13){
        if((int)a[i]!=r)
            a[i]='\0';
        i++;
        goto n;
    }
    for(;i>0&&i<l;)
        a[i++]='\0';
    if(r>0)
        goto m;
    for(i=0;i<l;i++)
        if(a[i]!='\0')
            c.WriteLine(i+":"+a[i]);

Оригінальна відповідь

використовуючи c = System.Console; клас P {статичний недійсний Main () {char [] a; var b = c.ReadLine (); a = b.ToCharArray (); while (b! = "") {for (int i = 0; i

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


Читаема версія:

    static void Readable()
    {
        char[]a;
        string b=System.Console.ReadLine();
        a=b.ToCharArray();
        while(b.Length>0)
        {
            for (int i = 0; i < a.Length; i++)
            {
                if (a[i] != b[i])
                {
                    a[i] = '\0';
                }
                else
                {
                    System.Console.WriteLine(i+": "+a[i]);
                }
            }
            b=System.Console.ReadLine();
        }
    }


Коли я запускаю це на тестовому вході у виклику, я отримую 0: a 1: b 2: c 3: d 4: e 5: f 6: g 0: a 2: c 3: d 6: g 0: a 3: d 6: g. Очікуваний результат буде 0: a 3: d 6: g.
Ільмарі Каронен

@Ilmari Добре, але він виводить стовпці / символи, однакові після кожного рядка введення. Якщо ви подаєте файл у якості стандартного вводу, то вихід може здатися дивним, але якщо ви вводите вручну, я думаю, це має сенс. Я, однак, подумаю, як зробити рефактор.
Учень доктора Вілі

Ваше рішення виходить з ладу, якщо будь-який рядок довший першого.
Тімві

@Timwi Ах ... спасибі за вказівку на це!
Учень доктора Вілі

0

пітон 122 символів :

print("\n".join([str(i)+':'+str(x[0]) for i,x in enumerate(zip(*[tuple(x) for x in input().split()])) if len(set(x))<2]))

вам не потрібен пробіл між )і for. Тож замість цього …str(x[0]) for i,x…можна зробити …str(x[0])for i,x…. Він також з'являється у tuple(x) forта.split()])) if
Cyoce

-1

Рубін (242)

s = %w{ abcdefg avcddeg acbdeeg aejdjeggd }
cols = []
s.sort{ |a, b| b.size <=> a.size }[0].size.times do |i|
  uneq=true
  l = s[0][i]
  s.each { |w| uneq = false if l != w[i] }
  cols << [l, i] if uneq
end
cols.each { |c| puts c.join('|') }

Завданням завдання було прочитати рядки зі стандартного введення. Я готовий скоротити певну слабкість для мов (наприклад, у браузері JavaScript), де ця концепція насправді не існує, але у Ruby є STDIN( ARGFабо просто звичайна gets).
Ільмарі Каронен

ах гаразд. Але враховуючи, що STDIN приймає один рядок, чи припускати щось на кшталт: "Введіть в інший рядок, або" n "для зупинки"? Тому створіть цикл для створення масиву.
agmcleod

До питання я додав кілька уточнень. По суті, вам слід тримати читання рядків для введення, поки не досягнете кінця файлу.
Ільмарі Каронен

у вас є тонна зайвого пробілу.
Cyoce

-1

C #

List<string> strings = new List<string> { "abcdefg", "avcddeg", "acbdeeg", "aejdjeggd" };
var transposes = from index in Enumerable.Range(0, strings.First().Length)
                 select new string((from s in strings select s[index]).ToArray());
int i = 0;
foreach(string transpose in transposes)
{
   if (transpose.Distinct().Count() == 1)
     Console.WriteLine("{0}: {1}", i , transpose[0]);
   i++;
}

1
Привіт, Арджанг, і ласкаво просимо до codegolf.SE! Кілька коментарів до вашої відповіді: По-перше, оскільки це проблема з кодовим гольфом , ви, як очікується, спробуєте зробити своє рішення якомога коротшим; для початку, у вас є кілька довгих імен змінних, які ви можете легко скоротити до окремих символів, і кілька зайвих пробілів, які ви могли б видалити. (Добре розміщувати читану версію коду разом із "гольф", але ви також повинні також розмістити рішення про гольф.) По-друге, якщо ви уважно прочитали питання, я вказав, що ви повинні прочитати рядки зі стандартного вводу , не жорсткий код.
Ільмарі Каронен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.