Collatz 스타일의 계란 사냥


11

위대한 API 부활절 달걀 사냥에서 영감을 !

요약

귀하의 작업은 가능한 가장 적은 단계를 사용하여 "콜라 츠 공간"(후술 할 설명)에서 미리 결정된 정수를 검색하는 것입니다.

소개

이 도전은 적어도 여기에있는 모든 사람들이 들어 본 유명한 Collatz 추측에 근거합니다. 다음은 Super Collatz 번호 인쇄에 대한 요약 입니다 .

Collatz 시퀀스 는, 양의 정수로 시작이 예를 들어 우리가 10를 사용하고, 그것에 단계의 설정을 적용 할 경우 (도 3 배 + 1 문제라고합니다)입니다 :

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

Collatz 거리 C(m,n)두 숫자 사이 mn이러한 문제의 목적은, 상기의 두 숫자 사이의 거리 Collatz 그래프 (하여 다음과 같이 정의된다 (이 개념에 대해 말해위한 @tsh 크레딧) 2113예로서 ) :

Collatz 시퀀스를 기록하십시오 m(이 경우 21) :

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

Collatz 시퀀스를 기록하십시오 n(이 경우 13) :

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

이제 시퀀스 중 하나에 만 숫자가 나타나는지 계산하십시오. 이것은 Collatz 사이의 거리로 정의 m하고 n. 이 경우, 8

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

Collatz 거리 2113as 사이의 거리가 있습니다 C(21,13)=8.

C(m,n) 다음과 같은 멋진 속성이 있습니다.

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

바라건대 정의 C(m,n)가 이제 명확 해졌습니다. Collatz 공간에서 계란 사냥을 시작합시다!

간격의 정수 : 게임이 시작될 때, 제어기는 하나의 차원 좌표로 표현되는 이스터의 위치 결정 [p,q](즉, 사이의 정수 pq, 양단 값 포함).

계란의 위치는 게임 내내 일정하게 유지됩니다. 이 좌표를로 표시합니다 r.

이제 초기 추측을 0으로 만들 수 있으며 컨트롤러에 의해 기록됩니다. 이것은 당신의 0 라운드입니다. 운이 좋으면 처음에 바로 얻었을 때 (예 : 0 = r) 게임이 끝나고 점수는 0( 점수가 낮을수록 좋습니다). 그렇지 않으면, 당신은 첫 번째 라운드를 입력하고 당신은 새로운 추측을 1 1 , 이것은 당신이 바로 그것을 얻을 때까지 계속됩니다, 즉 n = r, 그리고 점수는 것 n입니다.

0 일 이후 각 라운드에 대해 컨트롤러는 다음 피드백 중 하나를 제공하므로 주어진 정보를 기반으로 더 나은 추측을 할 수 있습니다. 당신이 현재 세 n번째 라운드에 있다고 가정 하고 추측은 n입니다

  • "당신은 그것을 발견했다!" 경우 N 게임 종료하고 점수를하는 경우 = R, n.
  • C (a n , r) <C (a n-1 , r) 인 경우 "더 가까워요 :)"
  • C (a n , r) = C (a n-1 , r) 인 경우 "계란 주위를 돌고 있습니다"
  • "당신이 더 먼 :("C (a n , r)> C (a n-1 , r)

바이트를 절약하기 위해 위에서 제시 한 순서대로 응답을 "Right", "Closer", "Same", "Farther"라고합니다.

의 게임 예는 다음과 같습니다 p=1,q=15.

  • a 0 = 10
  • a 1 = 11, 응답 : "가까이"
  • a 2 = 13, 응답 : "Farther"
  • a 3 = 4, 응답 : "Farther"
  • a 4 = 3, 응답 : "가까이"
  • a 5 = 5, 응답 : "동일"
  • a 6 = 7, 응답 : "오른쪽"

점수 : 6.

도전

최고의 점수로 게임을 플레이 할 결정적인 전략을 설계하십시오 p=51, q=562.

답은 알고리즘을 자세하게 설명해야합니다. 알고리즘을 설명하는 데 도움이되는 코드를 첨부 할 수 있습니다. 이것은 코드 골프가 아니므로 읽을 수있는 코드를 작성하는 것이 좋습니다.

답은 가능한 모든 경우에 달성 할 수있는 최악의 점수와 최악의 점수가 가장 높은 점수를 포함해야합니다 r. 동점 인 경우 가능한 모든 rs에 대한 평균 점수가 더 높은 알고리즘 (답변에 포함되어야 함)이 승리합니다. 더 이상의 타이 브레이커가 없으며 결국 여러 개의 승자가있을 수 있습니다.

명세서

  • 반복하려면 r간격에 [51,562]있습니다.
  • 기본 허점이 적용됩니다.

현상금 (첫 번째 답변이 게시 된 후 추가됨)

나는 개인적으로 모든 추측이 범위 내에서 이루어지고 [51,562]여전히 최악의 점수를 얻었을 때 현상금을 제공 할 수 있습니다 .


컨트롤러가 있습니까?
user202729

원래 질문의 것과 같은 것은 아닙니다.
Weijun Zhou

1
C (m, n)은 Collatz 그래프 에서 m, n의 거리입니다 .
tsh

나는 개념을 스스로 생각해 내었고 Collatz 그래프를 몰랐다. 말해줘서 고마워 질문에 정보를 포함시킬 것입니다.
Weijun Zhou

답변:


5

루비, 196

처음에 생각했던 것보다 더 힘들었습니다. 나는 많은 모호한 사례를 처리해야했고 많은 추악한 코드로 끝났습니다. 그러나 많은 재미이었다! :)

전략

모든 Collatz 시퀀스는 2의 거듭 제곱 시퀀스로 끝납니다 (예 : [16, 8, 4, 2, 1]). 2의 거듭 제곱이 발생하자마자, 우리는 1에 도달 할 때까지 2로 나눕니다. 가장 가까운 pow2 순서로 2의 첫 번째 거듭 제곱을 호출합시다 (이는 Collatz Distance를 사용하여 숫자에 가장 가까운 2의 거듭 제곱이기 때문에). 주어진 범위 (51-562)에서 가능한 가장 가까운 pow2 숫자는 다음과 같습니다. [16, 64, 128, 256, 512, 1024]

짧은 버전

알고리즘은 다음을 수행합니다.

  • 현재 수에 가장 가까운 2의 거듭 제곱을 구하는 이진 검색
  • 대상 번호가 검색 될 때까지 시퀀스의 모든 이전 요소를 파악하기위한 선형 검색

상세 버전

대상 번호가있는 게임이 주어지면 r전략은 다음과 같습니다.

  1. 이진 검색을 사용 r하여 가능한 한 적은 단계로 가장 가까운 2의 거듭 제곱을 구 하십시오.
  2. 찾은 가장 가까운 2의 거듭 제곱이 해결책이면, 중지하십시오. 그렇지 않으면 계속 3으로 진행하십시오.
  3. 발견 된 2의 거듭 제곱은 시퀀스에서 발생하는 첫 번째 2의 거듭 제곱이기 때문에 (* 3 + 1) 연산을 수행하여 해당 값에 도달했습니다. (/ 2 작업 후에 온 경우 이전 숫자도 2의 거듭 제곱이됩니다). 역 동작을 수행하여 순서에서 이전 숫자를 계산합니다 (-1 및 / 3).
  4. 해당 숫자가 목표이면 중지하십시오. 그렇지 않으면 계속 5로 진행하십시오.
  5. 시퀀스에서 알려진 현재 번호가 주어지면 돌아가서 시퀀스에서 이전 번호를 찾아야합니다. 현재 숫자가 (/ 2) 또는 (* 3 +1) 연산에 의해 도달했는지 여부를 알 수 없으므로 알고리즘은 두 숫자를 모두 시도하고 어느 것이 목표에서 더 가까운 숫자 (콜라 츠 거리)를 산출하는지 확인합니다 .
  6. 새로 발견 된 번호가 올바른 경우 중지하십시오.
  7. 새로 발견 된 번호를 사용하여 5 단계로 돌아가십시오.

결과

51-562 범위의 모든 숫자에 대해 알고리즘을 실행하면 일반 PC에서 약 1 초가 걸리며 총 점수는 38665입니다.

코드

온라인으로 사용해보십시오!

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}"

감동적인. 사소한 점이 있습니다. 의견 중 하나가 "완전한 사각형"이라고 말해서는 안된다고 생각합니다.
Zhou

1
@WeijunZhou 당신이 맞아요. 결정된!
Cristian Lupascu

모든 경우에 대해 최악의 점수 인 196을 포함해야 할 것입니다.
Weijun Zhou

3

최악의 점수 : 11, 합계 점수 : 3986

모든 추측은 범위 내에 있습니다 [51,562].

내 알고리즘 :

  1. 처음으로 512를 추측하고 가능한 결과 세트를 유지하십시오 vals. 처음에는 세트에 범위의 모든 숫자가 포함됩니다 [51,562].
  2. 각 단계에서 다음을 수행하십시오.

    1. 다음의 추측 값을 찾아서 guess범위 [51,562]의 값이되도록, vals(제외한 guess자체) 가능한 결과에 대응하는 3 개 세트로 분할되고 Closer, Same그리고 Farther, 이들 3 개 세트의 최대 크기는 최소이다. 위
      guess조건 을 만족하는 여러 가능한 값이 있는 경우 가장 작은 값을 선택하십시오.
    2. 가치를 생각한다 guess.
    3. 대답이 "오른쪽"이면 완료됩니다 (프로그램을 종료하십시오).
    4. vals해당 결과를 제공 할 수 없도록 세트의 모든 값을 제거하십시오 .

C ++ 및 Bash로 작성된 참조 구현은 내 컴퓨터에서 약 7.6 초 내에 실행되며 제목에 설명 된대로 최악의 점수 / 합계 점수를 제공합니다.

가능한 모든 첫 번째 추측 값을 시도하면 내 컴퓨터에서 약 1.5 시간이 걸립니다. 나는 그것을 고려할 수 있습니다.


(P / S가 : 비 코드 제출이 허용됩니다 그냥 스스로를 구현하고 참조, 내 점수를 신뢰하지 않는 경우.)
user202729

당신은하지만 실제로 는 몇 가지 이유를 다시 구현하지 않고 작업을보고 싶어, 온라인으로보십시오 !
user202729

프로그램에서 의사 결정 트리를 출력하고 점수를 매기도록 할 수없는 이유를 잠시 기다려주십시오. | 그것은 ... 훨씬 빠른 것
user202729
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.