วิธีแปลงออโตมาต้าให้เป็นนิพจน์ปกติ


115

แปลงนิพจน์ปกติเป็น (น้อย) NFA ที่ยอมรับภาษาเดียวกันเป็นเรื่องง่ายด้วยขั้นตอนวิธีการมาตรฐานเช่นอัลกอริทึม ธ อมป์สัน ในอีกทางหนึ่งดูเหมือนว่าจะน่าเบื่อกว่าและบางครั้งการแสดงออกที่เกิดขึ้นนั้นยุ่งเหยิง

มีอัลกอริธึมอะไรบ้างสำหรับการแปลง NFA ให้เป็นนิพจน์ทั่วไปที่เทียบเท่ากัน มีข้อได้เปรียบเกี่ยวกับความซับซ้อนของเวลาหรือขนาดผลลัพธ์หรือไม่

นี่ควรจะเป็นคำถามอ้างอิง โปรดรวมการลดทอนของวิธีการทั่วไปของคุณรวมถึงตัวอย่างที่ไม่สำคัญ


2
สังเกตคำถามที่คล้ายกันที่ cstheory.SEซึ่งอาจไม่เหมาะสำหรับผู้ชมของเรา
ราฟาเอล

คำตอบทั้งหมดใช้เทคนิคที่เป็นทางการในการเขียน RE จาก DFA ฉันเชื่อว่าเทคนิคของฉันโดยการวิเคราะห์นั้นค่อนข้างง่ายและมีวัตถุประสงค์ฉันแสดงให้เห็นในคำตอบของฉัน: ภาษาของออโต้ จำกัด อัน จำกัด นี้คืออะไร? ฉันรู้สึกว่ามันจะเป็นประโยชน์ในบางครั้ง ใช่แน่นอนบางครั้งฉันเองใช้วิธีการอย่างเป็นทางการ (Arden theorem) เพื่อเขียน RE เป็นคำถามที่ซับซ้อนเหมือนที่กำหนดไว้ในตัวอย่างนี้: วิธีการเขียนนิพจน์ปกติสำหรับ DFA
Grijesh Chauhan

คำตอบ:


94

มีหลายวิธีในการทำการแปลงจากออโตมาตา จำกัด เป็นนิพจน์ปกติ ที่นี่ฉันจะอธิบายหนึ่งที่มักจะสอนในโรงเรียนซึ่งเป็นภาพมาก ฉันเชื่อว่ามันถูกใช้มากที่สุดในการปฏิบัติ อย่างไรก็ตามการเขียนอัลกอริทึมไม่ใช่ความคิดที่ดี

วิธีการกำจัดสถานะ

อัลกอริทึมนี้เกี่ยวกับการจัดการกราฟของหุ่นยนต์และดังนั้นจึงไม่เหมาะสำหรับอัลกอริทึมเนื่องจากมันต้องการกราฟแบบดั้งเดิมเช่น ... การลบรัฐ ฉันจะอธิบายมันโดยใช้พื้นฐานระดับสูง

ความคิดหลัก

แนวคิดคือการพิจารณานิพจน์ทั่วไปบนขอบจากนั้นลบสถานะสื่อกลางในขณะที่ทำให้ป้ายชื่อขอบคงที่

รูปแบบหลักสามารถเห็นได้ในรูปต่อไปนี้ เป็นครั้งแรกที่มีป้ายระหว่างที่มีการแสดงออกปกติและเราต้องการที่จะเอาคิวe , f , g , h , i qp,q,re,f,g,h,iq

pqr หุ่นยนต์

เมื่อนำออกแล้วเราจะเขียนด้วยกัน (ในขณะที่รักษาขอบอื่น ๆ ระหว่างและแต่สิ่งนี้ไม่ปรากฏบนนี้):p re,f,g,h,ipr

ป้อนคำอธิบายรูปภาพที่นี่

ตัวอย่าง

ใช้ตัวอย่างเดียวกันกับคำตอบของราฟาเอล :

1-2-3 หุ่นยนต์

เราลบอย่างต่อเนื่อง:q2

1-3 หุ่นยนต์

แล้ว :q3

1 หุ่นยนต์

แล้วเรายังคงต้องใช้ดาวบนแสดงออกจากเพื่อq_1ในกรณีนี้สถานะสุดท้ายก็เริ่มต้นด้วยดังนั้นเราแค่ต้องเพิ่มดาว:q 1q1q1

(ab+(b+aa)(ba)(a+bb))

ขั้นตอนวิธี

L[i,j]เป็น regexp ของภาษาจากที่เพื่อq_jก่อนอื่นเราจะลบหลายขอบทั้งหมด:q jqiqj

for i = 1 to n:
  for j = 1 to n:
    if i == j then:
      L[i,j] := ε
    else:
      L[i,j] := ∅
    for a in Σ:
      if trans(i, a, j):
        L[i,j] := L[i,j] + a

ตอนนี้การกำจัดของรัฐ สมมติว่าเราต้องการลบสถานะ :qk

remove(k):
  for i = 1 to n:
    for j = 1 to n:
      L[i,i] += L[i,k] . star(L[k,k]) . L[k,i]
      L[j,j] += L[j,k] . star(L[k,k]) . L[k,j]
      L[i,j] += L[i,k] . star(L[k,k]) . L[k,j]
      L[j,i] += L[j,k] . star(L[k,k]) . L[k,i]

โปรดทราบว่าทั้งสองด้วยดินสอกระดาษและด้วยวิธีที่คุณควรลดความซับซ้อนของการแสดงออกเช่นstar(ε)=ε, e.ε=e, ∅+e=e, ∅.e=∅(ด้วยมือคุณเพียงแค่ไม่ได้เขียนขอบเมื่อมันไม่หรือแม้กระทั่งสำหรับห่วงตนเองและคุณไม่สนใจเมื่อมี ไม่มีการเปลี่ยนแปลงระหว่างและหรือและ )εεq k q j q kqiqkqjqk

ตอนนี้วิธีการใช้งานremove(k)? คุณไม่ควรลบสถานะสุดท้ายหรือเริ่มต้นเบา ๆ มิฉะนั้นคุณจะพลาดบางส่วนของภาษา

for i = 1 to n:
  if not(final(i)) and not(initial(i)):
    remove(i)

หากคุณมีสถานะสุดท้ายเพียงรัฐและสถานะเริ่มต้นหนึ่งสถานะดังนั้นนิพจน์สุดท้ายคือ:q sqfqs

e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])

หากคุณมีสถานะขั้นสุดท้ายหลายสถานะ (หรือแม้กระทั่งสถานะเริ่มต้น) ดังนั้นจึงไม่มีวิธีที่ง่ายในการผสานสถานะเหล่านี้นอกเหนือจากการใช้วิธีการปิด transitive โดยทั่วไปแล้วนี่ไม่ใช่ปัญหาด้วยมือ แต่มันก็น่าอึดอัดใจเมื่อเขียนอัลกอริทึม วิธีแก้ปัญหาที่ง่ายกว่ามากคือการแจกแจงคู่ทั้งหมดและเรียกใช้อัลกอริทึมบนกราฟ (แล้วลบรัฐ) เพื่อรับนิพจน์ทั้งหมดสมมติว่าเป็นสถานะเริ่มต้นเท่านั้นและเป็นขั้นสุดท้ายเท่านั้น ของรัฐแล้วทำสหภาพทุกF}e s , f s f e s , f(s,f)es,fsfes,f

สิ่งนี้และความจริงที่ว่านี่เป็นการแก้ไขภาษาแบบไดนามิกมากกว่าวิธีแรกทำให้เกิดข้อผิดพลาดได้ง่ายขึ้นเมื่อตั้งโปรแกรม ฉันแนะนำให้ใช้วิธีอื่น

จุดด้อย

มีหลายกรณีในอัลกอริทึมนี้ตัวอย่างเช่นสำหรับการเลือกโหนดที่เราควรลบจำนวนสุดท้ายของสถานะในตอนท้ายข้อเท็จจริงที่ว่าสถานะสุดท้ายสามารถเริ่มต้นได้เช่นกัน

โปรดทราบว่าขณะนี้อัลกอริทึมถูกเขียนขึ้นแล้วนี่เป็นวิธีการปิด transitive บริบทการใช้งานจะแตกต่างกันเท่านั้น ฉันไม่แนะนำให้ใช้อัลกอริทึม แต่การใช้วิธีทำด้วยมือเป็นความคิดที่ดี


1
ในตัวอย่างรูปภาพที่ 2 หลังจากลบโหนด "2" มีขอบหายไป - วนขอบ (ab) ในโหนด A
Panos Kal

@Kabamaru: แก้ไขแล้ว แต่ตอนนี้ฉันคิดว่าในภาพที่ 3 ควรเป็นเช่นนั้นและอาจคล้ายกันในการแสดงออกปกติสุดท้าย εab
Wandering Logic

คุณสามารถทำให้อัลกอริทึมทำงานสำหรับสถานะเริ่มต้นและขั้นสุดท้ายจำนวนเท่าใดก็ได้โดยการเพิ่มค่าเริ่มต้นใหม่และสถานะสุดท้ายใหม่และเชื่อมต่อสิ่งเหล่านี้กับสถานะเริ่มต้นและขั้นสุดท้ายดั้งเดิมโดย -edges ตอนนี้เอาทุกรัฐเดิม การแสดงออกก็จะพบที่ขอบที่เหลือเดียวจากเพื่อq_-การก่อสร้างจะไม่ให้ลูปที่หรือเนื่องจากสถานะเหล่านี้ไม่มีการตอบสนองภายใน ขอบขาออก หรือถ้าคุณเข้มงวดพวกเขาจะมีป้ายกำกับที่แสดงถึงเซตที่ว่างเปล่า q - ε q + q - q + q -q+qεq+qq+q
Hendrik Jan

1
ยังคงมีปัญหากับตัวอย่างที่สอง: ก่อนที่จะทำให้เข้าใจง่ายออโตมาตายอมรับ "ba", (1, 3, 1) แต่หลังจากการทำให้เข้าใจง่ายมันก็ไม่ได้
wvxvw

50

วิธี

วิธีที่ดีที่สุดที่ฉันเคยเห็นก็คือวิธีหนึ่งที่แสดงออกถึงระบบสมการของภาษา (ปกติ) ซึ่งสามารถแก้ไขได้ โดยเฉพาะอย่างยิ่งเป็นเรื่องที่ดีเนื่องจากดูเหมือนว่าจะให้การแสดงออกที่กระชับมากกว่าวิธีอื่น ๆ

ให้ NFA โดยไม่มี -transitions สำหรับทุก ๆ รัฐให้สร้างสมการε q iA=(Q,Σ,δ,q0,F)εqi

Qi=qiaqjaQj{{ε}, qiF, else

ที่คือชุดของรัฐสุดท้ายและหมายความว่ามีการเปลี่ยนแปลงจากเพื่อที่มีป้ายกำกับ หากคุณอ่านเป็นหรือ (ขึ้นอยู่กับนิยามนิพจน์ปกติของคุณ) คุณจะเห็นว่านี่เป็นสมการของนิพจน์ทั่วไปq i a q j q ฉันq j a + Fqiaqjqiqja+

สำหรับการแก้ปัญหาระบบคุณจำเป็นต้องมีการเชื่อมโยงและการกระจายของและ (การเรียงสตริง), การสับเปลี่ยนของและLemma ของ Arden :

LetภาษาปกติกับU จากนั้นL,U,VΣεU

L=ULVL=UV

การแก้ปัญหาคือชุดของการแสดงออกปกติหนึ่งสำหรับทุกรัฐq_iอธิบายว่าคำพูดเหล่านั้นที่สามารถรับการยอมรับจากเมื่อเริ่มต้นใน ; ดังนั้น (ถ้าเป็นสถานะเริ่มต้น) คือการแสดงออกที่ต้องการQiqiQiAqiQ0q0


ตัวอย่าง

เพราะความชัดเจนที่เรา denotate ชุดเดี่ยวโดยองค์ประกอบของพวกเขาคือ\} ตัวอย่างนี้เกิดจาก Georg Zetzschea={a}

พิจารณา NFA นี้:

ตัวอย่าง nfa
[ แหล่งที่มา ]

ระบบสมการที่สอดคล้องกันคือ:

Q0=aQ1bQ2εQ1=bQ0aQ2Q2=aQ0bQ1

ตอนนี้เสียบสมการที่สามลงในที่สอง:

Q1=bQ0a(aQ0bQ1)=abQ1(baa)Q0=(ab)(baa)Q0

สำหรับขั้นตอนที่ผ่านมาเราใช้อาร์เดนของบทแทรกด้วย ,และQ_0 โปรดทราบว่าทั้งสามภาษาเป็นภาษาปกติและทำให้เราสามารถใช้บทแทรกได้ ตอนนี้เราเสียบผลลัพธ์นี้เข้ากับสมการแรก:L=Q1U=abV=(baa)Q0εU={ab}

Q0=a(ab)(baa)Q0baQ0bb(ab)(baa)Q0ε=((abb)(ab)(baa)ba)Q0ε=((abb)(ab)(baa)ba)(by Arden's Lemma)

ดังนั้นเราจึงพบการแสดงออกปกติสำหรับภาษาที่ยอมรับโดยหุ่นยนต์ด้านบนคือ

((a+bb)(ab)(b+aa)+ba).

โปรดทราบว่ามันค่อนข้างสั้น (เปรียบเทียบกับผลของวิธีการอื่น ๆ ) แต่ไม่ได้มีการพิจารณาเฉพาะ การแก้ระบบสมการด้วยการเรียงลำดับที่แตกต่างกันนำไปสู่การเทียบเท่าอื่น ๆ ! - การแสดงออก


  1. สำหรับหลักฐานการ Arden ของบทแทรกให้ดูที่นี่

1
ความซับซ้อนของเวลาของอัลกอริทึมนี้คืออะไร? มีการ จำกัด ขนาดของนิพจน์ที่สร้างขึ้นหรือไม่?
jmite

@jmite: ฉันไม่มีความคิด ฉันไม่คิดว่าฉันจะพยายามใช้สิ่งนี้ (วิธีการอื่น ๆ ดูเหมือนจะเป็นไปได้มากขึ้นในเรื่องนี้) แต่ใช้มันเป็นวิธีปากกาและกระดาษ
Raphael

1
นี่คือการใช้ Prolog ของอัลกอริทึมนี้: github.com/wvxvw/intro-to-automata-theory/blob/master/automata/…แต่เพรดิเคตmaybe_union/2สามารถใช้งานได้มากขึ้น (โดยเฉพาะการกำจัดคำนำหน้าทั่วไป) อีกวิธีหนึ่งในการดูวิธีนี้คือการเข้าใจว่าเป็นการแปลจาก regex เป็นไวยากรณ์เชิงเส้นขวาซึ่งภาษาที่มีการรวมกันของ Prolog-like หรือการจับคู่รูปแบบคล้าย ML ทำให้เป็น transducers ที่ดีมากดังนั้นจึงไม่ใช่แค่ปากกาและกระดาษ อัลกอริทึม :)
wvxvw

แค่คำถามเดียว εในสมการแรกเป็นเพราะ Qo เป็นสถานะเริ่มต้นหรือเพราะเป็นสถานะสุดท้าย? วิธีเดียวกันถ้าฉันมีสองสถานะสุดท้ายใช้?
Georgio3

@PAOK ตรวจสอบคำจำกัดความของด้านบน (บรรทัด); มันเป็นเพราะเป็นสถานะสุดท้าย q 0Qiq0
กราฟิลส์

28

วิธีพีชคณิต Brzozowski

นี่เป็นวิธีเดียวกับที่อธิบายไว้ในคำตอบของราฟาเอลแต่จากมุมมองของอัลกอริธึมที่เป็นระบบและจากนั้นแน่นอนอัลกอริทึม มันกลายเป็นเรื่องง่ายและเป็นธรรมชาติที่จะดำเนินการเมื่อคุณรู้ว่าจะเริ่มต้นที่ไหน นอกจากนี้มันอาจจะง่ายขึ้นด้วยมือถ้าการวาดออโตมาตาทั้งหมดนั้นไม่สามารถทำได้ด้วยเหตุผลบางประการ

เมื่อเขียนอัลกอริธึมคุณต้องจำไว้ว่าสมการนั้นต้องเป็นเชิงเส้นเสมอเพื่อให้คุณได้ภาพที่เป็นนามธรรมของสมการสิ่งที่คุณสามารถลืมได้เมื่อคุณแก้ไขด้วยมือ

แนวคิดของอัลกอริทึม

ฉันจะไม่อธิบายวิธีการทำงานเนื่องจากทำได้ดีในคำตอบของ Raphaelซึ่งฉันแนะนำให้อ่านก่อน แต่ฉันมุ่งเน้นไปที่ลำดับที่คุณควรแก้สมการโดยไม่ต้องทำการคำนวณเพิ่มเติมหรือกรณีพิเศษมากเกินไป

เริ่มต้นจากวิธีแก้ปัญหาที่แยบยลของกฎของ Ardenไปยังสมการภาษาเราสามารถพิจารณาออโตมาตาเป็นชุดสมการของรูปแบบ:X = A X BX=ABX=AXB

Xi=Bi+Ai,1X1++Ai,nXn

เราสามารถแก้ปัญหานี้ได้โดยการเหนี่ยวนำที่โดยการอัปเดตอาร์เรย์และตามลำดับ ในขั้นตอนที่เรามี:A i , j B i , j nnAi,jBi,jn

Xn=Bn+An,1X1++An,nXn

และกฎของอาร์เดนให้เรา:

Xn=An,n(Bn+An,1X1++An,n1Xn1)

และโดยการตั้งค่าและเราได้รับ:Bn=An,nBnAn,i=An,nAn,i

Xn=Bn+An,1X1++An,n1Xn1

และเราสามารถลบความต้องการทั้งหมดของในระบบโดยการตั้งค่าสำหรับ :Xni,j<n

A i , j = A i , j + A i , n A n , j

Bi=Bi+Ai,nBn
Ai,j=Ai,j+Ai,nAn,j

เมื่อเราแก้ไขเมื่อเราจะได้สมการดังนี้: n = 1Xnn=1

X1=B1

ไม่มีi} ดังนั้นเราจึงได้รับการแสดงออกปกติของเราA1,i

อัลกอริทึม

ด้วยสิ่งนี้เราสามารถสร้างอัลกอริทึม ที่จะมีการประชุมเดียวกันกว่าในการเหนี่ยวนำข้างต้นเราจะบอกว่าสถานะเริ่มต้นคือและว่าจำนวนของรัฐที่เป็นเมตรก่อนการเริ่มต้นเพื่อเติม : m Bq1mB

for i = 1 to m:
  if final(i):
    B[i] := ε
  else:
    B[i] := ∅

และ :A

for i = 1 to m:
  for j = 1 to m:
    for a in Σ:
      if trans(i, a, j):
        A[i,j] := a
      else:
        A[i,j] := ∅

แล้วการแก้ปัญหา:

for n = m decreasing to 1:
  B[n] := star(A[n,n]) . B[n]
  for j = 1 to n:
    A[n,j] := star(A[n,n]) . A[n,j];
  for i = 1 to n:
    B[i] += A[i,n] . B[n]
    for j = 1 to n:
      A[i,j] += A[i,n] . A[n,j]

การแสดงออกสุดท้ายคือ:

e := B[1]

การดำเนินงาน

แม้ว่ามันอาจดูเหมือนว่าระบบของสมการที่ดูเหมือนจะเป็นสัญลักษณ์ของอัลกอริทึม แต่มันก็เหมาะสำหรับการนำไปใช้ นี่คือการดำเนินการตามขั้นตอนวิธีนี้ใน Ocaml (ลิงค์เสีย) โปรดทราบว่านอกเหนือจากฟังก์ชั่นbrzozowskiทุกอย่างคือการพิมพ์หรือใช้สำหรับตัวอย่างของราฟาเอล โปรดทราบว่ามีฟังก์ชันที่มีประสิทธิภาพอย่างน่าประหลาดใจในการทำให้การแสดงผลปกติง่ายsimple_reขึ้น


4
การเชื่อมโยงจะตาย ...
Columbo

การใช้งานใน Javascript: github.com/devongovett/regexgen/blob/master/src/regex.js
cakraww

24

วิธีการปิดสกรรมกริยา

วิธีนี้ง่ายต่อการเขียนในรูปแบบของอัลกอริทึม แต่สร้างนิพจน์ปกติที่มีขนาดใหญ่อย่างไร้เหตุผลและไม่สามารถทำได้ถ้าคุณทำด้วยมือส่วนใหญ่เป็นเพราะมันเป็นระบบเกินไป มันเป็นวิธีแก้ปัญหาที่ดีและง่ายสำหรับอัลกอริทึม

ความคิดหลัก

Letแทนการแสดงออกปกติสำหรับสตริงไปจากเพื่อใช้รัฐ\} ให้เป็นจำนวนสถานะของหุ่นยนต์ q ฉันq jRi,jkqiqjn{q1,,qk}n

สมมติว่าคุณรู้อยู่แล้วว่าการแสดงออกปกติจากเพื่อโดยไม่ต้องรัฐกลาง (ยกเว้นขา) สำหรับทุก j จากนั้นคุณสามารถเดาได้ว่าการเพิ่มสถานะอื่นจะส่งผลต่อนิพจน์ทั่วไปใหม่ : มันเปลี่ยนแปลงเฉพาะในกรณีที่คุณมีการเปลี่ยนโดยตรงไปยังและสามารถแสดงออกได้ดังนี้: q i q j q k i , j R i , j q kRi,jqiqjqki,jRi,jqk

Ri,j=Ri,j+Ri,k.Rk,k.Rk,j

(คือและคือ .)R k - 1 R R kRRk1RRk

ตัวอย่าง

เราจะใช้เช่นเดียวกับในคำตอบของราฟาเอล ในตอนแรกคุณสามารถใช้การเปลี่ยนแปลงโดยตรงเท่านั้น

นี่คือขั้นตอนแรก (โปรดทราบว่าห่วงตัวเองที่มีป้ายจะได้เปลี่ยนคนแรกเข้าA)aε(ε+a)

R0=[εabbεaabε]

ในขั้นตอนที่สองเราสามารถใช้ (ซึ่งถูกเปลี่ยนชื่อเป็นสำหรับเราเพราะถูกใช้เพื่อวัตถุประสงค์ด้านบนแล้ว) เราจะดูว่าทำงานอย่างไรq0q1R0R1

จากถึง :BAq2q2R2,21=R2,20+R2,10R1,10R1,20=ε+bεa=ε+ba

ทำไมถึงเป็นอย่างนั้น? เป็นเพราะการไปจากถึงโดยใช้เพียงเนื่องจากสถานะระดับกลางสามารถทำได้โดยการอยู่ที่นี่ ( ) หรือไปที่ ( ) วนไปที่นั่น ( ) และกลับมา ( )q2q2q1εq1aεb

R1=[εabbε+baa+bbab+aaε+ab]

คุณสามารถคำนวณเช่นนั้นและได้เช่นกันและจะให้นิพจน์สุดท้ายแก่คุณเพราะเป็นทั้งค่าเริ่มต้นและครั้งสุดท้าย โปรดทราบว่ามีการทำนิพจน์ที่เรียบง่ายจำนวนมากที่นี่แล้ว มิฉะนั้นคนแรกของจะเป็นและเป็นครั้งแรกของจะเป็นก)R2R3R1,131aR0(+a)aR1((+a)+ε(ε)a)

ขั้นตอนวิธี

การเริ่มต้น:

for i = 1 to n:
  for j = 1 to n:
    if i == j:
      R[i,j,0] := ε
    else:
      R[i,j,0] := ∅
    for a in Σ:
      if trans(i, a, j):
        R[i,j,0] := R[i,j,0] + a

การปิดสกรรมกริยา:

for k = 1 to n:
  for i = 1 to n:
    for j = 1 to n:
      R[i,j,k] := R[i,j,k-1] + R[i,k,k-1] . star(R[k,k,k-1]) . R(k,j,k-1)

จากนั้นนิพจน์สุดท้ายคือ (สมมุติว่าเป็นสถานะเริ่มต้น):qs

e := ∅
for i = 1 to n:
  if final(i):
    e := e + R[s,i,n]

แต่คุณสามารถจินตนาการว่ามันสร้างการแสดงออกปกติน่าเกลียด แน่นอนคุณสามารถคาดหวังสิ่งที่ชอบที่แสดงถึงภาษาเดียวกับAAโปรดทราบว่าการทำให้นิพจน์ทั่วไปให้เป็นประโยชน์ในทางปฏิบัติง่ายขึ้น()+(a+())(ε)(a+)aa

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.