Below is a python program that solves the puzzle, complete with comments to explain.
#!/usr/bin/python -OOtt

# A mathematical and logical puzzle.

# What we know:

# * There are two numbers

# * They are both positive integers

# * They are both different from each other

# * They are both larger than one

# * The sum of the two numbers is less than or equal to 50

#

# The product of the numbers is given to one Logician (who we

# will call 'P'). The sum of the numbers is given to another

# Logician (who we will call 'S'). Both Logicians are aware

# of the same information as us, as presented above. P says

# "I don't know what the two numbers are."[1]. S responds with

# "Well, I *already* knew that."[2]. To which P replies "Well, in

# that case, I *do* know what the two numbers are."[3]. At that

# point S says "Oh, well, in that case, so do I."[4].

#

# The puzzle is, of course, to find the two numbers that fit the

# information we have been given, and which *also* cause the

# statements of the Logicians to be true.

#

# See below for a solution. When this program is executed with

# '50' as the first argument on the command line, it solves the

# puzzle as presented above for S <= 50 -- it returns the pair of

# numbers in question. This program can also be used to solve for

# higher values of S, eg 500, 2000, etc. Bear in mind that as the

# number increases, it is an O(N) increase in execution time.

# Solving for S <= 2000 took over six hours on my 2.2Ghz P4.

import sys

def findPrimePair(pairlist, primeLookupList, compLookupList):

for pair in pairlist:

if findPrime(pair[0], primeLookupList, compLookupList) and findPrime(pair[1], primeLookupList, compLookupList):

# In other words, both numbers are prime

return 1

return 0

def findPrime(x, primeLookupList, compLookupList):

#return [[y, x/y] for y in range(2, x+1) if (x % y == 0) and (y > 1) and (x/y > 1) and (x/y + y <= self.limit)]

# This is where the program spends most of its execution time; so we'll

# take every advantage and shortcut we can...

if x in compLookupList:

# this should be the bulk of all cases

return 0

elif x in primeLookupList:

return 1

elif x != 2 and not x % 2:

# shortcut

if x not in compLookupList:

compLookupList.append(x)

return 0

for i in range(2, x+1):

# longhand

if i * i >= x:

# we only care about numbers up to and including the

# sqrt of x...

for z in range(2, i+1):

if not x % z: # it divides cleanly

if x / z > 1:

# x isn't prime

if x not in compLookupList:

compLookupList.append(x)

return 0

if x not in primeLookupList:

primeLookupList.append(x) # x appears to be prime

return 1

LIMIT = int(sys.argv[1])

# First we'll generate a pool of raw data to work with:

dict = {}

dict['all_products'] = {}

for i in range(1, LIMIT+1):

i_name = str(i)

dict[i_name] = {}

dict[i_name]['addends'] = map(lambda y: [i-y, y], range(2, i-1))

# Or:

#dict[i_name]['addends'] = [[i-y, y] for y in range(2, i-1)]

# Clean up duplicate addends:

templist = []

for item in dict[i_name]['addends']:

item.sort()

if item not in templist:

templist.append(item)

dict[i_name]['addends'] = templist

for item in templist:

prod = item[0] * item[1]

dict['all_products'].setdefault(str(prod), [])

if item not in dict['all_products'][str(prod)]:

dict['all_products'].setdefault(str(prod), []).append(item)

# At this point we have all our raw data and it fits our rules;

# now we have to apply the logic to it.

P_sol = []

sumThreadList = []

Pair_list = []

primeLookupList = []

compLookupList = []

for prod in dict['all_products'].keys():

if len(dict['all_products'][prod]) > 1: # See note [1] below

# See notes [2] and [3] below

numpairs = len(dict['all_products'][prod])

prodThreadList = []

for factor_pair in dict['all_products'][prod]:

sum = factor_pair[0] + factor_pair[1]

ret = findPrimePair(dict[str(sum)]['addends'], primeLookupList, compLookupList)

if ret:

numpairs -= 1

else:

solpair = factor_pair

# anything with numpairs == 1 at this point would satisfy P

# above, but not necessarily S below

if numpairs == 1:

# See note [4] below

sum = solpair[0] + solpair[1]

ret = findPrimePair(dict[str(sum)]['addends'], primeLookupList, compLookupList)

if not ret:

sumThreadList.append(sum)

P_sol.append(prod) # We'll need this later

S_sol = []

# We have to spin this out as a separate loop as we must

# have a complete P_sol before we begin.

for sum in sumThreadList:

if sum not in S_sol:

# First time we've seen it:

S_sol.append(sum)

count = 0

soladd = None

for addend_pair in dict[str(sum)]['addends']:

if str(addend_pair[0] * addend_pair[1]) in P_sol:

count += 1

soladd = addend_pair

if count == 1: # Here's our baby...

print soladd

# [1] - For P to make this statement, his number must have more than

# one pair of qualifying factors, hence a valid product is one with

# more than one pair of factors whose sum is less than or equal to

# the LIMIT (not including 1 and itself) ergo:

# "if len(dict['all_products'][prod]) > 1:"

# [2] - S states that he already knows, therefore his number must not

# have any single pair of addends where both are prime. Why? Because

# if one pair of addends were both prime, if these happened to be the

# pair of numbers in question, then P would not have made statement [1];

# he would already have known what the answer was. To follow on from this:

# [3] - P then states that he knows the answer; therefore, only *one*

# of his pairs of qualifying factors must sum to a number with no pairs

# of primes amongst all of its addend pairs (ie, he correctly discerns

# point [2], above, and, inspecting all of his pairs of factors, discovers

# that only one of them generates case [2] above).

# [4] - For S to be able to state that he also knows, then S must

# have a number that only has one pair of addends that make

# point [3] true for P above (this is the mind-blowing one ;).

# Interestingly, for S <= 50 in the default puzzle, there are several

# pairs of numbers that generate true cases for everything up until

# this point...