En Hızlı Mini-Flak Quine


26

Mini-Flak , ve işlemlerinin yasaklandığı Brain-Flak dilinin alt kümesidir . Açıkçası , aşağıdaki regex ile eşleşmemelidir :<><...>[]

.*(<|>|\[])

Mini-Flak, Brain-Flak'ın bilinen en küçük Turing komple alt kümesidir.


Biraz ben yapabildi süre önce Quine içinde Mini Flak , ancak evrenin yaşamda çalıştırmak için çok yavaş.

Bu yüzden sana meydan okuyorum hızlı bir Quine yapmak.


puanlama

Kodunuzu puanlamak için kodunuzun @cysonuna bir bayrak koyun ve bayrağı kullanarak Ruby tercümanında çalıştırın ( Çevrimiçi deneyin yakut tercümanı kullanır) -d. Puanınız STDERR'e aşağıdaki şekilde yazdırılmalıdır:

@cy <score>

Bu, programınızın sonlandırmadan önce gerçekleştirdiği döngü sayısıdır ve çalıştırmalar arasında aynıdır. Her bir çevrimin çalışması yaklaşık aynı süreyi gerektirdiğinden, puanınız, programınızı çalıştırmak için harcadığınız zamanla doğrudan ilişkilendirilmelidir.

Quine'ınız bilgisayarınızda makul bir şekilde çalışmanız için çok uzunsa, döngü sayısını elle hesaplayabilirsiniz.

Döngü sayısının hesaplanması çok zor değildir. Döngü sayısı, çalışan monad sayısının 2 katı artı çalışan nilad sayısının 2 katı kadardır. Bu, her bir nilafı tek bir karakterle değiştirmek ve toplamda çalışan karakter sayısını saymakla aynıdır.

Örnek puanlama

  • (()()()) puanlar 5 çünkü 1 monad ve 3 nilad var.

  • (()()()){({}[()])} 29 puan, ilk bölüm öncekiyle aynı ve 5 puan alırken, döngü 6 monad ve 2 nilads 8 puan alır. 1*5 + 3*8 = 29


Gereksinimler

Programınız ...

  • En az 2 bayt olun

  • -ABayrak kullanarak Brain-Flak'ta yürütüldüğünde kaynak kodunu yazdırın

  • Regex ile eşleşmiyor .*(<|>|\[])


İpuçları

  • Vinç-Flak tercüman hızlı yakut tercüman daha kategorik ama bazı özellikleri eksik. İlk önce Crane-Flak'ı kullanarak kodunuzu test etmenizi ve çalıştığını bildiğiniz zaman bunu ruby ​​tercümandan almanızı öneririm. Programınızı TIO'da çalıştırmamanızı da kesinlikle tavsiye ederim. TIO yalnızca masaüstü tercümandan daha yavaş değil, aynı zamanda yaklaşık bir dakika içinde zaman aşımına uğrayacak. Biri TIO zaman aşımına uğramadan önce programlarını çalıştırmak için yeterince düşük puan almayı başarsa çok etkileyici olurdu.

  • [(...)]{}ve (...)[{}]aynı şekilde çalışın <...>ancak kısıtlanmış kaynak gereksinimini bozmayın

  • Dışarı kontrol edebilirsiniz Beyin-Flak ve Mini Flak Eğer bu zorluğu yaklaşım konusunda bir fikir istiyorsanız Quines.


Yanıtlar:


33

Mini Flak, 6851113 devir

Program (kelimenin tam anlamıyla)

Çoğu insanın bir Mini Flak kınının yazdırılamayan karakterler ve hatta çok baytlık karakterler kullanmasını beklemeyeceğini biliyorum (kodlamayı alakalı kılar). Bununla birlikte, bu quine ve quine boyutuyla birleştirilen yazdırılamayanlar (UTF-8'in 102646 baytı olarak kodlanan 93919 karakter), programı bu yazıya yerleştirmeyi oldukça zorlaştırır.

Ancak, program çok tekrarlayan ve bu nedenle, gerçekten iyi sıkıştırır . Böylece, tüm program tam anlamıyla Stack Exchange'den temin edilebilmesi için, aşağıda kapatılabilir olanın arkasına gizlenmiş tam sıranın sıkıştırılmış bir versiyonunun xxdgeri dönüşümlü bir gziphexdump değeri vardır:

(Evet, o kadar tekrarlıdır ki, sıkıştırıldıktan sonra tekrarları bile görebilirsiniz ).

"Programınızı TIO'da çalıştırmamanızı kesinlikle tavsiye ederim. TIO yalnızca masaüstü tercümandan daha yavaş değil, aynı zamanda bir dakika içinde zaman aşımına uğrayacak. TIO önce programları zaman aşımına uğradı. " Bunu yapabilirim! Ruby tercümanını kullanarak TIO'yu çalıştırmak yaklaşık 20 saniye sürüyor: Çevrimiçi deneyin!

Program (kolayca okunabilir)

Şimdi bilgisayarların okuyabileceği programın bir sürümünü verdim, hadi insanların okuyabildiği bir sürümünü deneyelim. Çeki oluşturan baytları kod sayfası 437'ye (yüksek bit kümesine sahiplerse) veya Unicode kontrol resimlerine (ASCII kontrol kodlarıysa) dönüştürdüm, boşluk ekledi (önceden var olan herhangi bir boşluk, kontrol resimlerine dönüştürüldü. ), sözdizimi kullanılarak kodlanmış çalışma uzunluğu «string×length»ve elde edilen bazı veri ağırlıklı bitler:

␠
(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                («()×35» («()×44» («()×44» («()×44» («()×44» («()×45»
                … much more data encoded the same way …
                («()×117»(«()×115»(«()×117»
                «000010101011┬â┬ … many more comment characters … ┬â0┬â┬à00␈␈
                )[({})(
                    ([({})]({}{}))
                    {
                        ((()[()]))
                    }{}
                    {
                        {
                            ({}(((({}())[()])))[{}()])
                        }{}
                        (({}))
                        ((()[()]))
                    }{}
                )]{}
                %Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'×almost 241»
                ,444454545455┬ç┬ … many more comment characters … -a--┬ü␡┬ü-a␡┬ü
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

("Neredeyse 241", 241'inci kopyanın izini kaybetmemesi ', ancak diğer 240 ile aynı olması nedeniyledir.)

açıklama

Yorumlar hakkında

Açıklanacak ilk şey, Mini-Flak komutları olmayan yazdırılamayan karakterlerin ve diğer önemsiz şeylerin nesi var? Maddelere yorum eklemenin sadece işleri zorlaştırdığını düşünebilirsiniz, ancak bu bir hız yarışmasıdır (büyüklük yarışması değil); Bu arada, Brain-Flak ve dolayısıyla Mini-Flak, yığının içeriğini standart çıktıya döktü; yığının yalnızca içerdiğinden emin olmak zorundaysanızProgramınızın komutlarını oluşturan karakterler, yığını temizlemek için döngü harcamak zorunda kalacaksınız. Olduğu gibi, Brain-Flak çoğu karakteri görmezden geliyor, bu yüzden önemsiz yığın öğelerinin geçerli Brain-Flak komutları olmadığından (bunu Brain-Flak / Mini-Flak polyglot yapıyoruz) ve negatif ya da dışarda olmadığından emin olduğumuz sürece Unicode serisi, onları yığında bırakabilir, çıktısını almalarına izin verebilir ve quine özelliğini korumak için aynı karakteri programımıza koyabiliriz.

Bundan yararlanmamızın özellikle önemli bir yolu var. Quine, uzun bir veri dizgesi kullanılarak çalışır ve temel olarak diziden tüm çıktı, veri dizisini çeşitli yollarla biçimlendirerek üretilir. Programın birden fazla parçaya sahip olmasına rağmen, yalnızca bir veri dizgisi var; bu nedenle programın farklı bölümlerini yazdırmak için aynı veri dizisini kullanabilmemiz gerekir. "Önemsiz veriler önemli değil" numarası, bunu çok basit bir şekilde yapmamıza izin veriyor; ASCII koduna bir değer ekleyerek veya çıkararak programı oluşturan karakterleri veri dizisinde saklarız. Spesifik olarak, programın başlangıcını oluşturan karakterler ASCII kodu + 4, ASCII kodu olarak neredeyse 241 kez tekrarlanan bölümü oluşturan karakterler - 4,veri dizisinin bir karakteri olan her karakter; örneğin, her karakter koduna 4 eklenmiş olarak basarsak, tekrarlanan bölümden bir tekrar alırız, önce ve sonra bazı yorumlar. (Bu yorumlar basitçe programın diğer bölümleridir, karakter kodları değiştirilerek geçerli bir Brain-Flak komutu oluşturmazlar, çünkü yanlış ofset eklenir. Sadece Mini değil, Brain-Flak komutlarından kaçınmalıyız. Flak , sorunun kısmını ihlal etmemek için, ofset seçimi, bunu sağlamak için tasarlandı.)

Bu yorum numarası nedeniyle, aslında sadece iki farklı şekilde biçimlendirilmiş veri dizisini çıktılayabiliyoruz: a) kaynaktakiyle aynı şekilde kodlanmış, b) her koda eklenmiş bir ofset ile karakter kodları gibi. Bu, eklenen uzunluğu tamamen değecek kılan büyük bir basitleştirmedir.

Program yapısı

Bu program dört bölümden oluşur: intro, data dizesi, data dizesi formatlayıcısı ve outro. Giriş ve çıkış temel olarak veri dizgisini ve formatlayıcısını bir döngüde çalıştırmaktan, her seferinde uygun formatı belirlemek (yani kodlanıp kodlanmama veya dengelenme ve hangi ofsetin kullanılacağı) belirtilmesinden sorumludur. Veri dizgisi sadece veridir ve onu oluşturan karakterlerin veri dizgisinde kelimenin tam anlamıyla belirtilmediği tek sıranın parçasıdır (bunu yapmak, kendisinden daha uzun olması gerektiği için açıkça imkansız olurdu); bu nedenle, kendisinden yenilenmek için özellikle kolay bir şekilde yazılmıştır. Veri dizgisi formatlayıcısı, her biri veri dizisindeki 241'den belirli bir veriyi biçimlendiren 241 neredeyse aynı parçadan oluşur.

Programın her bölümü aşağıdaki gibi veri dizgisi ve biçimlendiricisi aracılığıyla üretilebilir:

  • Outro'yu üretmek için, veri dizisini +8 ofset ile biçimlendirin
  • Veri dizgisi biçimlendiricisini üretmek için, veri dizgisini +4, 241 kez ofsetle biçimlendirin
  • Veri dizesini üretmek için veri dizisini kaynak biçimine kodlayarak biçimlendirin.
  • Intro'yu üretmek için, veri dizisini -4 ofsetiyle biçimlendirin

Tek yapmamız gereken programın bu bölümlerinin nasıl çalıştığına bakmak.

Veri dizesi

(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45» …

Mini-Flak kodunda kodlamayı tersine çevirebilmemiz gerektiğinden, veri dizisi için basit bir kodlamaya ihtiyacımız var. Bundan daha basit olamazsın!

Bu sıranın arkasındaki ana fikir (yorum numarasının dışında) temelde büyük miktarda veri depolayabileceğimiz tek bir yer olduğunu belirtmek: program kaynağının çeşitli yuvalama seviyelerinde "komut dönüş değerlerinin toplamı". (Bu genellikle üçüncü yığın olarak bilinir., Mini-Flak'ın ikinci bir yığını olmamasına rağmen, "çalışan yığın", Mini-Flak bağlamında daha iyi bir isim olabilir.) Veri depolama için diğer olasılıklar ana / ilk yığın olacaktır (bu çalışmaz) çünkü çıktımızın gitmesi gereken yer, ve çıktıyı uzaktan verimli bir şekilde depodan geçiremedik) ve tek bir yığın öğesinde bignum olarak kodlandı (bu sorun için uygun değil çünkü ondan veri çıkarmak); Bunları ortadan kaldırdığınızda, çalışma yığını kalan tek konumdur.

Verileri bu yığında "saklamak" için, (…)daha sonra veri dizgisi biçimlendiricisi içinde dengelenecek dengesiz komutları (bu durumda, komutun ilk yarısı ) kullanırız. Biçimlendirici içindeki bu komutlardan birini kapattığımız her seferinde, veri dizgisinden alınan bir verinin toplamını ve biçimlendirici içindeki bu yerleştirme seviyesindeki tüm komutların dönüş değerlerini itecektir; ikincisinin sıfıra eklenmesini sağlayabiliriz, bu nedenle biçimlendirici yalnızca veri dizesinden alınan tek değerleri görür.

Biçimi çok basittir: n'in( ardından n kopyaları (), n saklamak istediğimiz sayıdır. (Bunun yalnızca negatif olmayan sayıları depolayabileceğimiz anlamına geldiğini ve veri dizisinin son öğesinin pozitif olması gerektiğini unutmayın.)

Veri dizgisi hakkında biraz sezgisel bir nokta hangi sırayla olduğu. Veri dizisinin "başlangıcı", programın başlangıcına, yani en dışa yerleştirme seviyesine yakın olan uçtur; bu kısım en son formatlanır (formatlayıcı en içten dışa doğru iç içe geçme seviyelerine kadar uzanır). Ancak, en son biçimlendirilmesine rağmen, önce basılır , çünkü önce yığına basılan değerler en son Mini-Flak yorumlayıcısı tarafından yazdırılır. Aynı ilke programın tamamı için de geçerlidir; önce outro'yu, sonra veri dizgisi biçimlendiricisini, sonra veri dizesini, sonra introyu, yani programda depolandıkları sıranın tersini biçimlendirmemiz gerekir.

Veri dizesi biçimlendirici

)[({})(
    ([({})]({}{}))
    {
        ((()[()]))
    }{}
    {
        {
            ({}(((({}())[()])))[{}()])
        }{}
        (({}))
        ((()[()]))
    }{}
)]{}

Veri dizgisi formatlayıcısı, her biri veri dizisinin belirli bir karakterini biçimlendiren, her biri özdeş koda (24 bölümden biri marjinal olarak farklı yorumlara sahip) olan 241 bölümden oluşur. (Burada bir döngü kullanamadık: dengesini )eşleştirerek veri dizisini okumak için dengesiz bir şeye ihtiyacımız var (ve bunlardan birini bir {…}döngü içine koyamıyoruz , var olan tek döngü biçimi. Öyleyse biz " "biçimlendiricinin kilidini kaldırın ve veri dizisini biçimlendiricinin 241 kez kaymasıyla çıkarmak için giriş / çıkış alın.

)[({})( … )]{}

Bir biçimlendirici öğesinin en dış kısmı, veri dizesinin bir öğesini okur; veri dizisinin kodlamasının sadeliği, onu okumakta biraz karmaşıklığa yol açar. Eşleştirilmemiş (…)veri dizgisini kapatıp başlıyoruz , sonra […]iki değeri negate ( ) iki değeri: sadece veri dizesinden ( ({})) okuduğumuz veri ( ) ve programın geri kalanının dönüş değeri. Formatlayıcı öğenin geri kalanının dönüş değerini ile(…) kopyalarız ve kopyasını olumsuzlanan sürüme ekleriz {}. Sonuç, veri dizisi öğesinin ve biçimlendirici öğesinin dönüş değerinin birlikte veri eksi veri eksi veri dönüş değeri artı dönüş değeri, veya 0; Bu, bir sonraki veri dizgisi elemanının doğru değeri üretmesini sağlamak için gereklidir.

([({})]({}{}))

Formatlayıcı, hangi modda olduğunu bilmek için üst stack öğesini kullanır (0 = veri dizgisi formatında format, herhangi bir değer = çıktısı olan uzaklık). Bununla birlikte, sadece veri dizgisini okumuş olmak üzere, veri yığındaki formatın üstündedir ve onlardan tam tersi olmasını istiyoruz. Bu kod alarak beyin-Flak takas kodunun kısa bir varyantı olan bir yukarıdaki B için b , yukarıda bir  +  b ; ilave yan etkisi nedeniyle, sadece bu, daha yararlı daha kısa (bu özel durumda), aynı zamanda var olan b için bir zaman sorun değildir b 0 olduğu zaman, ve b 0 değil, bizim için ofset hesaplama yapar.

{
    ((()[()]))
}{}
{
    …
    ((()[()]))
}{}

Brain-Flak sadece bir kontrol akış yapısına sahiptir, bu yüzden bir whiledöngüden başka bir şey istiyorsak biraz çalışacağız. Bu bir "olumsuz" yapıdır; yığının üstüne 0 varsa, kaldırır, aksi halde yığının üstüne 0 yerleştirir. (Çok basit bir şekilde çalışır: yığının üstünde 0 olmadıkça, yığının üstüne 1 - 1 iki kez itin; işiniz bittiğinde, üst yığın öğesini açın.)

Kodu, burada görüldüğü gibi olumsuz bir yapının içine yerleştirmek mümkündür. Kod yalnızca yığının üstü sıfır olmadığında çalışır; bu nedenle, iki negatif yapımız varsa, ilk iki yığın öğesinin her ikisi de sıfır olmadığını varsayarsak , birbirlerini iptal ederler, ancak ilk yapı içindeki herhangi bir kod yalnızca üst yığın öğesi sıfır olmadığında çalışır ve kodun içindeki ikinci yapı sadece üst yığın elemanı sıfır olduğunda çalışacaktır. Başka bir deyişle, bu bir if-then-else ifadesinin karşılığıdır.

Eğer format sıfır değilse, çalışan "o zaman" cümlesinde, aslında yapacak bir şeyimiz yok; İstediğimiz şey data + offset'i ana yığına itmek (programın sonunda çıkarılabilmesi için), ama zaten orada. Bu yüzden sadece veri dizgisi elemanını kaynak biçiminde kodlama ile ilgilenmeliyiz.

{
    ({}(((({}())[()])))[{}()])
}{}
(({}))

İşte böyle yaparız. {({}( … )[{}()])}{}Yapı çalışma yığına döngü sayacı hareket etmesini ve orada tutarak çalışır yineleme belirli bir sayıda (bir döngü olarak sahibi olmalıdır, çalışma yığına erişim bağlı olduğu için, herhangi bir başka bir kod güvenli olacak Programın yuvalanma düzeyi). Döngünün gövdesi ((({}())[()])), üst yığın elemanının üç kopyasını çıkarır ve en düşük 1 ekler. Diğer bir deyişle, bu 41 üzerinde 40 üzerinde 40 içine yığını üstüne bir 40 dönüşümleri veya ASCII olarak görülebilir (içine ((); yapacak defalarca bu çalışan (içine (()içine (()()içine (()()()böylece vb ve bizim veri dize oluşturmak için basit bir yoludur (a var olduğunu varsayarak (yığının üstünde zaten).

Döngü bittikten sonra (({})), yığının üstünü çoğaltır (böylece şimdi ((()…yerine şimdi başlar (()…. Baştaki (, bir sonraki karakteri biçimlendirmek için veri dizesi biçimlendiricinin bir sonraki kopyası tarafından kullanılır (genişletir) (()(()…sonra (()()(()…, ve benzerleri, bu (, veri dizisinde ayırmayı oluşturur ).

%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'

Veri dizgisi formatlayıcısında son bir ilgi alanı var. Tamam, bu yüzden çoğunlukla bu sadece 4 kod noktası aşağı doğru kayan outro; ancak, sondaki kesme işareti yerinden görünmeyebilir. '(kod noktası 39) +bir Brain-Flak komutu olmayan (kod noktası 43) olur, bu nedenle başka bir amaç için orada olduğunu tahmin etmiş olabilirsiniz.

Bunun nedeni, veri dizgisi formatlayıcısının orada (zaten bir yığında olmasını beklemektir (herhangi bir yerde hazır bilgi 40 içermez). 'aslında veri dizisi biçimlendiriciyi, sonunu değil, diziyi oluşturmak için tekrarlanan bloğun başlangıcındadır, bu yüzden veri dizesi biçimlendiricisinin karakterleri yığının üzerine itildikten sonra (ve kod veri dizisini yazdırmak üzere hareket etmek üzeredir). kendisi), outro, yığının üstündeki 39'u 40'lık bir değere ayarlar, onu kullanmak için biçimlendiriciye (bu durumda çalışan biçimlendiricinin kendisi değil kaynak gösterimi için) hazırlar. Bu yüzden biçimlendiricinin "neredeyse 241" kopyası var; ilk kopya ilk karakterinde eksik. Ve bu karakter, kesme işareti, programın herhangi bir yerinde Mini-Flak koduna karşılık gelmeyen veri dizisindeki sadece üç karakterden biridir; sadece bir sabit sağlama yöntemi olarak var.

Giriş ve çıkış

(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                …
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

Giriş ve çıkış kavramsal olarak programın aynı kısmıdır; Bir ayrım yapmamızın tek nedeni, outro'nun veri dizesinden önce çıktısının alınması ve biçimlendiricisinin (onlardan sonra yazdırılması için) olması ve intro'nun onlardan sonra çıkması gerektiğidir (onlardan önce basılması).

(((()()()()){}))

Yığına 8 adet iki kopya koyarak başlıyoruz. Bu ilk yinelemenin ofsetidir. İkinci kopya, ana döngünün yığının üstündeki yığının üzerinde bir önemsiz eleman olmasını beklediği, ana döngünün var olup olmayacağına karar veren testten geride bıraktığı ve bu yüzden de bir istenmeyen eleman yerleştirmemiz gerektiği içindir. gerçekte istediğimiz öğeyi atmaz; Bir kopya bunun en kısa yoludur (bu nedenle çıktıya en hızlı şekilde).

8 sayısının bundan daha uzun olmayan başka gösterimleri var. Ancak, en hızlı kod için giderken, bu kesinlikle en iyi seçenektir. Birincisi, ()()()()kullanım, örneğin, (()()){}her ikisi de 8 karakter uzunluğunda olmasına rağmen, birincisi daha hızlı olduğundan, çünkü daha hızlıdır, çünkü (…)2 döngü olarak sayılır, ancak ()sadece tek olarak. Bununla birlikte, bir çevrimi kaydetmek, bir için çok daha büyük bir kıyasla önemsizdir : (ve )daha düşük kod noktalarına sahiptir {ve }bu nedenle, onlar için veri parçasını oluşturmak çok daha hızlı olacaktır (ve veri parçası kodda daha az yer kaplar, çok).

{{} … }{}{}

Ana döngü. Bu, yinelemeleri saymaz (bu bir whiledöngüdür, bir fordöngü değildir ve ayrılmak için bir test kullanır). Çıktıktan sonra, ilk iki yığın öğesini atıyoruz; üst öğe zararsız bir 0'dır, ancak aşağıdaki öğe "sonraki yinelemede kullanılacak biçim" olacaktır; bu (negatif bir öteleme) negatif bir sayıdır ve Mini -Flak programı çıkar, tercüman onları çıkarmaya çalışırken çöker.

Bu döngü, kesmek için açık bir test kullandığından, bu testin sonucu yığında bırakılacaktır, bu nedenle yaptığımız ilk şey olarak atıyoruz (değeri faydalı değildir).

(({})[(()()()())])

Bu kod 4 ve f  - 4'ü bir istif elemanının f üzerine iterken , bu elemanı yerinde bırakır. Bir sonraki yinelemenin formatını önceden hesaplıyoruz (sürekli 4 kullanışlı olmasına rağmen) ve yığını aynı anda programın sonraki birkaç kısmı için doğru sıraya sokuyoruz: f için format olarak kullanacağız: Bu yineleme ve 4 daha önce gereklidir.

(({})( … )[{}])

Bu  , çalışma yığında f - 4 kopyasını kaydeder , böylece bir sonraki yineleme için kullanabiliriz. (Değeri f biz doğru yere manevra olabilir bile hala bu noktada mevcut olacak, ancak yığın garip bir yerde olacak ve biz döngüleri ondan 4 çıkarılarak geçirmek olurdu, ve bu çıkarma işlemini yapmak için kodu yazdırma döngüleri. Şimdi depolamak çok daha kolay.)

{{}{}((()[()]))}{}

Ofsetin 4 olup olmadığını görmek için bir test (yani f  - 4, 0'dır). Eğer öyleyse, veri dizgisi formatlayıcısını yazdırıyoruz, bu nedenle veri dizgisini ve formatlayıcısını bu ofsette bir kez değil 241 kez çalıştırmamız gerekiyor. Kod oldukça basittir: f  - 4 sıfır değilse , f  - 4 ile 4'ün kendisini bir çift sıfırla değiştirin; sonra her iki durumda da üst yığın öğesini açın. Şimdi yığında f üzerinde bir sayı var , 4 (bu yinelemeyi 241 kez yazdırmak istiyorsak) veya 0 (sadece bir kez yazdırmak istiyorsak).

(
    ((((((({})){}){}{})){}{}){}){}
    ()
)

Bu ilginç bir Brain-Flak / Mini-Flak sabiti çeşididir; Buradaki uzun çizgi 60 sayısını temsil eder. ()Normal olarak Brain-Flak sabitlerinde yer alan eksiklikten dolayı kafanız karışabilir ; bu normal bir sayı değil, sayıları çoğaltma işlemi olarak yorumlayan bir Kilise sayısıdır. Örneğin, burada görülen 60 numaralı Kilise rakamı, girişinin 60 kopyasını çıkarır ve hepsini bir araya getirir; Brain-Flak'ta birleştirebileceğimiz tek şey normal sayılardır, ayrıca ek olarak, yığının üst kısmına 60 kopya ekleyerek yığının tepesini 60 ile çarptık.

Bir yandan not olarak, Mini-Flak'ta da uygun sayıyı bulmak için, Underload sözdiziminde Church rakamları üreten bir Underload rakam bulucusunu kullanabilirsiniz . Düşük yük sayıları (sıfırdan farklı) "yinelenen üst yığın öğesi" :ve "ilk iki yığın öğelerini birleştir" işlemlerini kullanır *; hem bu işlemler sadece tercüme yüzden, Beyin Flak var :etmek ), *için {}, başa getirebilir {}ve yeterli eklemek (dengesine başında (bu ana yığını ve çalışma yığının garip karışımı kullanıyor, ama çalışıyor).

Bu özel kod parçası, 60 x  + 1 ifadesini üretmek için, 60 nolu kilisenin sayısını (etkili bir şekilde "60 ile çarpma" pasajı), bir artış ile birlikte kullanır; 241'den veya 0'a sahip olsaydık, sadece 1 değerini alırız, yani bu, ihtiyacımız olan yineleme sayısını doğru olarak hesaplar.

241'in seçimi tesadüfi değil; a) seçilen bir değerdi, yaklaşık olarak programın ne kadar süreceği ve b) 1 bir tur sayısının 4 katından fazla. Bu durumda 60 olan yuvarlak sayılar, Kilise sayıları olarak daha kısa gösterime sahip olma eğilimindedir, çünkü kopyalamak için faktörlerde daha fazla esnekliğe sahipsiniz. Program daha sonra uzunluğu tam olarak 241'e getirmek için dolguyu içerir.

{
    ({}(
        …
    )[{}()])
}{}

Bu bir for döngüsü, daha önce görüldüğü gibi, içindeki kodu basitçe ana yığının tepesine eşit miktarda (tükettiği; döngü sayacının kendisi çalışma yığında saklanır, ancak görünürlüğü) çalıştırır. bu, programın yuva seviyesine bağlıdır ve bu nedenle for döngüsünün kendisiyle etkileşime girmesi dışında hiçbir şeyin imkansız olması). Bu aslında veri dizgisini ve formatlayıcısını 1 veya 241 kez çalıştırıyor ve şimdi ana yığından kontrol akışını hesaplama için kullandığımız tüm değerleri attığımız için, bunun için hazır olacak formata sahibiz. kullanılacak formatlayıcı.

(␀␀!S␠su! … many more comment characters … oq␝qoqoq)

Buradaki yorum tamamen ilgisiz değil. Birincisi, birkaç Brain-Flak komutu var; )ucunda doğal bir program işin çeşitli kesimleri arasındaki geçişler, bu şekilde bir yan etkisi olarak oluşturulur (Yorum iç koyarak başlangıcında el ile denge (ve açıklama içinde uzunluğuna rağmen ilave edildi bir ()komut hala bir ()komuttur, bu nedenle tek yapması gereken, veri dizgisinin ve formatlayıcısının dönüş değerine 1 eklemesidir;

Daha da önemlisi, yorumun başlangıcındaki bu NUL karakterleri açıkça hiçbir şeyden mahsup edilmez (+8 ile -4 arasındaki fark bile (bir NUL'a dönüşmek için yeterli değildir ). Bunlar, 239 elemanlı veri dizisini 241 elemana kadar getirecek saf dolgulardır (kendileri için kolayca öderler: gerekli yinelemelerin sayısını hesaplarken 1 ile 241 yerine 1 - 239 üretmek için iki bayttan çok daha fazla zaman alırlardı. ). Dolgu karakteri olarak NUL kullanıldı çünkü mümkün olan en düşük kod noktasına sahip (veri dizgisi için kaynak kodunu daha kısa ve böylece daha hızlı çıkış).

{}({}())

Üst yığın öğesini (kullandığımız biçim) bırakın, bir sonrakine 1 ekleyin (çıkacak olan son karakter, yani yazdırılacak ilk karakter, yeni biçimlendirdiğimiz program bölümünün). Artık eski formata ihtiyacımız yok (yeni format, çalışma yığınında saklanıyor); ve artış, çoğu durumda zararsızdır 've veri dizgisi biçimlendiricisinin kaynak gösteriminin bir ucunda bir olarak değiştirir ((bu, veri dizgisini biçimlendirmek için biçimlendiriciyi bir sonraki çalıştırmamız için gerekli olan yığın). Outro veya intro'da bunun gibi bir dönüşüme ihtiyacımız var, çünkü her veri dizesi biçimlendirici öğesini başlangıçta zorlamak (biraz daha karmaşık hale getirecek (daha (sonra kapatıp ardından etkisini geri almamız gerektiği gibi ) veBir şekilde fazladan üretmek gerekiyordu (biz sadece çünkü bir yerlerde neredeyse (bunu zararsız bir karakter yapılacak en iyi şeydir nedenle tüm 241 biçimlendiricinin 241 kopya, 'eksik olan biridir).

(({})(()()()()){})

Son olarak, döngü çıkış testi. Ana yığının şu anki tepesi, bir sonraki yineleme için ihtiyaç duyduğumuz formattır (çalışma yığınından yeni çıkmış). Bu kopyalar ve kopyaya 8 ekler; Elde edilen değer bir sonraki döngüde atılır. Ancak, introyu yeni basarsak, ofset -4'tür, böylece "sonraki yineleme" için ofset -8 olur; -8 + 8, 0 olur, bu yüzden döngü daha sonra yinelemeye devam etmek yerine çıkar.


16

128,673,515 döngü

Çevrimiçi deneyin

açıklama

Miniflak yarıklarının yavaş olmasının nedeni, Miniflak'ın rastgele erişime sahip olmamasıdır. Bunu aşmak için bir sayı alan ve bir veri döndüren bir kod bloğu oluşturdum. Her referans noktası, daha önce olduğu gibi tek bir karakteri temsil eder ve ana kod, bir kerede her biri için bu bloğu sorgular. Bu aslında rastgele erişim belleğinin bir bloğu olarak çalışır.


Bu kod bloğunun iki gereksinimi vardır.

  • Bir sayı almalı ve yalnızca o karakter için karakter kodunu vermelidir.

  • Brain-Flak'ta arama tablosunu azar azar yeniden oluşturmak kolay olmalı

Bu bloğu inşa etmek için Miniflak'ın Turing'in tamamlandığına dair kanıtımdan bir metodu tekrar kullandım. Her veri için bu gibi görünen bir kod bloğu var:

(({}[()])[(())]()){(([({}{})]{}))}{}{(([({}{}(%s))]{}))}{}

Bu, istifin üstündeki sayıdan bir çıkarır ve eğer sıfır sıfatı %sonun altındaki altına iter . Her parça boyutu birer birer azalttığı için, yığında n ile başlarsanız t veriyi geri alırsınız.

Bu hoş ve modüler, bu nedenle kolayca bir program tarafından yazılabilir.


Daha sonra, bu belleği kaynağa çeviren makineyi kurmamız gerekiyor. Bu 3 bölümden oluşur:

(([()]())())
{({}[(
  -Look up table-
 )]{})
 1. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}(([{}]))(()()()()()))]{})}{}

 2. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}
      (({}[(
      ({}[()(((((()()()()()){}){}){}))]{}){({}[()(({}()))]{}){({}[()(({}((((()()()){}){}){}()){}))]{}){({}[()(({}()()))]{}){({}[()(({}(((()()()()())){}{}){}))]{}){([(({}{}()))]{})}}}}}{}
      (({}({}))[({}[{}])])
     )]{}({})[()]))
      ({[()]([({}({}[({})]))]{})}{}()()()()()[(({}({})))]{})
    )]{})}{}

 3. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}
     (({}(({}({}))[({}[{}])][(
     ({}[()(
      ([()](((()()[(((((((()()()){})())){}{}){}){})]((((()()()()())){}{}){})([{}]([()()](({})(([{}](()()([()()](((((({}){}){}())){}){}{}))))))))))))
     )]{})
     {({}[()(((({})())[()]))]{})}{}
     (([(((((()()()()){}){}()))){}{}([({})]((({})){}{}))]()()([()()]({}(({})([()]([({}())](({})([({}[()])]()(({})(([()](([({}()())]()({}([()](([((((((()()()())()){}){}){}()){})]({}()(([(((((({})){}){}())){}{})]({}([((((({}())){}){}){}()){}()](([()()])(()()({}(((((({}())())){}{}){}){}([((((({}))){}()){}){}]([((({}[()])){}{}){}]([()()](((((({}())){}{}){}){})(([{}](()()([()()](()()(((((()()()()()){}){}){}()){}()(([((((((()()()())){}){}())){}{})]({}([((((({})()){}){}){}()){}()](([()()])(()()({}(((((({}){}){}())){}){}{}(({})))))))))))))))))))))))))))))))))))))))))))))))
     )]{})[()]))({()()()([({})]{})}{}())
    )]{})}{}

   ({}[()])
}{}{}{}
(([(((((()()()()){}){}())){}{})]((({}))([()]([({}())]({}()([()]((()([()]((()([({})((((()()()()){}){}()){})]()())([({})]({}([()()]({}({}((((()()()()()){}){}){}))))))))))))))))))

Makine 1 ile başlayan ve 3 ile biten dört bölümden oluşmaktadır. Bunları yukarıdaki kodda etiketledim. Her bölüm, kodlama için kullandığım aynı arama tablosu formatını da kullanıyor. Bunun nedeni, tüm programın bir döngüde yer almasıdır ve döngüden her geçtiğimizde her bölümü çalıştırmak istemiyoruz, böylece aynı RA yapısına yerleştiriyoruz ve her seferinde arzu ettiğimiz bölümü sorguluyoruz.

1

Bölüm 1 basit bir kurulum bölümüdür.

Program ilk sorgular bölüm 1 ve datum 0'ı bildirir. Datum 0 mevcut değil, bu nedenle bu değeri döndürmek yerine her veri için sorguyu bir kez azaltır. Bu yararlıdır, çünkü sonucu gelecek bölümlerde önemli olacak veri sayısını belirlemek için kullanabiliriz. Bölüm 1, sonucu olumsuzlaştırarak veri sayısını kaydeder ve Bölüm 2 ve son verileri sorgular. Tek sorun bölüm 2'yi doğrudan sorgulayamayız. Geriye kalan başka bir azalma olduğu için, devam etmeyen bir bölümü 5 sorgulamamız gerekir. Aslında, başka bir bölüm içindeki bir bölümü her sorguladığımız zaman bu böyle olacaktır. Bunu açıklamamda görmezden geleceğim, ancak bir kodu arıyorsanız, sadece 5'in bir bölüme geri döndüğünü ve 4'ün aynı bölümü tekrar çalıştırdığını hatırlayın.

2

Bölüm 2, verileri veri bloğundan sonra kodu oluşturan karakterlere dönüştürür. Her zaman yığının şöyle görünmesini bekler:

Previous query
Result of query
Number of data
Junk we shouldn't touch...

Her olası sonucu (1 ile 6 arasında bir sayı) altı geçerli Miniflak karakterinden ( (){}[]) biriyle eşleştirir ve "dokunmamalıyız" ile veri sayısının altına yerleştirir. Bu bize şöyle bir yığın kazandıracak:

Previous query
Number of data
Junk we shouldn't touch...

Buradan bir sonraki veriyi sorgulamamız gerekiyor ya da hepsini sorguladığımızda 3. bölüme geçiyoruz. Önceki sorgu aslında gönderilen tam sorgu değil, sorgu eksi bloktaki veri sayısını ifade ediyor. Bunun nedeni, her bir verinin sorguyu birer birer azalttığı, böylece sorgunun oldukça karışık çıktığıdır. Bir sonraki sorguyu oluşturmak için, veri sayısının bir kopyasını ekler ve birini çıkarırız. Şimdi yığınımız şuna benziyor:

Next query
Number of data
Junk we shouldn't touch...

Eğer bir sonraki sorgumuz sıfırsa, 3. bölümde ihtiyaç duyulan tüm belleği okuduk, bu yüzden tekrar sorguya veri sayısını ekledik ve 3. bölüme geçmek için yığının üstüne bir 4 tokat attık. Bir sonraki sorgu sıfır değilse biz 2. bölümü tekrar çalıştırmak için yığına bir 5 koyun.

3

3. Bölüm, RAM'ini 3. bölümdeki gibi sorgulayarak veri bloğunu yapar.

Kısalık uğruna, 3. bölümün nasıl çalıştığının ayrıntılarının çoğunu atlayacağım. Her veriyi bir karaktere çevirmek yerine, her birini RAM'e girişini temsil eden uzun bir kod grubuna çevirmek dışında, bölüm 2 ile neredeyse aynıdır. Bölüm 3 tamamlandığında, programa döngüden çıkmasını söyler.


Döngü çalıştırıldıktan sonra programın sadece ilk sırayı itmesi gerekir ([()]())(()()()()){({}[(. Bunu standart Kolmogorov-karmaşıklık tekniklerini uygulayan aşağıdaki kodla yapıyorum.

(([(((((()()()()){}){}())){}{})]((({}))([()]([({}())]({}()([()]((()([()]((()([({})((((()()()()){}){}()){})]()())([({})]({}([()()]({}({}((((()()()()()){}){}){}))))))))))))))))))

Umarım bu açıktı. Herhangi bir konuda kafanız karışıksa lütfen yorum yapın.


Bu ne kadar sürecek? TIO'da zamanlar.
Pavel

@Pavel TIO'da çalıştırmıyorum çünkü bu inanılmaz derecede yavaş olurdu, TIO'nun kullandığı tercümanı kullanıyorum ( yakut ). Erişebildiğim eski bir raf sunucusunda çalıştırmak yaklaşık 20 dakika sürüyor. Crain-Flak'ta yaklaşık 15 dakika sürüyor, ancak Crain-Flak'ın hata ayıklama bayrakları yok, bu yüzden Ruby tercümanında çalıştırmadan puanlayamıyorum.
Buğday Sihirbazı

@Pavel Tekrar koştum ve zamanladım. Bu sürdü 30m45.284syakut tercüman kullanılarak oldukça düşük uç sunucusu (bir ortalama modern masaüstü hemen eşit) üzerine tamamlayın.
Buğday Sihirbazı,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.