Tables de vérité: l'ordinateur de votre arrière-grand-père


13

Si vous vous souvenez de vos années de scolarité, vous vous souvenez peut-être avoir appris les tables de vérité . Ils semblaient ennuyeux, mais ils sont à la base de la logique et (certains diront) de l'informatique ...


Problème

Votre mission, si vous choisissez de l'accepter, est d'écrire un programme, une fonction ou un widget de code qui peut produire une entrée de table de vérité.

Contribution

L'entrée sera une chaîne (comme la structure de données) contenant l'instruction logique dans laquelle faire la table de vérité. Par exemple:

p ∧ q

Cela signifie p and q(conjonction logique) et produira:

 p  q  p ∧ q
 T  T    T
 T  F    F
 F  T    F
 F  F    F            

Remarquez l'espacement: l'élément de la colonne est au centre de l'en-tête

Personnages

Score via des caractères, pas des octets Les caractères de comparaison logique sont spéciaux et ne sont pas toujours à quoi ils ressemblent. Utilisez ces caractères:

Conjonction logique (AND): U + 2227

Disjonction logique (OR): U + 2228

Négation logique (NON) ~ou ¬U + 7e et U + ac respectivement


Bonus

Tous ces bonus sont facultatifs, mais vous feront perdre des points à votre score. Choisissez-en.

Négation logique

La négation logique est un opérateur unaire dans les tables de vérité. C'est l'équivalent de !dans la plupart des langages basés sur C. Cela fait false=> trueet vice versa. Il est noté avec un ¬ ou ~ (vous devez prendre en charge les deux). Soutenir cela réduira 10% de votre score. Vous devez cependant ajouter une colonne supplémentaire pour afficher ses résultats: Par exemple:

~p ∧ q

affichera:

p  ~p  q  ~p ∧ q
T  F   T     F
T  F   F     F
F  T   T     T
F  T   F     F

Jolie impression

La notation de table normale est ennuyeuse. Faisons-le joli! Le joli format d'impression est le suivant pour p ∧ qest le suivant:

+---+---+-------+
| p | q | p ∧ q |
+---+---+-------+
| T | T |   T   |
+---+---+-------+
| T | F |   F   |
+---+---+-------+
| F | T |   F   |
+---+---+-------+
| F | F |   F   |
+---+---+-------+

Détails spéciaux pour une jolie impression:

  • Il y a 1 espace de remplissage dans chaque cellule
  • Les valeurs des cellules sont toujours centrées

Si vous imprimez assez vos tableaux, à partir de votre code, puis multipliez par 0,6. Utilisez cette fonction pour ce bonus:

score = 0.6 * code

Exemples

p ∧ q:

p  q  p ∧ q
T  T    T
T  F    F
F  T    F
F  F    F

p ∨ q:

p  q  p ∨ q
T  T    T
T  F    T
F  T    T
F  F    F

~p ∧ q:

p  ~p  q  ~p ∧ q
T   F  T     F
T   F  F     F
F   T  T     T
F   T  F     F

~p ∨ q:

p  ~p  q  ~p ∧ q
T   F  T     T
T   F  F     F
F   T  T     T
F   T  F     T

Règles

  • Des échappatoires standard s'appliquent
  • Pas de ressources externes
  • Si vous allez enfreindre les règles, soyez intelligent;)

Le code le plus court (en caractères) gagne. Bonne chance!


4
D'après la description, cela ressemblait à des expressions booléennes arbitraires. Mais tous les exemples (sans bonus) n'ont qu'un seul opérateur. Est-ce limité à un seul opérateur? En outre, les noms des valeurs dans les exemples sont tous pet q. À moins qu'ils aient toujours ces noms, vous souhaiterez peut-être afficher quelques options différentes dans les exemples de test. S'agit-il toujours d'une seule lettre?
Reto Koradi

2
Puisqu'il utilise des caractères non ASCII, il peut être utile de spécifier si la longueur du code est comptée en caractères ou octets. S'il s'agit d'octets, il serait utile de savoir combien d'octets les caractères unicode utilisent.
Reto Koradi

Simplifier :). score = 0.6 * (code - 15)=.6 * code - 9
mınxomaτ

@RetoKoradi modifié. Score par caractères, pas d'octets
MayorMonty

@RetoKoradi Si ce que mon professeur de géométrie me dit est correct, vous n'en verrez jamais plus p qet rdans une table de vérité;)
MayorMonty

Réponses:


6

JavaScript (ES6), 141

Fonction simple, pas de bonus, 141 caractères. (140 uft8, 1 unicode de large)

Gestion des fonctions complexes ~ ou ¬, 254 caractères (253 utf, 1 unicode large), score 229

Pourrait économiser 6 octets en utilisant alertau lieu de console.log, mais alertest particulièrement impropre à afficher des tableaux.

Testez l'exécution de l'extrait ci-dessous dans un navigateur compatible EcmaScript 6 (testé avec Firefox. Ne fonctionnera pas dans Chrome car Chrome ne le prend pas en charge .... De plus, la version bonus utilise une extension splitqui est spécifique à Firefox).

/* TEST: redirect console.log into the snippet body */ console.log=x=>O.innerHTML+=x+'\n'

// Simple
F=s=>{[a,o,b]=[...s],z='  ',r=a+z+b+z+a+` ${o} ${b}
`;for(w='FT',n=4;n--;r+=w[c]+z+w[e]+z+z+w[o<'∧'?c|e:c&e]+`
`)c=n&1,e=n>>1;console.log(r)}

// Simple, more readable
f=s=>{
   [a,o,b]=[...s]
   r=a+'  '+b+'  '+a+` ${o} ${b}\n`
   for(w='FT',n=4; n--; )
   {
     c = n&1, e = n>>1, x=o<'∧' ? c|e : c&e
     r += w[c]+'  '+w[e]+'    '+w[x]+'\n'
   }
   console.log(r)
}

// 10% Bonus
B=s=>{[a,o,b]=s.split(/([∧∨])/),t=a>'z',u=b>'z',z='  ',r=(t?a[1]+z:'')+a+z+(u?b[1]+z:'')+b+z+a+` ${o} ${b}
`;for(s=v=>'FT'[v]+z,n=4;n--;r+=s(c)+(t?s(d)+' ':'')+s(e)+(u?s(f)+' ':'')+(t?'   ':z)+s(o<'∧'?d|f:d&f)+`
`)c=n&1,d=c^t,e=n>>1,f=e^u;console.log(r)}

Test1 = ['q∨p','q∧p']
Test2 = Test1.concat([
  '~q∨p','q∨~p','~q∨~p','~q∧p','q∧~p','~q∧~p',
  '¬q∨p','q∨¬p','¬q∨¬p','¬q∧p','q∧¬p','¬q∧¬p'
])


console.log('SIMPLE')
Test1.forEach(t=>F(t));

console.log('BONUS')
Test2.forEach(t=>B(t));
<pre id=O></pre>


1
+1, j'aime JavaScript et cette solution mérite un vote positif.
Arjun

JavaScript est ma langue maternelle, mais je ne laisserai pas cela m'affecter! : D Bon travail!
MayorMonty

6

Modèle MediaWiki - 2347 caractères

MediaWiki a une fonction de modèle intégrée appelée {{#expr}}qui peut gérer les expressions logiques. Ce doit être le défi parfait pour les modèles MediaWiki! Des fonctionnalités telles que des variables, des boucles et une syntaxe lisible auraient cependant aidé un peu. De plus, le fait qu'il n'y ait pas d'opérateur NOT pour la fonction expr la rendait un peu plus complexe.

{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}} {{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}} {{{1}}}<br>T T &nbsp;{{#if:{{#pos:{{#sub:{{#replace:{{{1}}}|~|¬}}|0|1}}|¬}}|&nbsp;|}} {{#replace:{{#replace:{{#expr:{{#replace:{{#replace:{{#replace:{{#replace:{{#replace:{{#replace:{{{1}}}|~|¬}}|{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}}|{{#if:{{#pos:{{#replace:{{{1}}}|~|¬}}|¬{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}}}}|0|1}}}}|{{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}}|{{#if:{{#pos:{{#replace:{{{1}}}|~|¬}}|¬{{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}}}}|0|1}}|0|1}}|¬|}}|∧|and}}|∨|or}}}}|1|T}}|0|F}}<br>T F &nbsp;{{#if:{{#pos:{{#sub:{{#replace:{{{1}}}|~|¬}}|0|1}}|¬}}|&nbsp;|}} {{#replace:{{#replace:{{#expr:{{#replace:{{#replace:{{#replace:{{#replace:{{#replace:{{#replace:{{{1}}}|~|¬}}|{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}}|{{#if:{{#pos:{{#replace:{{{1}}}|~|¬}}|¬{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}}}}|0|1}}}}|{{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}}|{{#if:{{#pos:{{#replace:{{{1}}}|~|¬}}|¬{{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}}}}|1|0}}|1|0}}|¬|}}|∧|and}}|∨|or}}}}|1|T}}|0|F}}<br>F T &nbsp;{{#if:{{#pos:{{#sub:{{#replace:{{{1}}}|~|¬}}|0|1}}|¬}}|&nbsp;|}} {{#replace:{{#replace:{{#expr:{{#replace:{{#replace:{{#replace:{{#replace:{{#replace:{{#replace:{{{1}}}|~|¬}}|{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}}|{{#if:{{#pos:{{#replace:{{{1}}}|~|¬}}|¬{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}}}}|1|0}}}}|{{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}}|{{#if:{{#pos:{{#replace:{{{1}}}|~|¬}}|¬{{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}}}}|0|1}}|0|1}}|¬|}}|∧|and}}|∨|or}}}}|1|T}}|0|F}}<br>F F &nbsp;{{#if:{{#pos:{{#sub:{{#replace:{{{1}}}|~|¬}}|0|1}}|¬}}|&nbsp;|}} {{#replace:{{#replace:{{#expr:{{#replace:{{#replace:{{#replace:{{#replace:{{#replace:{{#replace:{{{1}}}|~|¬}}|{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}}|{{#if:{{#pos:{{#replace:{{{1}}}|~|¬}}|¬{{#sub:{{#replace:{{#replace:{{{1}}}|~|}}|¬|}}|0|1}}}}|1|0}}}}|{{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}}|{{#if:{{#pos:{{#replace:{{{1}}}|~|¬}}|¬{{#sub:{{{1}}}|{{#expr:{{#len:{{{1}}}}}-1}}|{{#len:{{{1}}}}}}}}}|1|0}}|1|0}}|¬|}}|∧|and}}|∨|or}}}}|1|T}}|0|F}}

Tester:

{{TemplateName|¬X ∧ ~Y}}

{{TemplateName|p ∨ q}}

Résultat:

X Y ¬X ∧ ~Y
T T    F
T F    F
F T    F
F F    T

p q p ∨ q
T T   T
T F   T
F T   T
F F   F

Je suppose que MediaWiki> = 1.18, où les extensions ParserFunctions sont livrées avec le logiciel.


2
Bienvenue dans Programming Puzzles et Code Golf. L'utilisation de MediaWiki n'est pas quelque chose à laquelle j'aurais pensé; +1. Cependant, le comportement de colonne supplémentaire de l' opérateur ¬/ ~est manquant; si vous l'ajoutez, vous aurez droit à un 10%bonus.
wizzwizz4

Je viens de réaliser que, sauf si je peux utiliser des modèles imbriqués (qui pourraient étirer un peu trop les règles?), L'ajout correct de cette colonne augmenterait en fait le nombre de caractères ... :)
leo

Dans ce cas, vous devriez probablement supprimer le support de négation, car vous n'obtenez aucun bonus pour cela.
wizzwizz4

Ouaip, va y réfléchir. Je ne pense pas que cela aura beaucoup d'impact sur le classement final ...: D
leo

1
@leo C'est génial, et je pense que l'utilisation de modèles imbriqués serait bien si vous ajoutez simplement le nombre de caractères des deux, ce qui semble être la pratique acceptée de nos jours.
Harry

4

Python - 288 caractères (pénalité de +10 car je n'ai pas pu faire fonctionner l'unicode: c)

Pas de bonus. Ceci est ma toute première réponse codegolf.

def f(i):
    i=i.split(" ")
    print i[0],i[2],
    for f in i[0:3]: print f,
    print ""
    for t in["TT","TF","FT","FF"]:
        p,q=t[0],t[1]
        y = t[0]+" "+t[1]
        if i[1]=="^": r=(False,True)[p==q]
        if i[1]=="v": r=(False,True)[p!=q]
        if r: y+="   T"
        else: y+="   F"
        print y

i est l'entrée.

EDIT: Suppression de quelques espaces et il utilise maintenant la fonction args comme entrée.


1
Bienvenue chez PP&CG! Veuillez vous assurer que votre code respecte les règles, selon la question. En tant que spécification de règle, votre code doit être une fonction, un programme complet ou un morceau de code. Cela implique que l'entrée DOIT être STDIN ou des arguments de fonction (ou équivalent) Happy Coding!
MayorMonty

3

Dyalog APL , 58 48 caractères

Requiert ⎕IO←0, qui est par défaut sur de nombreux systèmes. Prend la chaîne comme argument.

{('p q ',⍵)⍪'FT '[p,q,⍪⍎⍵]\⍨324⊤⍨9⍴≢p q←↓2 2⊤⌽⍳4}

Aucun bonus, mais du côté positif, tout opérateur fonctionne.

⍳4 quatre premiers indices (0 1 2 3)

 marche arrière (3 2 1 0)

2 2⊤ table booléenne deux bits

 divisé en une liste de listes à deux éléments (bits hauts, bits bas)

p q← stocker sous p et q

 les compter (2) *

9⍴ remodeler cycliquement à la longueur 9 (2 2 2 2 2 2 2 2 2)

324⊤⍨ coder ainsi 324, c'est-à-dire en binaire 12 bits (1 0 1 0 0 0 1 0 0)

\⍨ utiliser cela pour développer (insérer un espace pour chaque 0) ...

'FT '[... ] la chaîne "FT", indexée par

⍎⍵ l'argument exécuté (valide puisque p et q ont maintenant des valeurs)

en faire une matrice de colonnes

q, ajouter une colonne composée de q (1 1 0 0)

q, ajouter une colonne composée de p (1 0 1 0)

(... )⍪ insérer une ligne au-dessus, composée de

 l'argument

'p q ', ajouté avec la chaîne "p q"


* Veuillez lire ce problème si vous voyez comme ≢et non comme ̸≡.


2

Julia, 161 octets

Pas de bonus.

s->(S=split(s);P=println;p=S[1];q=S[3];a=[&,|][(S[2]=="∨")+1];c="  ";P(p,c,q,c,s);for t=["TT","TF","FT","FF"] P(t[1],c,t[2],c^2,"FT"[a(t[1]>'F',t[2]>'F')+1])end)

Non golfé:

function f(s::String)
    # Split the input on spaces
    S = split(s)

    # Separate out the pieces of the statement
    p = S[1]
    q = S[3]
    a = [&, |][(S[2] == "∨") + 1]

    # Print the header
    println(p, "  ", q, "  ", s)

    # Create the table entries in a loop
    for t = ["TT", "TF", "FT", "FF"]
        println(t[1], "  ", t[2], "    ", "FT"[a(t[1] > 'F', t[2] > 'F') + 1])
    end
end

1

Mathematica, 129 octets

Golfé:

t=InputString[];s=Append[StringCases[t,LetterCharacter],t];Grid[Prepend[Map[If[#,"T","F"]&,BooleanTable[ToExpression[s]],{2}],s]]

Non golfé:

(*Take input*)
t=InputString[];
(* Find all occurrences of letters and append the final statement.*)
s=Append[StringCases[t,LetterCharacter],t];
(* Evaluate the list as expressions and create a boolean table of True/False values, then display as a table. *)
(* To satisfy the output conditions, we must convert each True/False to T/F *)
Grid[Prepend[Map[If[#,"T","F"]&,BooleanTable[ToExpression[s]],{2}],s]]

Pas un expert de Mathematica, mais j'ai trouvé cela plutôt élégant par rapport à la comparaison directe des caractères.

J'avais une solution qui fonctionnait pour la négation, mais elle était plus longue que la réduction du score ne décollerait.

Selon ce qui se qualifie pour une jolie impression, je pourrais essayer ce bonus. J'ai l'impression que la sortie en ASCII dans Mathematica serait beaucoup trop coûteuse pour que la réduction du score compense, mais si les deux fonctionnalités principales sont une bordure en pointillés et un remplissage spécifié à l'intérieur des cellules, ce ne sont que quelques options dans Grid.

Avec une jolie impression, 171 * 0,6 = 102,6 octets

t=InputString[];s=Append[StringCases[t,LetterCharacter],t];Grid[Prepend[Map[If[#,"T","F"]&,BooleanTable[ToExpression[s]],{2}],s],Spacings->1,Frame->All,FrameStyle->Dashed]

1

Python3, 145 139 120 119 Octets

Pas de bonus (avec bonus à la fin)

 def f(s):
 a,m,b=s.split(" ");print(a,b,s);F,T,c=0,1,"FT"
 for p in c:
  for q in c:print(p,q," ",c[eval(p+"+*"[m=="∧"]+q)>0])

Besoin de Python3 pour le support Unicode prêt à l'emploi.

Basé sur le code Python de DJgamer98, comprendre sa table n'est pas correct.

Edit1: division en variables distinctes et omission de la variable de chaîne d'opérateur

Edit2: (ab) utilisant F et T comme variables et caractères de chaîne

Edit3: économiser un espace grâce à NoOneIsHere

Avec Bonus, 215 * 0,6 = 129

def f(s):
 r="+---"*3+"----+"
 a,m,b=s.split(" ");F,T,c=0,1,"FT"
 print("%s\n| %s | %s | %s |\n%s"%(r,a,b,s,r));
 for p in c:
  for q in c: print("| %s | %s |   %s   |\n%s"%(p,q,c[eval(p+"+*"[m=="∧"]+q)>0],r));

Bienvenue chez PPCG! Vous pouvez enregistrer un octet en supprimant l'espace après q in c:.
NoOneIsHere

Edit2: Ce n'est pas un abus. Voir ici , où j'utilise le premier caractère du contenu du fichier comme nom de fichier!
Adám

1

C / C ++ 302 octets

335 caractères moins 10% pour la gestion de la négation. Formatage incomplet mais soumission avant de voir quel est l'impact de l'achèvement.

Marqué comme C / C ++ parce que mes gcc et g ++ l'acceptent avec -fpermissive et il me semble beaucoup plus C que C ++.

#include <stdio.h>
void T(char*S) { int (*P)(char*,...)=printf;char*v[2]={"F","T"};for(int m=4;m--;){P("|");char*s=S;int x=m&1;X:P(" %s |",v[x]);if(*++s!=' '){x=x^1;goto X;}char*o=++s;s+=3;int y=(m>>1)&1;Y:P(" %s |",v[y]);if(*++s){y=y^1;goto Y;}int g;for(g=o-S+1;g--;)P(" ");P(*++o==39?v[x&y]:v[x|y]);for(g=s-o;g--;)P(" ");P("|\n");}}

Je suis sûr qu'il y a probablement quelques ajustements qui pourraient être appliqués. En fait, la gestion des nots ajoute plus que le bonus de 10% ne supprime.

Cela suppose que le format d'entrée est comme indiqué, c'est-à-dire 2 valeurs d'entrée (p et q), avec ou sans le préfixe not et rien d'autre, et tous les jetons délimités par un seul espace.

Non golfé:

void ungolfed(char* S)
{
   int (*P)(char*,...) = printf;         // useful lookup stuff
   char* v[2] = {"F","T"};

   for(int m = 4; m--;) {                // loop over all 2 bit bit patterns (truth table inputs)

      P("|");                            // start of line format
      char* s=S;                         // iterator to start of equation for each bit pattern

      int x = m&1;                       // input 1 (aka. p which I called x here to be awkward)
X:    P(" %s |",v[x]);                   // input 1 output and format

      if(*++s!=' ') {                    // if next character is not a space then input must be prefixed with the not character
         x=x^1;                          // so negate the input
         goto X;                         // and redo input 1 output
      }

      char* o = ++s;                     // remember where the operator is
      s+=3;                              // and skip it and following space

      int y = (m>>1)&1;                  // input 2 (aka. q which I called y obviously) processing as for input 1
Y:    P(" %s |",v[y]);

      if(*++s) {
         y=y^1;
         goto Y;
      }

      int g;

      for(g=o-S+1;g--;) P(" ");         // pre-result value padding

      P(*++o==39?v[x&y]:v[x|y]);      // result

      for(g=s-o;g--;) P(" ");           // post-result value padding and format
      P("|\n");
   }
}

et les tests:

int main()
{
   T("p \x22\x27 q");  // p & q
   puts("");

   T("p \x22\x28 q");  // p | q
   puts("");

   T("\x7ep \x22\x27 q");  // ~p & q
   puts("");

   T("\xacp \x22\x28 q");  // ~p | q
   puts("");

   T("p \x22\x28 \xacq");  // p | ~q
   puts("");

   return 0;
}

0

Mathematica, 128 caractères

TraditionalForm@Grid[({#}~Join~BooleanTable[#,Cases[b,_Symbol,{0,∞}]]&/@Cases[b=ToExpression@#,_,{0,∞}]/.{0<1->"T",0>1->"F"})]&

est le caractère à usage privé U+F3C7représentant \[Transpose].

Heureusement pour nous les golfeurs de Mathematica, et représentons déjàAnd et Or, donc nous devons faire tout est convertir la chaîne d'entrée en une expression Mathematica et nous pouvons faire des opérations logiques symboliques sur elle.

Notez que cette solution gérera également Not( ¬), Implies( ), Equivalent( ), Xor( ), Nand( ), Xor( ) et Nor( ), mais elle n'obtient pas le bonus car il ~ps'agit d'une erreur de syntaxe dans Mathematica. Meh.

entrez la description de l'image ici

Explication

b=ToExpression@#

Convertit la chaîne d'entrée en une expression Mathematica et la stocke dans b.

Cases[b=ToExpression@#,_,{0,∞}]

Il s'agit d'une liste de toutes les sous-expressions possibles de l'entrée. Chacun recevra sa propre colonne.

Cases[b,_Symbol,{0,∞}]

Il s'agit d'une liste de toutes les variables qui apparaissent dans l'entrée.

BooleanTable[#,Cases[b,_Symbol,{0,∞}]]&

Fonction pure qui prend une expression d'entrée #et renvoie une liste de valeurs de vérité pour toutes les combinaisons possibles de valeurs de vérité pour les variables.

{#}~Join~BooleanTable[...]

Ajoute l'expression elle-même à cette liste.

.../@Cases[b=ToExpression@#,_,{0,∞}]

Applique cette fonction à chaque sous-expression de l'entrée.

.../.{0<1->"T",0>1->"F"}

Remplacez ensuite true ( 0<1) par "T" et false ( 0>1) par "F".

(...)

Échangez des lignes et des colonnes.

Grid[...]

Affichez le résultat sous forme de Grid.

TraditionalForm@Grid[...]

Convertissez le Griden forme traditionnelle afin qu'il utilise les symboles fantaisie.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.