Come possiamo abbinare a ^ nb ^ n con regex Java?


99

Questa è la seconda parte di una serie di articoli regex educativi. Mostra come i lookaheads e i riferimenti annidati possono essere usati per abbinare la lingua non regolare a n b n . I riferimenti annidati vengono introdotti per la prima volta in: In che modo questa regex trova numeri triangolari?

Uno dei linguaggi archetipici non regolari è:

L = { an bn: n > 0 }

Questo è il linguaggio di tutte le stringhe non vuote costituite da un certo numero di a's seguito da un numero uguale di bs. Esempi di stringhe in questa lingua sono ab, aabb, aaabbb.

Questa lingua può essere dimostrata non regolare dal lemma di pompaggio . Si tratta infatti di un linguaggio archetipico context-free , che può essere generato dalla grammatica context-free S → aSb | ab .

Tuttavia, le moderne implementazioni di regex riconoscono chiaramente più dei semplici linguaggi normali. Cioè, non sono "regolari" per definizione della teoria del linguaggio formale. PCRE e Perl supportano regex ricorsivo e .NET supporta la definizione di gruppi di bilanciamento. Anche le caratteristiche meno "fantasiose", ad esempio la corrispondenza del backreference, significano che regex non è regolare.

Ma quanto sono potenti queste funzionalità "di base"? Possiamo riconoscere Lcon Java regex, per esempio? Forse possiamo combinare lookarounds e riferimenti annidati e hanno un modello che funziona con ad esempio String.matchesper abbinare le stringhe come ab, aabb, aaabbb, ecc?

Riferimenti

Domande collegate


4
Questa serie è stata avviata con il permesso di alcuni membri della comunità ( meta.stackexchange.com/questions/62695/… ). Se la ricezione è buona, ho intenzione di continuare a coprire altre caratteristiche più avanzate e più basilari di regex.
lubrificanti poligenici


Wow, non ho mai saputo che le regex di Java non si limitassero alle espressioni regolari. Immagino che questo spieghi perché ho sempre pensato che non fossero completamente implementati. Quello che voglio dire è che non ci sono complementi, differenze o operatori di prodotto incorporati in Java Regex, ma questo ha senso poiché non sono limitati ai linguaggi regolari.
Lan

Questa domanda è stata aggiunta alle domande frequenti sulle espressioni regolari di Stack Overflow , in "Advanced Regex-Fu".
aliteralmind

Risposte:


139

La risposta è, inutile dirlo, SI! Puoi sicuramente scrivere un pattern regex Java che corrisponda a n b n . Utilizza un lookahead positivo per l'asserzione e un riferimento annidato per il "conteggio".

Piuttosto che fornire immediatamente lo schema, questa risposta guiderà i lettori attraverso il processo di derivazione. Vengono forniti vari suggerimenti man mano che la soluzione viene costruita lentamente. In questo aspetto, si spera che questa risposta conterrà molto di più di un semplice modello regex pulito. Si spera che i lettori impareranno anche a "pensare in regex" e a mettere insieme armoniosamente vari costrutti, in modo da poter ricavare più modelli da soli in futuro.

Il linguaggio utilizzato per sviluppare la soluzione sarà PHP per la sua concisione. Il test finale una volta finalizzato il pattern verrà eseguito in Java.


Passaggio 1: guarda avanti per l'affermazione

Cominciamo con un problema più semplice: vogliamo trovare una corrispondenza a+all'inizio di una stringa, ma solo se è seguita immediatamente da b+. Possiamo usare ^per ancorare la nostra corrispondenza, e poiché vogliamo solo trovare la corrispondenza a+senza b+, possiamo usare l' asserzione lookahead(?=…) .

Ecco il nostro modello con un semplice test harness:

function testAll($r, $tests) {
   foreach ($tests as $test) {
      $isMatch = preg_match($r, $test, $groups);
      $groupsJoined = join('|', $groups);
      print("$test $isMatch $groupsJoined\n");
   }
}
 
$tests = array('aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb');
 
$r1 = '/^a+(?=b+)/';
#          └────┘
#         lookahead

testAll($r1, $tests);

L'output è ( come visto su ideone.com ):

aaa 0
aaab 1 aaa
aaaxb 0
xaaab 0
b 0
abbb 1 a

Questo è esattamente l'output che vogliamo: confrontiamo a+, solo se è all'inizio della stringa e solo se è immediatamente seguito da b+.

Lezione : puoi utilizzare modelli nei lookaround per fare asserzioni.


Passaggio 2: acquisizione in una prospettiva (e modalità di spaziatura libera)

Ora diciamo che anche se non vogliamo b+che faccia parte della partita, vogliamo catturarlo comunque nel gruppo 1. Inoltre, poiché prevediamo di avere uno schema più complicato, usiamo il xmodificatore per la spaziatura libera, quindi dobbiamo può rendere la nostra regex più leggibile.

Basandosi sul nostro precedente snippet PHP, ora abbiamo il seguente modello:

$r2 = '/ ^ a+ (?= (b+) ) /x';
#                └──┘ 
#                  1  
#             └────────┘
#              lookahead
 
testAll($r2, $tests);

L'output è ora ( come visto su ideone.com ):

aaa 0
aaab 1 aaa|b
aaaxb 0
xaaab 0
b 0
abbb 1 a|bbb

Nota che ad esempio aaa|bè il risultato di joinciò con cui ogni gruppo ha catturato '|'. In questo caso, il gruppo 0 (ovvero ciò che il modello corrisponde) acquisito aaae il gruppo 1 catturato b.

Lezione : puoi catturare all'interno di un lookaround. È possibile utilizzare la spaziatura libera per migliorare la leggibilità.


Passaggio 3: refactoring del lookahead nel "loop"

Prima di poter introdurre il nostro meccanismo di conteggio, dobbiamo apportare una modifica al nostro modello. Attualmente, il lookahead è al di fuori del +"loop" di ripetizione. Questo va bene fino ad ora perché volevamo solo affermare che c'è un b+nostro che segue a+, ma quello che vogliamo veramente fare alla fine è affermare che per ogni acorrispondenza all'interno del "ciclo", c'è un corrispondente bda seguire.

Non preoccupiamoci del meccanismo di conteggio per ora e facciamo semplicemente il refactoring come segue:

  • Primo refactoring a+di (?: a )+(nota che (?:…)è un gruppo non di acquisizione)
  • Quindi sposta il lookahead all'interno di questo gruppo di non acquisizione
    • Nota che ora dobbiamo "saltare" a*prima di poter "vedere" b+, quindi modifica il modello di conseguenza

Quindi ora abbiamo quanto segue:

$r3 = '/ ^ (?: a (?= a* (b+) ) )+ /x';
#                     └──┘  
#                       1   
#               └───────────┘ 
#                 lookahead   
#          └───────────────────┘
#           non-capturing group

L'output è lo stesso di prima ( visto su ideone.com ), quindi non ci sono cambiamenti al riguardo. La cosa importante è che ora facciamo l'affermazione ad ogni iterazione del +"ciclo". Con il nostro modello attuale, questo non è necessario, ma in seguito faremo in modo che il gruppo 1 "conti" per noi usando l'autoreferenzialità.

Lezione : puoi acquisire all'interno di un gruppo non di acquisizione. I lookaround possono essere ripetuti.


Passaggio 4: questo è il passaggio in cui iniziamo a contare

Ecco cosa faremo: riscriveremo il gruppo 1 in modo che:

  • Alla fine della prima iterazione di +, quando il primo aè abbinato, dovrebbe acquisireb
  • Alla fine della seconda iterazione, quando un altro aviene abbinato, dovrebbe acquisirebb
  • Alla fine della terza iterazione, dovrebbe acquisire bbb
  • ...
  • Alla fine dell'n -esima iterazione, il gruppo 1 dovrebbe catturare b n
  • Se non ce ne sono abbastanza bper catturare nel gruppo 1, l'asserzione semplicemente fallisce

Quindi il gruppo 1, che è ora (b+), dovrà essere riscritto in qualcosa di simile (\1 b). Cioè, proviamo ad "aggiungere" a ba quale gruppo 1 catturato nell'iterazione precedente.

C'è un piccolo problema qui in quanto a questo modello manca il "caso base", cioè il caso in cui può corrispondere senza l'autoreferenzialità. È richiesto un caso di base perché il gruppo 1 inizia "non inizializzato"; non ha ancora catturato nulla (nemmeno una stringa vuota), quindi un tentativo di autoreferenzialità fallirà sempre.

Ci sono molti modi per aggirare questo problema, ma per ora rendiamo facoltativa la corrispondenza autoreferenziale , ad es \1?. Questo può o non può funzionare perfettamente, ma vediamo solo cosa fa, e se c'è qualche problema, attraverseremo quel ponte quando ci arriveremo. Inoltre, aggiungeremo altri casi di test mentre ci siamo.

$tests = array(
  'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb'
);
 
$r4 = '/ ^ (?: a (?= a* (\1? b) ) )+ /x';
#                     └─────┘ | 
#                        1    | 
#               └──────────────┘ 
#                   lookahead    
#          └──────────────────────┘
#             non-capturing group

L'output è ora ( come visto su ideone.com ):

aaa 0
aaab 1 aaa|b        # (*gasp!*)
aaaxb 0
xaaab 0
b 0
abbb 1 a|b          # yes!
aabb 1 aa|bb        # YES!!
aaabbbbb 1 aaa|bbb  # YESS!!!
aaaaabbb 1 aaaaa|bb # NOOOOOoooooo....

A-ha! Sembra che siamo davvero vicini alla soluzione ora! Siamo riusciti a far "contare" il gruppo 1 usando l'autoreferenzialità! Ma aspetta ... qualcosa non va nel secondo e nell'ultimo test case !! Non sono abbastanzab s, e in qualche modo ha contato male! Esamineremo il motivo per cui ciò è accaduto nel passaggio successivo.

Lezione : un modo per "inizializzare" un gruppo autoreferenziale consiste nel rendere facoltativa la corrispondenza autoreferenziale.


Passaggio 4½: capire cosa è andato storto

Il problema è che poiché abbiamo reso facoltativo l'abbinamento autoreferenziale, il "contatore" può "reimpostare" a 0 quando non ce ne sono abbastanza b. Esaminiamo attentamente cosa succede ad ogni iterazione del nostro pattern con aaaaabbbinput.

 a a a a a b b b

# Initial state: Group 1 is "uninitialized".
           _
 a a a a a b b b
  
  # 1st iteration: Group 1 couldn't match \1 since it was "uninitialized",
  #                  so it matched and captured just b
           ___
 a a a a a b b b
    
    # 2nd iteration: Group 1 matched \1b and captured bb
           _____
 a a a a a b b b
      
      # 3rd iteration: Group 1 matched \1b and captured bbb
           _
 a a a a a b b b
        
        # 4th iteration: Group 1 could still match \1, but not \1b,
        #  (!!!)           so it matched and captured just b
           ___
 a a a a a b b b
          
          # 5th iteration: Group 1 matched \1b and captured bb
          #
          # No more a, + "loop" terminates

A-ha! Alla nostra quarta iterazione, potevamo ancora eguagliare \1, ma non potevamo eguagliare \1b! Dal momento che consentiamo che la corrispondenza autoreferenziale sia facoltativa con\1? , il motore torna indietro e ha scelto l'opzione "no grazie", che ci consente quindi di abbinare e acquisire solob !

Si noti, tuttavia, che, tranne nella prima iterazione, è sempre possibile abbinare solo l'autoreferenzialità \1. Questo è ovvio, ovviamente, poiché è ciò che abbiamo appena catturato nella nostra precedente iterazione e nella nostra configurazione possiamo sempre abbinarlo di nuovo (ad esempio, se abbiamo catturato l' bbbultima volta, siamo garantiti che ci sarà ancora bbb, ma potrebbe o potrebbe non essere bbbbquesta volta).

Lezione : attenzione al backtracking. Il motore di regex eseguirà tutto il backtracking consentito fino a quando il pattern specificato non corrisponde. Ciò potrebbe influire sulle prestazioni (ad es. Backtracking catastrofico ) e / o sulla correttezza.


Passaggio 5: padronanza di sé in soccorso!

La "correzione" dovrebbe ora essere ovvia: combinare la ripetizione opzionale con il quantificatore possessivo . Cioè, invece di semplicemente ?, usa?+ invece (ricorda che una ripetizione che è quantificata come possessiva non torna indietro, anche se tale "cooperazione" può risultare in una corrispondenza del modello generale).

In termini molto informali, questo è ciò che ?+, ?e ??dice:

?+

  • (facoltativo) "Non deve essere lì"
    • (possessivo) "ma se c'è, devi prenderlo e non lasciarlo andare!"

?

  • (facoltativo) "Non deve essere lì"
    • (avido) "ma se lo è puoi prenderlo per ora",
      • (backtracking) "ma ti potrebbe essere chiesto di lasciarlo andare più tardi!"

??

  • (facoltativo) "Non deve essere lì"
    • (riluttante) "e anche se lo è non devi ancora prenderlo,"
      • (backtracking) "ma ti potrebbe essere chiesto di prenderlo più tardi!"

Nel nostro setup, \1non ci sarà la prima volta, ma sarà sempre lì in qualsiasi momento dopo, e allora vogliamo sempre eguagliarlo. Quindi, \1?+realizzerebbe esattamente ciò che vogliamo.

$r5 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ /x';
#                     └──────┘  
#                         1     
#               └───────────────┘ 
#                   lookahead     
#          └───────────────────────┘
#             non-capturing group

Ora l'output è ( come visto su ideone.com ):

aaa 0
aaab 1 a|b          # Yay! Fixed!
aaaxb 0
xaaab 0
b 0
abbb 1 a|b
aabb 1 aa|bb
aaabbbbb 1 aaa|bbb
aaaaabbb 1 aaa|bbb  # Hurrahh!!!

Ecco!!! Problema risolto!!! Ora stiamo contando correttamente, esattamente come vogliamo!

Lezione : impara la differenza tra la ripetizione avida, riluttante e possessiva. Il possessivo facoltativo può essere una potente combinazione.


Passaggio 6: ritocchi finali

Quindi quello che abbiamo in questo momento è uno schema che corrisponde aripetutamente, e per ogni ache è stato trovato, c'è un corrispondente bcatturato nel gruppo 1. Il +termine termina quando non ce ne sono più a, o se l'asserzione fallisce perché non c'è un corrispondente bper una .

Per finire il lavoro, dobbiamo semplicemente aggiungere il nostro modello \1 $. Questo è ora un riferimento all'indietro a ciò che corrisponde al gruppo 1, seguito dalla fine dell'ancora di linea. L'ancora assicura che non ci siano extra bnella stringa; in altre parole, che di fatto abbiamo a n b n .

Ecco il modello finalizzato, con casi di test aggiuntivi, incluso uno lungo 10.000 caratteri:

$tests = array(
  'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb',
  '', 'ab', 'abb', 'aab', 'aaaabb', 'aaabbb', 'bbbaaa', 'ababab', 'abc',
  str_repeat('a', 5000).str_repeat('b', 5000)
);
 
$r6 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ \1 $ /x';
#                     └──────┘  
#                         1     
#               └───────────────┘ 
#                   lookahead     
#          └───────────────────────┘
#             non-capturing group

Essa trova 4 partite: ab, aabb, aaabbb, e il un 5000 b 5000 . Ci vogliono solo 0,06 secondi per funzionare su ideone.com .


Passaggio 7: il test Java

Quindi il pattern funziona in PHP, ma l'obiettivo finale è scrivere un pattern che funzioni in Java.

public static void main(String[] args) {
 
        String aNbN = "(?x) (?:  a  (?= a* (\\1?+ b))  )+ \\1";
        String[] tests = {
                "",      // false
                "ab",    // true
                "abb",   // false
                "aab",   // false
                "aabb",  // true
                "abab",  // false
                "abc",   // false
                repeat('a', 5000) + repeat('b', 4999), // false
                repeat('a', 5000) + repeat('b', 5000), // true
                repeat('a', 5000) + repeat('b', 5001), // false
        };
        for (String test : tests) {
                System.out.printf("[%s]%n  %s%n%n", test, test.matches(aNbN));
        }
 
}
 
static String repeat(char ch, int n) {
        return new String(new char[n]).replace('\0', ch);
}

Il pattern funziona come previsto ( come visto su ideone.com ).


E ora arriviamo alla conclusione ...

Va detto che a*nel lookahead, e in effetti il ​​" +ciclo principale ", entrambi consentono il backtracking. I lettori sono incoraggiati a confermare perché questo non è un problema in termini di correttezza, e perché allo stesso tempo rendere entrambi possessivi funzionerebbe anche (sebbene forse mescolare quantificatori possessivi obbligatori e non obbligatori nello stesso schema può portare a percezioni errate).

Va anche detto che, sebbene sia chiaro che esiste un pattern regex che corrisponderà a n b n , questa non è sempre la soluzione "migliore" nella pratica. Una soluzione molto migliore è semplicemente abbinare ^(a+)(b+)$e quindi confrontare la lunghezza delle stringhe acquisite dai gruppi 1 e 2 nel linguaggio di programmazione host.

In PHP, potrebbe assomigliare a questo ( come visto in ideone.com ):

function is_anbn($s) {
   return (preg_match('/^(a+)(b+)$/', $s, $groups)) &&
      (strlen($groups[1]) == strlen($groups[2]));
}

Lo scopo di questo articolo NON è convincere i lettori che le espressioni regolari possono fare quasi tutto; chiaramente non può, e anche per le cose che può fare, dovrebbe essere considerata almeno una delega parziale alla lingua ospitante se porta a una soluzione più semplice.

Come accennato in alto, mentre questo articolo è necessariamente etichettato [regex]per stackoverflow, forse è più di questo. Sebbene certamente ci sia valore nell'apprendere asserzioni, riferimenti annidati, quantificatori possessivi, ecc., Forse la lezione più grande qui è il processo creativo attraverso il quale si può provare a risolvere i problemi, la determinazione e il duro lavoro che spesso richiedono quando si è sottoposti a vari vincoli, la composizione sistematica da varie parti per costruire una soluzione funzionante, ecc.


Materiale bonus! Modello ricorsivo PCRE!

Dato che abbiamo attivato PHP, va detto che PCRE supporta pattern e subroutine ricorsivi. Quindi, il seguente modello funziona per preg_match( come visto su ideone.com ):

$rRecursive = '/ ^ (a (?1)? b) $ /x';

Attualmente la regex di Java non supporta il pattern ricorsivo.


Ancora più materiale bonus! Corrispondenza a n b n c n !!

Quindi abbiamo visto come abbinare un n b n che non è regolare, ma ancora privo di contesto, ma possiamo anche abbinare a n b n c n , che non è nemmeno libero dal contesto?

La risposta è, ovviamente, SI! I lettori sono incoraggiati a provare a risolverlo da soli, ma la soluzione viene fornita di seguito (con implementazione in Java su ideone.com ).

^ (?: a (?= a* (\1?+ b) b* (\2?+ c) ) )+ \1 \2 $


Senza dubbio potrebbero esserci errori / refusi in questa risposta lunga, quindi per favore lascia feedback come commenti in modo che io possa rivederli da solo.
lubrificanti poligenici

Ottimo lavoro. Mi ci vorrà un po 'per rileggere, ma l'ultima riga è praticamente impossibile da leggere; è un carattere così piccolo. ------ Oh aspetta. È un feature? ... Non sono sicuro che sia una buona idea. So qual è l'ultimo simbolo, ma non può essere letto (a parte copia e incolla).
Peter Ajtai

6
@Peter: evidenzia il piccolo testo e copia e incolla in qualcos'altro. È reso difficile da leggere apposta: è uno spoiler, la soluzione al puzzle bonus.
lubrificanti poligenici

8
+1: Spiegazione fantastica, questi "articoli avanzati" sono idee brillanti.
Callum Rogers

1
@LarsH PHP preg_match()è un esempio di PCRE . Le regex Java sembrano essere basate su una versione precedente delle espressioni regolari Perl . Ciò significa che le regex PHP sono più potenti della versione in Java. A partire dal 21/02/2013 , pcre.txt afferma che corrisponde approssimativamente a Perl 5.12 . Mentre Perl è attualmente a 5.16, con 5.18 pochi mesi di pausa. (In realtà non è stato aggiunto molto alle espressioni regolari in quel periodo)
Brad Gilbert,

20

Dato che non è stata fatta alcuna menzione a PCRE che supporta pattern ricorsivi, vorrei solo segnalare l'esempio più semplice ed efficiente di PCRE che descrive il linguaggio in questione:

/^(a(?1)?b)$/

+1 wow, non sapevo che PCRE supportasse il pattern ricorsivo (sto ancora imparando! Ogni giorno!). Ho rivisto l'articolo per accogliere queste informazioni. a^n b^n c^nTuttavia, non credo che il pattern ricorsivo possa corrispondere .
lubrificanti poligenici

Va notato che questa opzione è più semplice, ma non buona come la risposta pubblicata: la ricorsione trabocca su stringhe lunghe.
Kobi

@Kobi Questo dipende dalla tua definizione di "buono". Ad esempio, la soluzione ricorsiva è di circa un ordine di grandezza più veloce dell'altra ( codepad.viper-7.com/CWgy7c ). Ed è molto più facile da capire. La soluzione ricorsiva è praticamente la trasformazione diretta della grammatica in una regex (in realtà potresti semplicemente scriverla nella forma grammarizzata, funzionerebbe).
NikiC

1
@polygeniclubricants, puoi abbinare quel modello con due modelli ricorsivi, uno per consumare as e bs senza acquisire (e verifica che ci siano la stessa quantità con ricorsione), seguito da un'espressione regolare di cattura che consuma avidamente tutti gli a, quindi applica il ricorsivo Reticolo consumare e verificare che non vi siano lo stesso numero di bs e cs. La regex è: /^(?=(a(?-1)?b)c)a+(b(?-1)?c)$/x. Credito a: nikic.github.io/2012/06/15/…
Josh Reback

11

Come accennato nella domanda, con il gruppo di bilanciamento .NET, i modelli di tipo a n b n c n d n … z n possono essere abbinati facilmente come

^
  (?<A>a)+
  (?<B-A>b)+  (?(A)(?!))
  (?<C-B>c)+  (?(B)(?!))
  ...
  (?<Z-Y>z)+  (?(Y)(?!))
$

Ad esempio: http://www.ideone.com/usuOE


Modificare:

Esiste anche un pattern PCRE per il linguaggio generalizzato con pattern ricorsivo, ma è necessario un lookahead. Non credo che questa sia una traduzione diretta di quanto sopra.

^
  (?=(a(?-1)?b))  a+
  (?=(b(?-1)?c))  b+
  ...
  (?=(x(?-1)?y))  x+
     (y(?-1)?z)
$

Ad esempio: http://www.ideone.com/9gUwF


1
@poly: grazie :). In realtà non ho familiarità con i pattern .NET, ma per questo tipo di pattern risulta essere molto facile con i gruppi di bilanciamento, quindi integro questa risposta.
kennytm

puoi farlo con pattern ricorsivo? Perché se non puoi, è una svolta interessante che il gruppo di bilanciamento può fare cose che il modello ricorsivo non può fare. (E sì, apprezzo molto il supplemento).
lubrificanti poligenici

a proposito, il motivo per cui ho omesso la soluzione .NET era perché ho dei piani per "Come possiamo abbinare le a^n b^nespressioni regolari .NET?" articolo in futuro, ma sei più che benvenuto a scriverlo se vuoi. Non sto scrivendo questi articoli solo per me stesso; Voglio incoraggiare gli altri a farlo anche per avere buoni contenuti sul sito.
poligenelubrificanti

Aggiorna se trovi un modo per farlo con schemi ricorsivi. Ho giocato con i gruppi di bilanciamento per catturare parole la cui lunghezza fa una serie di Fibonacci, e non riuscivo a farlo funzionare. Potrebbe essere possibile utilizzare il look around, simile a quello che ho fatto.
Kobi

1
Vorrei solo sottolineare che la versione PCRE di questo modello è leggermente imperfetta in quanto corrisponde se il successivo blocco di caratteri è più lungo del precedente. Vedi qui: regex101.com/r/sdlRTm/1 Devi aggiungere (?!b), (?!c)ecc. Dopo i gruppi di cattura in questo modo: regex101.com/r/sdlRTm/2
jaytea
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.