Collatz tarzında yumurta avı


11

Büyük API Paskalya Yumurtası Avı esinlenerek !

özet

Senin görevin mümkün olan en az adım kullanarak "Collatz alanı" (daha sonra açıklanacak) önceden belirlenmiş bir tamsayı aramaktır.

Giriş

Bu meydan okuma, umarım burada herkesin en azından duyduğu ünlü Collatz varsayımına dayanır. Print the Super Collatz numaralarından alınan bir özet .

Collatz Sırası Eğer herhangi bir pozitif tamsayı ile başlar bu örnek için biz 10 kullanacaktır ve buna adımların bu grubu uygulamak nerede (ayrıca 3x + 1 sorun olarak adlandırılır) 'dir:

if n is even:
    Divide it by 2
if n is odd:
    Multiply it by 3 and add 1
repeat until n = 1

C(m,n)İki sayı arasındaki Collatz mesafesim ve nbu meydan okuma amacıyla, Collatz grafiğindeki iki sayı arasındaki mesafe (Krediler @tsh'den bu kavramı anlatmak için @tsh) aşağıdaki gibi tanımlanır: ( 21ve 13örneklerini kullanarak) ):

Collatz dizisini m (bu durumda 21) yazın:

21, 64, 32, 16, 8, 4, 2, 1

Collatz dizisini n (bu durumda 13) yazın:

13, 40, 20, 10, 5, 16, 8, 4, 2, 1

Şimdi sadece dizilerden birinde kaç sayı göründüğünü sayın. Bu, arasındaki Collatz mesafesi olarak tanımlanır.m ven . Bu durumda 8, yani,

21, 64, 32, 13, 40, 20, 10, 5

Yani Collatz arasındaki mesafe 21 ve 13as C(21,13)=8.

C(m,n) aşağıdaki güzel özelliklere sahip:

C(m,n)=C(n,m)
C(m,n)=0 iff. m=n

Umarım tanımı C(m,n)artık açıktır. Collatz bölgesinde yumurta avı yapmaya başlayalım!

Oyunun başında, bir denetleyici, tek boyutlu koordinatı ile ifade edilen bir paskalya yumurtasının konumuna karar verir: Aralıktaki bir tam sayı [p,q](diğer bir deyişle,p ve qher ikisi de dahil olmak üzere ).

Yumurtanın pozisyonu oyun boyunca sabit kalır. Bu koordinatı şu şekilde göstereceğiz:r .

Şimdi bir başlangıç ​​tahmini 0 yapabilirsiniz ve kontrolör tarafından kaydedilecektir. Bu senin 0. turun. Eğer o kadar şanslıysanız, ilk etapta (yani bir 0 = r) doğru anladınız, oyun biter ve skorunuz 0( skor ne kadar düşükse o kadar iyidir). Aksi takdirde, 1. tura girersiniz ve yeni bir tahmin yaparsınız 1 , doğru olana kadar devam eder, yani bir n = r ve puanınız olacaktır n.

0. turdan sonraki her tur için kontrolör size verilen geri bildirimlerden birini verir, böylece verilen bilgilere dayanarak daha iyi bir tahmin yapabilirsiniz. Lets sen şu anda varsayıyorum ninci turda ve dolayısıyla tahminin bir olduğunu n

  • "Buldun!" bir n = r ise, bu durumda oyun sona erer ve skor yaparsınız n.
  • "Yakınız :)" C (a n , r) <C (a n-1 , r) ise
  • C (a n , r) = C (a n-1 , r) ise "yumurtanın etrafında dönüyorsunuz"
  • "Sen daha uzaktasın :(" eğer C (a n , r)> C (a n-1 , r)

Bazı baytları kaydetmek için, yanıtları yukarıda verilen sırayla "Doğru", "Daha Yakın", "Aynı", "Daha Uzak" olarak adlandıracağım.

İşte ile bir örnek oyun p=1,q=15.

  • a 0 = 10
  • a 1 = 11, yanıt: "Yakından"
  • a 2 = 13, yanıt: "Uzak"
  • a 3 = 4, yanıt: "Uzak"
  • a 4 = 3, yanıt: "Yakından"
  • Bir 5 = 5, yanıtı: "Aynı"
  • a 6 = 7, yanıt: "Doğru"

Skor: 6.

Meydan okuma

Oyunu en iyi skorla oynamak için belirleyici bir strateji tasarlayın p=51, q=562.

Yanıtlar algoritmaları ayrıntılı olarak açıklamalıdır. Algoritmayı açıklamaya yardımcı olan herhangi bir kodu ekleyebilirsiniz. Bu codegolf değildir, bu yüzden okunaklı kod yazmanız önerilir.

Cevaplar, olası tüm vakalar için elde edebilecekleri en kötü puanı içermeli rve en düşük puanı alan puanları kazanmalıdır. Beraberlik durumunda, mümkün olan herkes için daha iyi bir ortalama puanı olan algoritmalarr s (cevaplara da dahil edilmesi gerekir) kazanır. Başka kravat kırıcı yok ve sonunda birden fazla kazananımız olabilir.

gözlük

Ödül (İlk cevap gönderildikten sonra eklenir)

Kişisel olarak tüm tahminlerin menzil içinde yapıldığı ve [51,562]yine de oldukça düşük bir en kötü skoru olduğu bir cevaba ödül verebilirim .


Kontrolörünüz var mı?
user202729

Orijinal sorudaki gibi değil.
Weijun Zhou

1
C (m, n) Collatz grafiğindeki m, n mesafesidir .
tsh

Konsepti kendim buldum ve Collatz grafiğini bilmiyordum. Bunu söylediğin için teşekkürler. Soruya bilgileri ekleyeceğim.
Weijun Zhou

Yanıtlar:


5

Ruby, 196

Başlangıçta düşündüğümden çok daha zordu. Çok fazla belirsiz durumla başa çıkmak zorunda kaldım ve bir sürü çirkin kodla sonuçlandım. Ama çok eğlenceliydi! :)

strateji

Her Collatz Dizisi, 2'lik bir güç dizisiyle sona erer (ör: [16, 8, 4, 2, 1]). 2 gücüne ulaşır ulaşmaz, 1'e ulaşana kadar 2'ye böleriz. 2'nin ilk gücünü en yakın pow2 dizisinde arayalım (bu da Collatz Mesafesini kullanarak 2'ye en yakın güçtür). Verilen aralık (51-562) için, mümkün olan en yakın pow2 numaraları şunlardır: [16, 64, 128, 256, 512, 1024]

Kısa versiyon

Algoritma şunları gerçekleştirir:

  • Mevcut sayıya en yakın 2 gücünü bulmak için bir ikili arama
  • hedef sayı bulunana kadar dizideki önceki her öğeyi bulmak için doğrusal bir arama.

Ayrıntılı sürüm

Hedef sayıya sahip bir oyun verildiğinde rstrateji şu şekildedir:

  1. rMümkün olduğunca az adımda en yakın 2'nin gücünü bulmak için ikili aramayı kullanın .
  2. Bulunan 2'nin en yakın gücü çözüm ise, durdurun. Aksi takdirde 3'e devam edin.
  3. Bulunan 2'nin gücü, dizide ortaya çıkan 2'nin ilk gücü olduğundan, bunu takip ederse, bu değere (* 3 + 1) işlemle ulaşılır. (Bir / 2 işleminden sonra geldiyse, önceki sayı 2'nin de gücü olurdu). Ters işlemi yaparak önceki sayıyı önceki sayıyı hesapla (-1 ve sonra / 3)
  4. Bu sayı hedefse, durun. Aksi takdirde 5'e devam edin.
  5. Sekanstan bilinen mevcut sayı göz önüne alındığında, geri dönüp sekanstaki önceki sayıyı keşfetmek gerekir. Mevcut sayıya bir (/ 2) veya (* 3 +1) işlemiyle ulaşılıp ulaşılmadığı bilinmemektedir, bu nedenle algoritma her ikisini de dener ve hangisinin hedeften daha yakın bir sayı (Collatz Mesafesi olarak) verdiğini görür .
  6. Yeni keşfedilen numara doğru numaraysa durdurun.
  7. Yeni keşfedilen numarayı kullanarak 5. adıma geri dönün.

Sonuçlar

51-562 aralığındaki tüm sayılar için algoritmanın çalıştırılması normal bir bilgisayarda yaklaşık bir saniye sürer ve toplam puan 38665'tir.

Kod

Çevrimiçi deneyin!

require 'set'

# Utility methods
def collatz(n)
  [].tap do |results|
    crt = n
    while true
      results << crt
      break if crt == 1
      crt = crt.even? ? crt / 2 : crt * 3 + 1
    end
  end
end

def collatz_dist(m, n)
  cm = collatz(m).reverse
  cn = collatz(n).reverse
  common_length = cm.zip(cn).count{ |x, y| x == y }
  cm.size + cn.size - common_length * 2
end



GuessResult = Struct.new :response, :score
# Class that can "play" a game, responding
# :right, :closer, :farther or :same when
# presented with a guess
class Game

  def initialize(target_value)
    @r = target_value
    @score = -1
    @dist = nil
    @won = false
  end
  attr_reader :score

  def guess(n)
    # just a logging decorator over the real method
    result = internal_guess(n)
    p [n, result] if LOGGING
    result
  end

  private

  def internal_guess(n)
    raise 'Game already won!' if @won
    @score += 1
    dist = collatz_dist(n, @r)
    if n == @r
      @won = true
      return GuessResult.new(:right, @score)
    end
    response = nil
    if @dist
      response = [:same, :closer, :farther][@dist <=> dist]
    end
    @dist = dist
    GuessResult.new(response)
  end

end

# Main solver code

def solve(game)
  pow2, won = find_closest_power_of_2(game)
  puts "Closest pow2: #{pow2}" if LOGGING

  return pow2 if won
  # Since this is the first power of 2 in the series, it follows that
  # this element must have been arrived at by doing *3+1...
  prev = (pow2 - 1) / 3
  guess = game.guess(prev)
  return prev if guess.response == :right

  solve_backward(game, prev, 300)
end

def solve_backward(game, n, left)
  return 0 if left == 0
  puts "***      Arrived at  ** #{n} **" if LOGGING
  # try to see whether this point was reached by dividing by 2
  double = n * 2
  guess = game.guess(double)
  return double if guess.response == :right

  if guess.response == :farther && (n - 1) % 3 == 0
    # try to see whether this point was reached by *3+1
    third = (n-1) / 3
    guess = game.guess(third)
    return third if guess.response == :right
    if guess.response == :closer
      return solve_backward(game, third, left-1)
    else
      game.guess(n) # reset to n...
    end
  end
  return solve_backward(game, double, left-1)
end


# Every Collatz Sequence ends with a sequence of powers of 2.
# Let's call the first occurring power of 2 in such a sequence
# POW2
#
# Let's iterate through the whole range and find the POW2_CANDIDATES
#
RANGE = [*51..562]
POWERS = Set.new([*0..15].map{ |n| 2 ** n })

POW2_CANDIDATES =
  RANGE.map{ |n| collatz(n).find{ |x| POWERS.include? x} }.uniq.sort
# Turns out that the candidates are [16, 64, 128, 256, 512, 1024]

def find_closest_power_of_2(game)
  min = old_guess = 0
  max = new_guess = POW2_CANDIDATES.size - 1
  guess = game.guess(POW2_CANDIDATES[old_guess])
  return POW2_CANDIDATES[old_guess], true if guess.response == :right
  guess = game.guess(POW2_CANDIDATES[new_guess])
  return POW2_CANDIDATES[new_guess], true if guess.response == :right
  pow2 = nil

  while pow2.nil?

    avg = (old_guess + new_guess) / 2.0

    case guess.response
    when :same
      # at equal distance from the two ends
      pow2 = POW2_CANDIDATES[avg.floor]
      # still need to test if this is correct
      guess = game.guess(pow2)
      return pow2, guess.response == :right
    when :closer
      if old_guess < new_guess
        min = avg.ceil
      else
        max = avg.floor
      end
    when :farther
      if old_guess < new_guess
        max = avg.floor
      else
        min = avg.ceil
      end
    end

    old_guess = new_guess
    new_guess = (min + max) / 2
    new_guess = new_guess + 1 if new_guess == old_guess
    # so we get next result relative to the closer one
    # game.guess(POW2_CANDIDATES[old_guess]) if guess.response == :farther
    guess = game.guess(POW2_CANDIDATES[new_guess])

    if guess.response == :right
      pow2 = POW2_CANDIDATES[new_guess]
      break
    end

    if min == max
      pow2 = POW2_CANDIDATES[min]
      break
    end

  end

  [pow2, guess.response == :right]

end



LOGGING = false

total_score = 0
51.upto(562) do |n|
  game = Game.new(n)
  result = solve(game)
  raise "Incorrect result for #{n} !!!" unless result == n
  total_score += game.score
end
puts "Total score: #{total_score}"

Etkileyici. Küçük bir nokta var: Yorumlardan birinin "mükemmel kare" dememesi gerektiğine inanıyorum.
Weijun Zhou

1
@WeijunZhou Haklısın. Sabit!
Cristian Lupascu

Muhtemelen en kötü skoru 196 olan dahil
etmelisiniz

3

En kötü skor: 11, toplam puan: 3986

Tüm tahminler aralıklıdır [51,562].

Algoritmam:

  1. İlk kez 512 tahmin edin ve bir dizi olası sonucu koruyun vals, başlangıçta küme aralıktaki tüm sayıları içerir [51,562].
  2. Her adımda aşağıdakileri yapın:

    1. Bir sonraki tahmin değerini bulmak guessaralığında [51,562]değerleri bu şekilde, vals(hariç guesskendini) olası sonuçları tekabül eden 3 gruba bölünür Closer, Sameve Fartherbu 3 set maksimum boyutu minimumdur. Yukarıdakileri karşılamak için
      birden fazla olası değer varsa guess, en küçük olanı seçin.
    2. Değeri tahmin guess.
    3. Cevap "Doğru" ise yapılır (programdan çıkın).
    4. Kümedeki tüm değerleri, valsbu sonucu veremeyecekleri şekilde kaldırın .

C ++ ve Bash ile yazılmış referans uygulamam makinemde yaklaşık 7.6 saniye içinde çalışıyor ve başlıkta açıklandığı gibi en kötü puan / toplam puanını veriyor.

Tüm olası ilk tahmin değerlerinin denenmesi, makinemde yaklaşık 1,5 saat sürecek. Bunu yapmayı düşünebilirim.


(P / S: Kod olmayan gönderimlere izin verilir.
Puanıma

Ancak , bazı nedenlerden dolayı yeniden uygulamadan gerçekten çalıştığını görmek istiyorsanız, çevrimiçi deneyin !
user202729

Bir dakika, neden sadece benim program bir karar ağacı çıktı ve puan için izin veremem: | çok daha hızlı olurdu ...
user202729
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.