Contoh rekursi dunia nyata [ditutup]


98

Apa masalah dunia nyata di mana pendekatan rekursif adalah solusi alami selain pencarian mendalam-pertama (DFS)?

(Saya tidak menganggap Menara Hanoi , bilangan Fibonacci , atau masalah dunia nyata faktorial. Mereka sedikit dibuat-buat dalam pikiran saya.)


2
Terima kasih atas semua saran tetapi semua orang menyarankan traversal pohon / jaringan. Tesis ini pada dasarnya adalah semua contoh Depth-First-Search (atau BFS menurut saya). Saya sedang mencari algoritma / masalah lain yang termotivasi dengan baik.
redfood

10
Saya suka pertanyaan ini! "Ceritakan semua penggunaan teknik X, KECUALI penggunaan praktis utama teknik X"
Standar Justin

1
Saya menggunakan rekursi sepanjang waktu tetapi biasanya untuk hal-hal matematika dan grafi. Saya mencoba mencari contoh rekursi yang akan berarti bagi non-programmer.
redfood

6
Pilih novel petualangan Anda sendiri! Saya ingin membaca semuanya, dan rekursi adalah cara terbaik untuk melakukannya.
Andres

Tidak ada rekursi di dunia nyata. Rekursi adalah abstraksi matematika. Anda dapat memodelkan banyak hal menggunakan rekursi. Dalam hal ini, Fibonacci benar-benar dunia nyata, karena ada beberapa masalah dunia nyata yang dapat dimodelkan dengan cara ini. Jika Anda berpikir bahwa Fibonacci bukanlah dunia nyata, maka saya akan mengklaim bahwa semua contoh lainnya adalah abstraksi juga, bukan contoh dunia nyata.
Zane

Jawaban:


43

Ada banyak contoh matematika di sini, tetapi Anda menginginkan contoh dunia nyata , jadi dengan sedikit pemikiran, ini mungkin yang terbaik yang bisa saya tawarkan:

Anda menemukan seseorang yang telah tertular infeksi menular tertentu, yang tidak fatal, dan sembuh sendiri dengan cepat (Tipe A), Kecuali satu dari 5 orang (Kami akan menyebutnya tipe B) yang terinfeksi secara permanen dan tidak menunjukkan gejala dan hanya bertindak sebagai penyebar.

Ini menciptakan gelombang malapetaka yang cukup menjengkelkan ketika tipe B menginfeksi banyak tipe A.

Tugas Anda adalah melacak semua tipe B dan mengimunisasi mereka untuk menghentikan tulang punggung penyakit. Sayangnya, Anda tidak dapat memberikan pengobatan nasional untuk semua, karena orang-orang yang bertipe A juga alergi mematikan terhadap obat yang bekerja untuk tipe B.

Cara Anda melakukan ini, akan menjadi penemuan sosial, mengingat orang yang terinfeksi (Tipe A), memilih semua kontak mereka dalam seminggu terakhir, menandai setiap kontak di tumpukan. Saat Anda menguji seseorang terinfeksi, tambahkan mereka ke antrian "tindak lanjut". Ketika seseorang adalah tipe B, tambahkan mereka ke "tindak lanjut" di kepala (karena Anda ingin menghentikan ini dengan cepat).

Setelah memproses orang tertentu, pilih orang tersebut dari depan antrian dan terapkan imunisasi jika diperlukan. Dapatkan semua kontak mereka yang sebelumnya tidak dikunjungi, lalu uji untuk melihat apakah mereka terinfeksi.

Ulangi hingga antrian orang yang terinfeksi menjadi 0, lalu tunggu wabah lainnya ..

(Oke, ini agak berulang, tetapi ini adalah cara berulang untuk memecahkan masalah rekursif, dalam hal ini, lintasan pertama yang luas dari basis populasi mencoba menemukan kemungkinan jalan menuju masalah, dan selain itu, solusi berulang seringkali lebih cepat dan lebih efektif , dan saya secara kompulsif menghapus rekursi di mana-mana sehingga menjadi naluriah .... Sialan!)


2
Terima kasih - ini masih grafik traversal tetapi termotivasi dengan baik dan masuk akal bagi orang-orang non-programmer.
redfood

Saya percaya menemukan pasien 0 akan menjadi contoh yang lebih baik. Tentukan semua interaksi yang dapat menyebabkan infeksi. Ulangi pada semua yang terlibat yang tertular pada saat interaksi sampai tidak ada penularan yang ditemukan
William FitzPatrick

7
contoh dunia nyata ini terasa begitu akrab sekarang :(
haroldolivieri

110

Contoh rekursi dunia nyata

Bunga matahari


13
dikodekan dengan rekursi oleh arsitek Matrix :)
Marcel

3
Bagaimana rekursif ini? Tentu, itu cantik. Tapi rekursif? Kubis fraktal akan bekerja dengan baik, tetapi saya tidak melihat kemiripan diri pada bunga ini.
Clément

1
Memang agak mengganggu, tetapi ini adalah contoh dari phyllotaxis, yang dapat dijelaskan dengan deret Fibonacci, yang biasanya diterapkan melalui rekursi.
Hans Sjunnesson pada

1
"umumnya diterapkan melalui rekursi" tidak selalu berarti bunga melakukannya. Mungkin memang demikian; Saya tidak cukup ahli biologi molekuler untuk mengetahui, tetapi tanpa penjelasan tentang bagaimana hal itu, saya tidak melihat ini sebagai sangat membantu. Merendahkan. Jika Anda ingin menambahkan deskripsi (apakah itu akurat secara biologis atau tidak, mungkin memberikan wawasan) untuk menyertainya, saya akan dengan senang hati mempertimbangkan kembali pemungutan suara tersebut.
Lindes

65

Bagaimana dengan apa pun yang melibatkan struktur direktori dalam sistem file. Menemukan file secara berulang, menghapus file, membuat direktori, dll.

Berikut adalah implementasi Java yang secara rekursif mencetak konten direktori dan subdirektorinya.

import java.io.File;

public class DirectoryContentAnalyserOne implements DirectoryContentAnalyser {

    private static StringBuilder indentation = new StringBuilder();

    public static void main (String args [] ){
        // Here you pass the path to the directory to be scanned
        getDirectoryContent("C:\\DirOne\\DirTwo\\AndSoOn");
    }

    private static void getDirectoryContent(String filePath) {

        File currentDirOrFile = new File(filePath);

        if ( !currentDirOrFile.exists() ){
            return;
        }
        else if ( currentDirOrFile.isFile() ){
            System.out.println(indentation + currentDirOrFile.getName());
            return;
        }
        else{
            System.out.println("\n" + indentation + "|_" +currentDirOrFile.getName());
            indentation.append("   ");

            for ( String currentFileOrDirName : currentDirOrFile.list()){
                getPrivateDirectoryContent(currentDirOrFile + "\\" + currentFileOrDirName);
            }

            if (indentation.length() - 3 > 3 ){
                indentation.delete(indentation.length() - 3, indentation.length());
            }
        }       
    }

}

2
Sistem file memberikan motivasi (yang bagus, terima kasih) tetapi ini adalah contoh spesifik DFS.
redfood

4
Saya tidak mendapatkan akronim "DFS" - sudah lama sejak saya duduk di ruang kelas.
Matt Dillard

5
pencarian kedalaman-pertama: dfs (node) {untuk setiap anak dalam node {kunjungan (anak); }}
Paling baik

Untuk contoh kode sederhana, lihat misalnya stackoverflow.com/questions/126756/…
Jonik

Apakah ada kesalahan dalam kode ini? Haruskah getPrivateDirectoryContent () diganti dengan getDirectoryContent ()?
Shn_Android_Dev


16

Teladan Matt Dillard bagus. Secara lebih umum, setiap berjalan di pohon umumnya dapat ditangani dengan sangat mudah dengan rekursi. Misalnya, mengompilasi pohon parse, berjalan di atas XML atau HTML, dll.


Saya menemukan ini "menyusun pohon parse" jawaban yang masuk akal, yang melibatkan pohon tetapi masih bukan masalah pencarian, seperti yang diinginkan oleh penanya. Ini bisa digeneralisasikan untuk beberapa pengertian umum tentang kompilasi atau interpretasi bahasa. Bisa juga "menafsirkan" (memahami, mendengarkan) bahasa alami, misalnya, bahasa Inggris.
imz - Ivan Zakharyaschev


13

Rekursi dapat dilakukan jika suatu masalah dapat diselesaikan dengan membaginya menjadi sub-masalah, yang dapat menggunakan algoritme yang sama untuk menyelesaikannya. Algoritme pada pohon dan daftar yang diurutkan sangat cocok. Banyak masalah dalam geometri komputasi (dan permainan 3D) dapat diselesaikan secara rekursif dengan menggunakan pohon partisi ruang biner (BSP), subdivisi lemak , atau cara lain untuk membagi dunia menjadi sub-bagian.

Rekursi juga sesuai saat Anda mencoba menjamin kebenaran algoritme. Dengan adanya fungsi yang mengambil input yang tidak dapat diubah dan mengembalikan hasil yang merupakan kombinasi panggilan rekursif dan non-rekursif pada input, biasanya mudah untuk membuktikan bahwa fungsi tersebut benar (atau tidak) menggunakan induksi matematika. Seringkali sulit untuk melakukan ini dengan fungsi iteratif atau dengan input yang mungkin bermutasi. Ini dapat berguna saat berhadapan dengan kalkulasi keuangan dan aplikasi lain yang sangat mengutamakan kebenaran.


11

Tentunya banyak kompiler di luar sana yang banyak menggunakan rekursi. Bahasa komputer secara inheren rekursif sendiri (yaitu, Anda dapat menyematkan pernyataan 'jika' di dalam pernyataan 'jika' lainnya, dll.).


Disematkan jika pernyataan bukan rekursi.
John Meagher

Tapi menguraikannya membutuhkan rekursi, John.
Apocalisp

2
John, fakta bahwa Anda dapat bersarang jika pernyataan berarti definisi bahasa (dan kemungkinan pengurai bahasa) adalah rekursif.
Derek Park

Turunan rekursif adalah salah satu cara termudah untuk membuat kode tangan kompiler. Tidak semudah menggunakan alat seperti yacc, tetapi lebih mudah untuk memahami cara kerjanya. Seluruh mesin status yang digerakkan oleh tabel dapat dijelaskan, tetapi biasanya berakhir menjadi kotak hitam.
Gerhana

Jawaban Cody Brocious yang menyebutkan "menyusun pohon parse" juga menunjuk pada bidang ini: analisis / interpretasi / kompilasi bahasa.
imz - Ivan Zakharyaschev

9

Menonaktifkan / menyetel hanya-baca untuk semua kontrol anak dalam kontrol penampung. Saya perlu melakukan ini karena beberapa kontrol anak adalah wadah itu sendiri.

public static void SetReadOnly(Control ctrl, bool readOnly)
{
    //set the control read only
    SetControlReadOnly(ctrl, readOnly);

    if (ctrl.Controls != null && ctrl.Controls.Count > 0)
    {
        //recursively loop through all child controls
        foreach (Control c in ctrl.Controls)
            SetReadOnly(c, readOnly);
    }
}

8

Siklus Evaluasi / Terapkan terkenal dari SICP

teks alt
(sumber: mit.edu )

Berikut definisi eval:

(define (eval exp env)
  (cond ((self-evaluating? exp) exp)
        ((variable? exp) (lookup-variable-value exp env))
        ((quoted? exp) (text-of-quotation exp))
        ((assignment? exp) (eval-assignment exp env))
        ((definition? exp) (eval-definition exp env))
        ((if? exp) (eval-if exp env))
        ((lambda? exp)
         (make-procedure (lambda-parameters exp)
                         (lambda-body exp)
                         env))
        ((begin? exp) 
         (eval-sequence (begin-actions exp) env))
        ((cond? exp) (eval (cond->if exp) env))
        ((application? exp)
         (apply (eval (operator exp) env)
                (list-of-values (operands exp) env)))
        (else
         (error "Unknown expression type - EVAL" exp))))

Berikut definisi dari aplikasinya:

(define (apply procedure arguments)
  (cond ((primitive-procedure? procedure)
         (apply-primitive-procedure procedure arguments))
        ((compound-procedure? procedure)
         (eval-sequence
           (procedure-body procedure)
           (extend-environment
             (procedure-parameters procedure)
             arguments
             (procedure-environment procedure))))
        (else
         (error
          "Unknown procedure type - APPLY" procedure))))

Berikut definisi eval-sequence:

(define (eval-sequence exps env)
  (cond ((last-exp? exps) (eval (first-exp exps) env))
        (else (eval (first-exp exps) env)
              (eval-sequence (rest-exps exps) env))))

eval-> apply-> eval-sequence->eval


7

Rekursi digunakan dalam hal-hal seperti pohon BSP untuk deteksi tabrakan dalam pengembangan game (dan area serupa lainnya).


7

Orang sering kali mengurutkan tumpukan dokumen menggunakan metode rekursif. Misalnya, bayangkan Anda menyortir 100 dokumen dengan nama di dalamnya. Pertama-tama, tempatkan dokumen ke dalam tumpukan berdasarkan huruf pertama, lalu urutkan setiap tumpukan.

Mencari kata-kata dalam kamus sering kali dilakukan dengan teknik seperti pencarian biner, yang bersifat rekursif.

Dalam organisasi, atasan sering memberikan perintah kepada kepala departemen, yang pada gilirannya memberi perintah kepada manajer, dan sebagainya.


5

Persyaratan dunia nyata yang saya dapatkan baru-baru ini:

Persyaratan A: Terapkan fitur ini setelah memahami Persyaratan A.


4

Parser dan compiler dapat ditulis dengan metode keturunan rekursif. Bukan cara terbaik untuk melakukannya, karena alat seperti lex / yacc menghasilkan parser yang lebih cepat dan efisien, tetapi secara konseptual sederhana dan mudah diimplementasikan, sehingga tetap umum.


4

Rekursi diterapkan pada masalah (situasi) di mana Anda dapat memecahnya (menguranginya) menjadi bagian-bagian yang lebih kecil, dan setiap bagian terlihat mirip dengan masalah aslinya.

Contoh bagus tentang hal-hal yang berisi bagian-bagian kecil yang mirip dengan dirinya sendiri adalah:

  • struktur pohon (cabang seperti pohon)
  • daftar (bagian dari daftar masih daftar)
  • wadah (boneka Rusia)
  • urutan (bagian dari urutan terlihat seperti berikutnya)
  • kelompok objek (subkelompok masih sekelompok objek)

Rekursi adalah teknik untuk terus memecah masalah menjadi potongan-potongan kecil dan kecil, sampai salah satu potongan itu menjadi cukup kecil untuk menjadi sepotong kue. Tentu saja, setelah Anda memecahnya, Anda kemudian harus "menjahit" hasilnya kembali dalam urutan yang benar untuk membentuk solusi total dari masalah awal Anda.

Beberapa algoritme pengurutan rekursif, algoritme berjalan di pohon, algoritme peta / reduksi, divide-and-conquer adalah contoh dari teknik ini.

Dalam pemrograman komputer, sebagian besar bahasa jenis panggilan-kembali berbasis tumpukan sudah memiliki kemampuan yang dibangun untuk rekursi: yaitu

  • memecah masalah menjadi bagian-bagian yang lebih kecil ==> menyebut dirinya dalam subset yang lebih kecil dari data asli),
  • melacak bagaimana potongan dibagi ==> tumpukan panggilan,
  • menjahit hasilnya kembali ==> kembali berbasis tumpukan


4

Beberapa contoh rekursi yang bagus ditemukan dalam bahasa pemrograman fungsional . Dalam bahasa pemrograman fungsional ( Erlang , Haskell , ML / OCaml / F # , dll.), Sangat umum untuk memiliki pemrosesan daftar menggunakan rekursi.

Ketika berhadapan dengan daftar dalam bahasa gaya-OOP imperatif yang khas, sangat umum untuk melihat daftar diimplementasikan sebagai daftar tertaut ([item1 -> item2 -> item3 -> item4]). Namun, dalam beberapa bahasa pemrograman fungsional, Anda menemukan bahwa daftar itu sendiri diimplementasikan secara rekursif, di mana "kepala" daftar menunjuk ke item pertama dalam daftar, dan "ekor" menunjuk ke daftar yang berisi item lainnya ( [item1 -> [item2 -> [item3 -> [item4 -> []]]]]). Cukup kreatif menurut saya.

Penanganan daftar ini, bila dikombinasikan dengan pencocokan pola, SANGAT ampuh. Katakanlah saya ingin menjumlahkan daftar angka:

let rec Sum numbers =
    match numbers with
    | [] -> 0
    | head::tail -> head + Sum tail

Ini pada dasarnya mengatakan "jika kita dipanggil dengan daftar kosong, kembalikan 0" (memungkinkan kita untuk menghentikan rekursi), jika tidak mengembalikan nilai head + nilai Sum yang dipanggil dengan item yang tersisa (karenanya, rekursi kita).

Misalnya, saya mungkin memiliki daftar URL , menurut saya memisahkan semua URL yang ditautkan ke setiap URL, lalu saya mengurangi jumlah total tautan ke / dari semua URL untuk menghasilkan "nilai" untuk halaman (pendekatan yang digunakan Google mengambil dengan PageRank dan yang dapat Anda temukan didefinisikan dalam kertas MapReduce asli ). Anda juga dapat melakukan ini untuk menghasilkan jumlah kata dalam dokumen. Dan banyak, banyak, banyak hal lainnya juga.

Anda dapat memperluas pola fungsional ini ke semua jenis kode MapReduce di mana Anda dapat mengambil daftar sesuatu, mengubahnya, dan mengembalikan sesuatu yang lain (apakah daftar lain, atau beberapa perintah zip pada daftar).


3

XML, atau melintasi apapun yang berupa pohon. Meskipun, sejujurnya, saya hampir tidak pernah menggunakan rekursi dalam pekerjaan saya.


Bahkan tidak rekursi ekor?
Apocalisp

Saya menggunakan rekursi sekali dalam karir saya, dan ketika kerangka kerja berubah, saya menyingkirkannya. 80% dari apa yang kami lakukan adalah CRUD.
Charles Graham

1
Menyebutkan "XML" pada awalnya cukup aneh. Ini bukan hal yang wajar, bukan sesuatu yang harus dihadapi oleh orang biasa yang akan Anda ajar dalam kehidupan sehari-hari. Tapi idenya tentu saja cukup masuk akal.
imz - Ivan Zakharyaschev

3

Umpan balik berputar dalam organisasi hierarki.

Atasan teratas memberi tahu eksekutif puncak untuk mengumpulkan umpan balik dari semua orang di perusahaan.

Setiap eksekutif mengumpulkan bawahan langsungnya dan memberi tahu mereka untuk mengumpulkan umpan balik dari bawahan langsung mereka.

Dan di telepon.

Orang yang tidak memiliki bawahan langsung - simpul daun di pohon - memberikan umpan balik mereka.

Umpan balik berjalan kembali ke atas pohon dengan setiap manajer menambahkan umpan baliknya sendiri.

Akhirnya semua umpan balik membuatnya kembali ke atasan.

Ini adalah solusi alami karena metode rekursif memungkinkan pemfilteran di setiap level - menyusun duplikat dan menghilangkan umpan balik ofensif. Atasan teratas dapat mengirim email global dan meminta setiap karyawan melaporkan umpan balik langsung kembali kepadanya, tetapi ada masalah "Anda tidak bisa menangani kebenaran" dan "Anda dipecat", jadi rekursi bekerja paling baik di sini.


2

Misalkan Anda sedang membangun CMS untuk situs web, di mana halaman Anda berada dalam struktur pohon, dengan misalnya root menjadi halaman beranda.

Misalkan juga permintaan {user | client | customer | boss} Anda agar Anda menempatkan jejak runut tautan di setiap halaman untuk menunjukkan posisi Anda di pohon.

Untuk halaman n tertentu, Anda mungkin ingin berjalan ke induk dari n, dan induknya, dan seterusnya, secara rekursif untuk membuat daftar node kembali ke akar pohon halaman.

Tentu saja, Anda menekan db beberapa kali per halaman dalam contoh itu, jadi Anda mungkin ingin menggunakan beberapa SQL aliasing di mana Anda mencari page-table sebagai a, dan page-table lagi sebagai b, dan bergabung dengan a.id dengan b.parent sehingga Anda membuat database melakukan rekursif bergabung. Sudah lama, jadi sintaks saya mungkin tidak membantu.

Kemudian lagi, Anda mungkin hanya ingin menghitung ini sekali dan menyimpannya dengan rekaman halaman, hanya memperbaruinya jika Anda memindahkan halaman. Itu mungkin akan lebih efisien.

Bagaimanapun, itu $ 0,02 saya


2

Anda memiliki pohon organisasi dengan kedalaman N level. Beberapa node dicentang, dan Anda ingin memperluas ke hanya node yang telah diperiksa.

Ini adalah sesuatu yang sebenarnya saya kodekan. Bagus dan mudah dengan rekursi.


2

Dalam pekerjaan saya, kami memiliki sistem dengan struktur data umum yang dapat digambarkan sebagai pohon. Itu berarti rekursi adalah teknik yang sangat efektif untuk bekerja dengan data.

Memecahkannya tanpa rekursi akan membutuhkan banyak kode yang tidak perlu. Masalah dengan rekursi adalah tidak mudah untuk mengikuti apa yang terjadi. Anda benar-benar harus berkonsentrasi saat mengikuti alur eksekusi. Tetapi jika berhasil, kodenya elegan dan efektif.



2
  • Mengurai file XML .
  • Pencarian yang efisien di ruang multi-dimensi. E. g. quad-tree dalam 2D, oct-tree dalam 3D, kd-tree, dll.
  • Pengelompokan hierarki.
  • Kalau dipikir-pikir, melintasi struktur hierarkis apa pun secara alami cocok untuk rekursi.
  • Pemrograman metaprogram di C ++, di mana tidak ada loop dan rekursi adalah satu-satunya cara.

"XML" tidak penting untuk ide jawaban ini (dan menyebutkan secara khusus XML mungkin akan membuat orang yang Anda ajar jijik / bosan). Semua bahasa biasa (bahasa komputer atau bahasa alami) akan menjadi contoh untuk masalah penguraian rekursif.
imz - Ivan Zakharyaschev

Poster tersebut menanyakan "masalah dunia nyata di mana pendekatan rekursif adalah solusi alami". Mengurai file xml tentu saja merupakan masalah dunia nyata, dan secara alami dapat terjadi rekursi. Fakta bahwa Anda tampaknya memiliki keengganan yang aneh terhadap XML tidak mengubah fakta bahwa XML sangat banyak digunakan.
Dima


2

Contoh terbaik yang saya tahu adalah quicksort , ini jauh lebih sederhana dengan rekursi. Melihat:

shop.oreilly.com/product/9780596510046.do

www.amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047

(Klik subtitle pertama di bawah bab 3: "Kode terindah yang pernah saya tulis").


1
Dan MergeSort, juga lebih sederhana dengan rekursi.
Matthew Schinckel

1
Tautannya rusak. Bisakah Anda menambahkan judul buku?
Peter Mortensen

@PeterMortensen, bukunya Beautiful Code oleh Greg Wilson dan Andy Oram. Saya memperbarui tautannya, meskipun tampaknya O'Reilly tidak mengizinkan untuk mengintip ke dalam lagi. Tapi Anda bisa melihat Amazon.
Fabio Ceconello

1

Perusahaan telepon dan kabel mempertahankan model topologi kabel mereka, yang merupakan jaringan atau grafik besar. Rekursi adalah salah satu cara untuk melintasi model ini ketika Anda ingin menemukan semua elemen induk atau semua anak.

Karena rekursi mahal dari perspektif pemrosesan dan memori, langkah ini biasanya hanya dilakukan ketika topologi diubah dan hasilnya disimpan dalam format daftar pra-order yang dimodifikasi.


1

Penalaran induktif, proses pembentukan konsep, bersifat rekursif. Otak Anda melakukannya sepanjang waktu, di dunia nyata.


1

Ditto komentar tentang kompiler. Simpul pohon sintaksis abstrak secara alami cocok untuk rekursi. Semua struktur data rekursif (daftar tertaut, pohon, grafik, dll.) Juga lebih mudah ditangani dengan rekursi. Saya pikir sebagian besar dari kita tidak dapat menggunakan rekursi terlalu sering setelah keluar dari sekolah karena jenis masalah di dunia nyata, tetapi ada baiknya untuk menyadarinya sebagai pilihan.


1

Perkalian bilangan asli adalah contoh rekursi dunia nyata:

To multiply x by y
  if x is 0
    the answer is 0
  if x is 1
    the answer is y
  otherwise
    multiply x - 1 by y, and add x
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.