I wrote a program for the game. Of course, I had to automate the human side, but I believe that I did all this in such a way that I did not violate my approach when I played against a real person.
I approached this from the point of view of machine learning and saw the problem as a hidden Markov model, where the total price was an observation. My solution is to use a particle filter. This solution is written in Python 2.7 using NumPy and SciPy.
I outlined any assumptions made explicitly in the comments or implicitly in the code. I also set some additional restrictions in order to run the code in automatic mode. This was not particularly optimized, as I tried to err on the side of understanding, not speed.
Each iteration displays the current true values and fortune telling. I just pass the output to a file so that I can easily view it. An interesting continuation will be the construction of the output on the chart either 2D (for 2 fruits) or 3D (for 3 fruits). Then you can see how the particle filter is honed in the solution.
Update:
Edited the code to include updated settings after configuration. Calls using matplotlib (via pylab) are included. Planning runs on Linux-Gnome; your mileage may vary. By default, NUM_FRUITS is 2 for building support. Just comment out all pylab calls to delete the schedule and you can change NUM_FRUITS to something.
Is it good at evaluating the current fxn represented by UnknownQuantities X Prices = TotalPrice. In 2D (2 Fruits) this is a line, in 3D (3 Fruits) it would be a plane. There seems to be too little data for a particle filter to reliably hone the right amounts. You need a few more emoticons on top of the particle filter to really collect historical information. 2- 3- .
2:
. , ( ).
Changes:
, . , - , . .
, - . , ( , ). , . (Plotting 64- Win 7).
/ . , "" .
, , , , . CHANGE_QUANTITIES . , 2 CHANGE_QUANTITIES. , , , , .
, CHANGE_QUANTITIES, MAX_QUANTITY_CHANGE (.001) "" (.05).
, , - ( ) . , , , .
, .
from __future__ import division import random import numpy import scipy.stats import pylab # Assume Guesser knows prices and total # Guesser must determine the quantities # All of pylab is just for graphing, comment out if undesired # Graphing only graphs first 2 FRUITS (first 2 dimensions) NUM_FRUITS = 3 MAX_QUANTITY_CHANGE = .01 # Maximum percentage change that total quantity of fruit can change per iteration MAX_QUANTITY = 100 # Bound for the sake of instantiating variables MIN_QUANTITY_TOTAL = 10 # Prevent degenerate conditions where quantities all hit 0 MAX_FRUIT_PRICE = 1000 # Bound for the sake of instantiating variables NUM_PARTICLES = 5000 NEW_PARTICLES = 500 # Num new particles to introduce each iteration after guessing NUM_ITERATIONS = 20 # Max iterations to run CHANGE_QUANTITIES = True CHANGE_PRICES = True ''' Change individual fruit quantities for a random amount of time Never exceed changing fruit quantity by more than MAX_QUANTITY_CHANGE ''' def updateQuantities(quantities): old_total = max(sum(quantities), MIN_QUANTITY_TOTAL) new_total = old_total max_change = int(old_total * MAX_QUANTITY_CHANGE) while random.random() > .005: # Stop Randomly change_index = random.randint(0, len(quantities)-1) change_val = random.randint(-1*max_change,max_change) if quantities[change_index] + change_val >= 0: # Prevent negative quantities quantities[change_index] += change_val new_total += change_val if abs((new_total / old_total) - 1) > MAX_QUANTITY_CHANGE: quantities[change_index] -= change_val # Reverse the change def totalPrice(prices, quantities): return sum(prices*quantities) def sampleParticleSet(particles, fruit_prices, current_total, num_to_sample): # Assign weight to each particle using observation (observation is current_total) # Weight is the probability of that particle (guess) given the current observation # Determined by looking up the distance from the hyperplane (line, plane, hyperplane) in a # probability density fxn for a normal distribution centered at 0 variance = 2 distances_to_current_hyperplane = [abs(numpy.dot(particle, fruit_prices)-current_total)/numpy.linalg.norm(fruit_prices) for particle in particles] weights = numpy.array([scipy.stats.norm.pdf(distances_to_current_hyperplane[p], 0, variance) for p in range(0,NUM_PARTICLES)]) weight_sum = sum(weights) # No need to normalize, as relative weights are fine, so just sample un-normalized # Create new particle set weighted by weights belief_particles = [] belief_weights = [] for p in range(0, num_to_sample): sample = random.uniform(0, weight_sum) # sum across weights until we exceed our sample, the weight we just summed is the index of the particle we'll use p_sum = 0 p_i = -1 while p_sum < sample: p_i += 1 p_sum += weights[p_i] belief_particles.append(particles[p_i]) belief_weights.append(weights[p_i]) return belief_particles, numpy.array(belief_weights) ''' Generates new particles around the equation of the current prices and total (better particle generation than uniformly random) ''' def generateNewParticles(current_total, fruit_prices, num_to_generate): new_particles = [] max_values = [int(current_total/fruit_prices[n]) for n in range(0,NUM_FRUITS)] for p in range(0, num_to_generate): new_particle = numpy.array([random.uniform(1,max_values[n]) for n in range(0,NUM_FRUITS)]) new_particle[-1] = (current_total - sum([new_particle[i]*fruit_prices[i] for i in range(0, NUM_FRUITS-1)])) / fruit_prices[-1] new_particles.append(new_particle) return new_particles # Initialize our data structures: # Represents users first round of quantity selection fruit_prices = numpy.array([random.randint(1,MAX_FRUIT_PRICE) for n in range(0,NUM_FRUITS)]) fruit_quantities = numpy.array([random.randint(1,MAX_QUANTITY) for n in range(0,NUM_FRUITS)]) current_total = totalPrice(fruit_prices, fruit_quantities) success = False particles = generateNewParticles(current_total, fruit_prices, NUM_PARTICLES) #[numpy.array([random.randint(1,MAX_QUANTITY) for n in range(0,NUM_FRUITS)]) for p in range(0,NUM_PARTICLES)] guess = numpy.average(particles, axis=0) guess = numpy.array([int(round(guess[n])) for n in range(0,NUM_FRUITS)]) print "Truth:", str(fruit_quantities) print "Guess:", str(guess) pylab.ion() pylab.draw() pylab.scatter([p[0] for p in particles], [p[1] for p in particles]) pylab.scatter([fruit_quantities[0]], [fruit_quantities[1]], s=150, c='g', marker='s') pylab.scatter([guess[0]], [guess[1]], s=150, c='r', marker='s') pylab.xlim(0, MAX_QUANTITY) pylab.ylim(0, MAX_QUANTITY) pylab.draw() if not (guess == fruit_quantities).all(): for i in range(0,NUM_ITERATIONS): print "------------------------", i if CHANGE_PRICES: fruit_prices = numpy.array([random.randint(1,MAX_FRUIT_PRICE) for n in range(0,NUM_FRUITS)]) if CHANGE_QUANTITIES: updateQuantities(fruit_quantities) map(updateQuantities, particles) # Particle Filter Prediction print "Truth:", str(fruit_quantities) current_total = totalPrice(fruit_prices, fruit_quantities) # Guesser Turn - Particle Filter: # Prediction done above if CHANGE_QUANTITIES is True # Update belief_particles, belief_weights = sampleParticleSet(particles, fruit_prices, current_total, NUM_PARTICLES-NEW_PARTICLES) new_particles = generateNewParticles(current_total, fruit_prices, NEW_PARTICLES) # Make a guess: guess = numpy.average(belief_particles, axis=0, weights=belief_weights) # Could optimize here by removing outliers or try using median guess = numpy.array([int(round(guess[n])) for n in range(0,NUM_FRUITS)]) # convert to integers print "Guess:", str(guess) pylab.cla() #pylab.scatter([p[0] for p in new_particles], [p[1] for p in new_particles], c='y') # Plot new particles pylab.scatter([p[0] for p in belief_particles], [p[1] for p in belief_particles], s=belief_weights*50) # Plot current particles pylab.scatter([fruit_quantities[0]], [fruit_quantities[1]], s=150, c='g', marker='s') # Plot truth pylab.scatter([guess[0]], [guess[1]], s=150, c='r', marker='s') # Plot current guess pylab.xlim(0, MAX_QUANTITY) pylab.ylim(0, MAX_QUANTITY) pylab.draw() if (guess == fruit_quantities).all(): success = True break # Attach new particles to existing particles for next run: belief_particles.extend(new_particles) particles = belief_particles else: success = True if success: print "Correct Quantities guessed" else: print "Unable to get correct answer within", NUM_ITERATIONS, "iterations" pylab.ioff() pylab.show()