Berburu telur dengan gaya Collatz

11

Terinspirasi oleh The Great API Easter Egg Hunt!

Ringkasan

Tugas Anda adalah mencari integer yang telah ditentukan di "ruang Collatz" (akan dijelaskan nanti) menggunakan langkah sesedikit mungkin.

pengantar

Tantangan ini didasarkan pada dugaan Collatz yang terkenal, semoga semua orang di sini setidaknya mendengar. Ini adalah rekap yang diambil dari Mencetak nomor Super Collatz .

The Collatz Urutan (juga disebut 3x + 1 masalah) adalah di mana Anda mulai dengan bilangan bulat positif, untuk contoh ini kita akan menggunakan 10, dan menerapkan set ini langkah-langkah untuk itu:

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

Jarak Collatz C(m,n)antara dua angka mdan n, untuk tujuan tantangan ini, adalah jarak antara dua angka dalam grafik Collatz (Kredit ke @tsh untuk memberi tahu saya tentang konsep ini), yang didefinisikan sebagai berikut: (menggunakan 21dan 13sebagai contoh ):

Tuliskan urutan Collatz untuk m(dalam hal ini, 21):

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

Tuliskan urutan Collatz untuk n(dalam hal ini, 13):

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

Sekarang hitung berapa banyak angka yang muncul hanya di salah satu urutan. Ini didefinisikan sebagai jarak Collatz antara mdan n. Dalam hal ini 8, yaitu,

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

Jadi kami memiliki jarak Collatz antara 21dan 13sebagai C(21,13)=8.

C(m,n) memiliki sifat-sifat baik berikut:

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

Semoga definisi C(m,n)sekarang jelas. Ayo mulai berburu telur di ruang Collatz!

Pada awal permainan, pengontrol memutuskan posisi telur paskah, yang diekspresikan oleh koordinat satu dimensi: Bilangan bulat dalam interval [p,q](dengan kata lain, bilangan bulat antara pdan q, kedua ujungnya inklusif).

Posisi telur tetap konstan sepanjang permainan. Kami akan menyatakan koordinat ini sebagai r.

Anda sekarang dapat membuat tebakan awal 0 , dan itu akan direkam oleh pengontrol. Ini adalah ronde 0 Anda. Jika Anda sangat beruntung bahwa Anda melakukannya dengan benar di tempat pertama (yaitu 0 = r), permainan berakhir, dan skor Anda adalah 0(Semakin rendah skor, semakin baik). Kalau tidak, Anda memasuki ronde 1 dan Anda membuat tebakan baru 1 , ini berlangsung sampai Anda benar, yaitu n = r, dan skor Anda akan menjadi n.

Untuk setiap putaran setelah 0, controller memberi Anda salah satu dari umpan balik berikut sehingga Anda dapat membuat tebakan yang lebih baik berdasarkan informasi yang diberikan. Mari kita asumsikan Anda saat ini di nputaran ke - th dan karenanya tebakan Anda adalah n

  • "Kamu menemukannya!" jika a n = r, dalam hal ini pertandingan berakhir dan Anda mencetak skor n.
  • "Kamu lebih dekat :)" jika C (a n , r) <C (a n-1 , r)
  • "Anda berputar-putar di sekitar telur" jika C (a n , r) = C (a n-1 , r)
  • "Anda lebih jauh :(" jika C (a n , r)> C (a n-1 , r)

Untuk menyimpan beberapa byte, saya akan memanggil respons sebagai "Benar", "Lebih Dekat", "Sama", "Lebih Jauh", dalam urutan yang disajikan di atas.

Berikut adalah contoh game dengan p=1,q=15.

  • a 0 = 10
  • a 1 = 11, respons: "Closer"
  • a 2 = 13, respons: "Lebih jauh"
  • a 3 = 4, respons: "Lebih jauh"
  • a 4 = 3, respons: "Closer"
  • a 5 = 5, respons: "Sama"
  • a 6 = 7, respons: "Benar"

Skor: 6.

Tantangan

Rancang strategi deterministik untuk bermain game p=51, q=562dengan skor terbaik.

Jawaban harus menjelaskan algoritme secara rinci. Anda dapat melampirkan kode apa pun yang membantu menjelaskan algoritma. Ini bukan codegolf sehingga Anda dianjurkan untuk menulis kode yang dapat dibaca.

Jawaban harus mencakup skor terburuk yang mungkin mereka raih untuk semua kasus yang mungkin terjadi r, dan yang dengan skor terburuk terendah menang. Dalam kasus seri, algoritma yang memiliki skor rata-rata yang lebih baik untuk semua kemungkinan r(yang juga harus dimasukkan dalam jawaban) menang. Tidak ada pemutus dasi lebih lanjut dan kami mungkin memiliki beberapa pemenang pada akhirnya.

Spesifikasi

  • Untuk mengulangi, rterletak pada interval [51,562].
  • Celah default berlaku.

Bounty (Ditambahkan setelah jawaban pertama diposting)

Saya pribadi dapat menawarkan hadiah untuk jawaban di mana semua tebakan dibuat dalam kisaran [51,562]sementara masih memiliki skor terburuk yang cukup rendah.

Weijun Zhou
sumber
Apakah Anda memiliki pengontrol?
user202729
Tidak ada yang seperti yang ada di pertanyaan awal.
Weijun Zhou
1
C (m, n) adalah jarak m, n pada grafik Collatz .
tsh
Saya datang dengan konsep sendiri dan tidak tahu grafik Collatz. Terima kasih telah memberitahuku hal itu. Saya akan memasukkan informasi dalam pertanyaan.
Weijun Zhou

Jawaban:

5

Ruby, 196

Ini jauh lebih sulit daripada yang saya pikirkan. Saya harus menangani banyak kasus yang tidak jelas dan berakhir dengan banyak kode jelek. Tapi itu sangat menyenangkan! :)

Strategi

Setiap Urutan Collatz berakhir dengan urutan kekuatan 2 (mis: [16, 8, 4, 2, 1]). Segera setelah kekuatan 2 ditemukan, kita membaginya dengan 2 sampai kita mencapai 1. Mari kita sebut kekuatan pertama 2 dalam urutan pow2 terdekat (karena ini juga merupakan kekuatan terdekat 2 untuk nomor kita menggunakan Jarak Collatz). Untuk rentang yang diberikan (51-562), semua angka pow2 terdekat yang mungkin adalah: [16, 64, 128, 256, 512, 1024]

Versi pendek

Algoritma melakukan:

  • pencarian biner untuk mengetahui kekuatan terdekat 2 ke angka saat ini
  • pencarian linear untuk mengetahui setiap elemen sebelumnya dalam urutan sampai nomor target ditemukan.

Versi terperinci

Diberikan permainan dengan nomor target r, strateginya adalah sebagai berikut:

  1. Gunakan pencarian biner untuk mengetahui kekuatan 2 yang paling dekat dengan rbeberapa langkah mungkin.
  2. Jika kekuatan terdekat 2 yang ditemukan adalah solusinya, berhenti. Kalau tidak, lanjutkan ke 3.
  3. Karena kekuatan 2 yang ditemukan adalah kekuatan pertama 2 yang muncul dalam urutan, jika mengikuti nilai tersebut tercapai dengan melakukan operasi (* 3 + 1). (Jika itu datang setelah operasi / 2, maka angka sebelumnya juga akan menjadi kekuatan 2). Hitung angka sebelumnya dalam urutan dengan melakukan operasi terbalik (-1 dan kemudian / 3)
  4. Jika angka itu adalah target, hentikan. Kalau tidak, lanjutkan ke 5.
  5. Mengingat nomor saat ini diketahui dari urutan, diperlukan untuk kembali dan menemukan nomor sebelumnya dalam urutan. Tidak diketahui apakah angka saat ini tiba dengan operasi (/ 2) atau (* 3 +1), sehingga algoritma mencoba keduanya dan melihat mana yang menghasilkan angka yang lebih dekat (seperti Jarak Collatz) dari target .
  6. Jika nomor yang baru ditemukan adalah yang benar, berhentilah.
  7. Menggunakan nomor yang baru ditemukan, kembali ke langkah 5.

Hasil

Menjalankan algoritma untuk semua angka dalam kisaran 51-562 membutuhkan waktu sekitar satu detik pada PC normal dan skor totalnya adalah 38665.

Kode

Cobalah online!

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}"
Cristian Lupascu
sumber
Impresif. Ada poin kecil: Saya percaya salah satu komentar tidak boleh mengatakan "kuadrat sempurna".
Weijun Zhou
1
@ WeijunZhou Anda benar. Tetap!
Cristian Lupascu
Anda mungkin harus memasukkan skor terburuk untuk semua kasus, yaitu 196.
Weijun Zhou
3

skor terburuk: 11, jumlah skor: 3986

Semua tebakan berada dalam jangkauan [51,562].

Algoritme saya:

  1. Pertama kali menebak 512, dan memelihara satu set hasil yang mungkin vals, awalnya set berisi semua angka dalam jangkauan [51,562].
  2. Di setiap langkah, lakukan hal berikut:

    1. Cari nilai menebak berikutnya guessdi kisaran [51,562]sehingga, ketika nilai-nilai dalam vals(tidak termasuk guesssendiri) dibagi menjadi 3 set yang sesuai dengan hasil yang mungkin Closer, Samedan Farther, ukuran maksimum yang 3 set minimum.
      Jika ada beberapa kemungkinan nilai guessmemuaskan di atas, pilih yang terkecil.
    2. Tebak nilainya guess.
    3. Jika jawabannya "Benar", dilakukan (keluar dari program).
    4. Hapus semua nilai di set valssehingga mereka tidak bisa memberikan hasil itu.

Implementasi referensi saya yang ditulis dalam C ++ dan Bash berjalan dalam waktu sekitar 7,6 detik pada mesin saya dan memberikan skor / jumlah skor terburuk seperti yang dijelaskan dalam judul.

Mencoba semua nilai tebakan pertama yang mungkin akan memakan waktu sekitar 1,5 jam di mesin saya. Saya mungkin mempertimbangkan untuk melakukan itu.

pengguna202729
sumber
(P / S: Pengajuan non-kode diperbolehkan. Jika Anda tidak mempercayai skor saya, cukup implementasikan sendiri dan lihat)
user202729
Tetapi jika Anda benar - benar ingin melihatnya berfungsi tanpa mengimplementasikannya untuk beberapa alasan, Cobalah secara online !
user202729
Tunggu sebentar, mengapa saya tidak bisa membiarkan program saya mengeluarkan pohon keputusan dan menilai: | itu akan jauh lebih cepat ...
user202729