POV-Ray : Newsgroups : povray.off-topic : Small math/programming problem Server Time
18 May 2024 11:32:12 EDT (-0400)
  Small math/programming problem (Message 16 to 25 of 25)  
<<< Previous 10 Messages Goto Initial 10 Messages
From: scott
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 02:10:10
Message: <4c2d82c2$1@news.povray.org>
>>> So the answer we seek is (approximately) 2↑↑↑2.
>>
>> Isn't that 4?
>
> No, since 2^^^2 = 2^^2^^2

I understood from wikipedia that 2^^^3 = 2^^2^^2 and 2^^^2 = 2^^2.  Is that 
wrong?


Post a reply to this message

From: scott
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 02:13:24
Message: <4c2d8384$1@news.povray.org>
>>> .....1,549,498,368
>>
>> I recognise those last 6 digits!  I think I got them from my program, but 
>> then my method was too slow to do any more digits in reasonable time.
>>
>
> It's interesting that you had an algorithm that could compute six digits 
> but not ten.

Well I simplified as far as Invisible did, and then didn't go through the 
modular math to simplifiy it, just applied the mod operator at various 
points in the code.  Using mod 1e5 took a fraction of a second, mod 1e6 took 
about 10 seconds, and mod 1e7 never finished!

> You might try running it on the second version of the puzzle that I gave, 
> since I only ask for the last five digits in that case.  Maybe it'll work!

Hmmm.


Post a reply to this message

From: Invisible
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 03:56:09
Message: <4c2d9b99$1@news.povray.org>
Kevin Wampler wrote:

> Ok, I haven't been able to post today since I've been on airplanes on or 
> airports without wireless the whole time, but I implemented the solution 
> and it seems to work, so this problem is totally solvable, and only 
> takes a fraction of a second on my computer.
> 
> It turns out that the solution is actually quite simple too!  Possibly 
> even more so than that to the last puzzle -- although certainly not as 
> mathematically concise.
> 
> Actually pretty fun, so feel free to give it a shot knowing that it does 
> indeed have a relatively simple solution!

I have a truly marvelous proof of this, which this forum is too small to 
contain...


Post a reply to this message

From: Stephen
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 05:31:02
Message: <4c2db1d6@news.povray.org>
On 02/07/2010 8:56 AM, Invisible wrote:
> Kevin Wampler wrote:
>
>> Ok, I haven't been able to post today since I've been on airplanes on
>> or airports without wireless the whole time, but I implemented the
>> solution and it seems to work, so this problem is totally solvable,
>> and only takes a fraction of a second on my computer.
>>
>> It turns out that the solution is actually quite simple too! Possibly
>> even more so than that to the last puzzle -- although certainly not as
>> mathematically concise.
>>
>> Actually pretty fun, so feel free to give it a shot knowing that it
>> does indeed have a relatively simple solution!
>
> I have a truly marvelous proof of this, which this forum is too small to
> contain...

LOL ;-)

-- 

Best Regards,
	Stephen


Post a reply to this message

From: Invisible
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 05:33:23
Message: <4c2db263$1@news.povray.org>
>> I have a truly marvelous proof of this, which this forum is too small to
>> contain...
> 
> LOL ;-)

You may laugh, but for 300 years mathematicians have been unable to 
rediscover this "marvellous proof" - so excuse me if mathematicians are 
a teeny bit skeptical about sweeping claims. ;-)


Post a reply to this message

From: Kevin Wampler
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 11:08:59
Message: <4c2e010b$1@news.povray.org>
scott wrote:
> 
> I understood from wikipedia that 2^^^3 = 2^^2^^2 and 2^^^2 = 2^^2.  Is 
> that wrong?
> 

Nope, it appears that the error was all mine.  Sorry about that!


Post a reply to this message

From: Kevin Wampler
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 11:13:14
Message: <4c2e020a@news.povray.org>
Invisible wrote:
> 
> I have a truly marvelous proof of this, which this forum is too small to 
> contain...

LOL.  Fortunately the code is small enough to post, I just didn't want 
to spoil the puzzle.

If you want to check your algorithm, the last five digits of the case 
with ten nested for-loops I get as being 68608.


Post a reply to this message

From: Nicolas Alvarez
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 11:21:08
Message: <4c2e03e4@news.povray.org>
I sent the problem to someone on IRC, who spent a while trying to solve it, 
then he posted it to ycombinator:

http://news.ycombinator.com/item?id=1480303

Interesting comments there.


Post a reply to this message

From: Kevin Wampler
Subject: Re: Small math/programming problem
Date: 2 Jul 2010 11:31:52
Message: <4c2e0668$1@news.povray.org>
Nicolas Alvarez wrote:
> I sent the problem to someone on IRC, who spent a while trying to solve it, 
> then he posted it to ycombinator:
> 
> http://news.ycombinator.com/item?id=1480303
> 
> Interesting comments there.
> 

Cool!  I'm glad to see that people like it!


Post a reply to this message

From: Kevin Wampler
Subject: Re: Small math/programming problem
Date: 5 Jul 2010 20:49:51
Message: <4c327daf$1@news.povray.org>
Kevin Wampler wrote:
> 
> Now for a tricker question, say I add another loop:
> 
> n = 2
> for(a = n; a > 0; a--)
>   for(b = n; b > 0; b--)
>     for(c = n; c > 0; c--)
>       for(d = n; d > 0; d--)
>         for(e = n; e > 0; e--)
>           n++
> print n
> 
> What are the last five digits printed?
> 

Don't read if you don't want the puzzle spoiled for you.

Ok, since nobody's posted in a while I'll assume that either everyone's 
lost interest or they're ready to hear the solution.  Invisible had the 
right answer (minus some minor analysis issues) for the case with four 
loops, but the technique he used doesn't extend well beyond that.

To figure out how to make things work for more nested for-loops, let's 
consider the function representing the body of the innermost loop:

f_0(n) = n+1

On successive iterations of the innermost loop, we end up computing:

f_0(n)
f_0(f_0(n))
f_0(f_0(f_0(n)))
etc...

Of course, we're only interested in the last k digits of the answer, so 
the values of these successive iterations end up repeating.  Obviously 
it's easy to represent this in closed from, but let's say we don't want 
to do that.  Then we can explicitly represent this with:

orbit_0 = []|[2,3,4,...,10^k,0,1]

The notation A|B here indicates that the initial value of n (2 in this 
case) goes through the values in A once, and then loops through the 
values in B forever.  We'll call this the orbit of n mod 10^k under f_0, 
meaning that these are the values we get if we start at n and then keep 
applying f_0.  For convienence I'll call A and B the prefix and loop of 
the orbit respectively.

The method of solving the puzzle for many nested loops will involve 
computing orbit_i, which is the orbit of n mod 10^k under f_i, where f_i 
is the function defined by i nested for-loops.  The a'th element in this 
orbit will be given by f_i^a(n), which stands for a compositions of f_i, 
so f_i^3(n) = f_i(f_i(f_i(n))).

In this approach to solving the problem, we'll store each orbit_i 
explicitly.  Thus we can get the value of f_i^a(n) by simply indexing 
into orbit_i.  In the case that the orbit's prefix is empty, then we can 
do this by f_0^a(n) = orbit_i.loop[ (indexOf(n,orbit_i) + a) mod 
period(orbit_i) ], which we'll write as orbit_i[n,a] for shorthand.  If 
the or orbit's prefix is non-empty, then the formula is a bit more 
complicated, but still not too bad, and we'll denote it with 
orbit_i[n,a] in any case.

So far this may not seem very interesting, but the power of this way of 
looking at things will become apparent if we consider the function 
defined by having a single for-loop:

for(a = n; a > 0; a--)
	n = f_{i-1}(n)
	
As noted before, this loop defines the function:

f_i(n) = f_{i-1}^n(n)

Now, the trick to getting this whole approach to work is to directly 
calculate orbit_i entirely in terms of the initial value of n (2 in this 
case) and orbit_{i-1}.  Since we show that the first element in orbit_i 
(it's 2), it's sufficient to be able to calculate element a in orbit_i 
given element a-1.  It's pretty direct to see how to do this:

Since: orbit_{i-1}[n,a] = f_{i-1}^a(n)
and: f_i(n) = f_{i-1}^n(n)
then: f_i(n) = orbit_{i-1}[n,n]

So to generate the orbit defined by f_i, we can use the procedure:

f_i^0(n) = 2
f_i^a(n) = orbit_{i-1}[f_i^{a-1}(n), f_i^{a-1}(n)]
          = orbit_i[n,a]

This allows us to generate values of f_i^a(n) until we hit a repeated 
value, at which point we have the full definition of orbit_i.  Thus if 
we want the last m digits of the result if we start at n and use i 
nested loops, we just look up the value of orbit_i[n,n].  Thus the 
algorithm requires linear time in the numeber of nested loops, although 
exponential time in the number of digits you want to return (which is 
why I changed the problem to only ask for five digits).

The Python code I udes to solve this problem (posted below) uses this 
procedure to get the answer for the case with five nested for-loops: 86528

Even better, the Python code allows you to create an orbit of orbits, 
and thus find that the last five digits for the case with 10^100 nested 
for-loops are 45728!  Not bad considering how completely intractable the 
problem appears at first.  I also think it's sort of nifty that the 
entire algorithm is basically just fancy array indexing, so in some 
sense it's quite simple.  Also, I haven't quite proved rigorusly that 
this algorithm works, and I double I'll bother to, so let me know if you 
find an error.  I also suspect it might even be possible to make an 
algorithm which is sub-exponential in the number of digits, but I 
haven't look into this much.

At any rate, here's the code which I used to compute all these answers:

-------------------------------------------------------------------------------

class InvertibleList(object):
	"""Like a list, but also allows you to efficiently find the index
	at which a given element appears.  All elements in the list are assumed
	to be unique.  Python probably has something like this built in, but it
	was easy enough to code that I just implemented it myself."""

	def __init__(self, items=[]):
		self.values = []
		self.invValues = {}
		self.extend(items)

	def __len__(self):
		return len(self.values)

	def __getitem__(self, key):
		return self.values[key]
		
	def indexOf(self, value):
		return self.invValues[value]

	def append(self, item):
		self.values.append(item)
		self.invValues[item] = len(self.values)-1
		
	def extend(self, items):
		for item in items:
			self.append(item)
		
	def __contains__(self, value):
		return value in self.invValues
		
	def __eq__(self, other):
		if len(self) != len(other): return False
		for v1, v2, in zip(self.values, other.values):
			if v1 != v2: return False
		return True
		
	def __hash__(self):
		h = hash(len(self))
		for i, v in enumerate(self.values):
			h ^= hash(i) + hash(v)
		return h

	def __str__(self):
		return str(self.values)


class Orbit(object):
	"""Represents the orbit which a value takes under the composition of a
	function in a finite field.  For convienence you can give the value and
	the function to be composed as parameters to the constructor."""

	def __init__(self, n=None, f=None):
		self.prefix = InvertibleList()
		self.loop = InvertibleList()

		if n is not None:
			assert f is not None
			
			# generate values until we hit a repeat
			o = InvertibleList()
			o.append(n)
			
			n = f(n)
			while n not in o:
				o.append(n)
				n = f(n)

			# partition o into perfix and loop components
			ind = o.indexOf(n)
			for i in xrange(ind):
				self.prefix.append(o[i])
			for i in xrange(ind,len(o)):
				self.loop.append(o[i])
				
	def itemFrom(self, n, a):
		"""Get the a'th item after the appearance on n in this orbit."""

		if n in self.loop:
			nInd = self.loop.indexOf(n)
			return self.loop[(a+nInd) % len(self.loop)]
		else:
			assert n in self.prefix
			nInd = self.prefix.indexOf(n)
			nInd2 = a + nInd
			if nInd2 < len(self.prefix):
				return self.prefix[nInd2]
			else:
				return self.loop[(nInd2-len(self.prefix)) % len(self.loop)]

	def __str__(self):
		return str(self.prefix) + "|" + str(self.loop)

	def __len__(self):
		return len(self.loop)
		
	def __eq__(self, other):
		return (self.prefix == other.prefix) and (self.loop == other.loop)
		
	def __hash__(self):
		return hash(self.prefix) ^ hash(self.loop)


def reduceOrbit(n, o1):
	"""Given an orbit for f, returns an orbit of n under the function
	f^n(n).  This assumes n is in the orbit o1, and that the sizes of
	these orbits behave nicely."""

	o2 = Orbit(n, lambda i: o1.itemFrom(i,i))
	assert len(o1.loop) % len(o2.loop) == 0
	return o2


def getLastDigits(n, numLevels, numDigits):
	mod = 10**numDigits

	o = Orbit(2, lambda i: (i+1) % mod)
	for i in xrange(1, numLevels):
		o = reduceOrbit(n, o)

	return o.itemFrom(n, n)


def getLastDigitsOrbit(n, numDigits):
	mod = 10**numDigits
	metaOrbit = Orbit(
		Orbit(2, lambda i: (i+1) % mod),
		lambda orb: reduceOrbit(n, orb))
	return metaOrbit

#-----------------------------[ main ]----------------------------#

if __name__ == "__main__":
	numDigits = 5
	print "These are the last five digits for the case with five nested 
for-loops:"
	print getLastDigits(2, 5, numDigits)

	print
	print "The last five digits for the case with 10^100 nested for-loops:"
	metaOrbit = getLastDigitsOrbit(2, numDigits)
	print metaOrbit.itemFrom(metaOrbit.prefix[0], 10**100).itemFrom(2,2)


Post a reply to this message

<<< Previous 10 Messages Goto Initial 10 Messages

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.