Showing posts with label puzzle. Show all posts
Showing posts with label puzzle. Show all posts

Tuesday, January 6, 2009

Fibonacci and Big-Oh reduction

I was asked to write some C# to implement a Fibonacci sequence, which falls into two parts: 1) generate a single number in the sequence, 2) generating the whole sequence.

Note that most examples of this sequence generator have a nasty bug in them. Most examples use the int data type, whilst this code uses the long data type, otherwise this function overflows around n=47.

The naive way to implement this is recursively:
  /// <summary>
  /// slow version of fib calc for testing - warning
  /// runs in O(1.618..n) 
  /// </summary>
  internal long Fibonacci(int n)
  {
   if (n < 2)
    return n;
   else
    return Fibonacci(n - 1) + Fibonacci(n - 2);
  }

And a more optimal approach is:
  /// <summary>
  /// returns the Fibonacci number for N
  /// </summary>
  public long Fibonacci(int n)
  {
   // returns N where n < 2, for higher values of N
   // avoids recursive solution by keeping track
   // of previous results

   long fn = 0;

   if (n < 0) // offset must be positive
    throw new NotSupportedException("Fibonacci sequence offset must be positive");
   else if (n < 2) // returns N where N < 2
    fn = n;
   else // use caching technique for higher values of N
   {
    long fnMinusTwo = 0;
    long fnMinusOne = 1;

    for (long ni = 2; ni <= n; ni++)
    {
     // effectively: fn = Fibonacci(n - 1) + Fibonacci(n - 2)
     fn = fnMinusOne + fnMinusTwo;

     fnMinusTwo = fnMinusOne;
     fnMinusOne = fn;
    }
   }

   return fn;
  }
The latter approach is much more efficient O(n) vs O(1.618..n). However its not as simple as the former. Another approach is to take the former solution and use a local cache:
  /// <summary>
  /// caching version of fib calc
  /// </summary>
  Dictionary<int, long> fibCache = new Dictionary<int, long>();
  internal long Fibonacci(int n)
  {
   long fn = 0;

   // use value from cache if already calculated
   if (fibCache.ContainsKey(n))
    return fibCache[n];

   // not cached, need to work it out
   if (n < 2)
    fn = n;
   else
    fn = Fibonacci(n - 1) + Fibonacci(n - 2);

   // add to cache
   fibCache.Add(n, fn);

   return fn;
  }
This gives us an O(n) runtime with a reasonably simple implementation. This is of course the classic trade-off of time vs. space - we have increased space used (the dictionary) to decrease time.

Finally we want to generate the whole sequence, which we can do in a lazy evaluation way using the yield keyword:
  /// <summary>
  /// returns the first N items in the Fibonacci sequence
  /// </summary>
  public static IEnumerable<long> FibonacciSequence(int n)
  {
   for (int fn = 0; fn < n; fn++)
    yield return Fibonacci(fn);
  }

Wednesday, November 19, 2008

Finding the Nth open door

As a follow up to my previous post about the 100 Door Puzzle, I realised there is a variation of the question where there is an even faster answer.

If the question is: which door is the nth open door (for example tell me where the 5th open door is) then the answer is: n2 or as per the example 52=25 (the 25th door along is the 5th open door)


private static int NextOpenDoorIsAt(int nthOpenDoor)
{
return (int)Math.Pow(nthOpenDoor, 2d);
}


So in the case where you only need to find the nth door that is open this is a faster method. This of course could be applied to my previous post to give an O(√N) solution for the whole series - the faster being down to the speed of summation vs. product.

Toggle door puzzle, fast solution

I was recently asked the following question:

You have 100 doors in a row that are all initially closed. you make 100 passes by the doors starting with the first door every time. the first time through you visit every door and toggle the door (if the door is closed, you open it, if its open, you close it). the second time you only visit every 2nd door (door #2, #4, #6). the third time, every 3rd door (door #3, #6, #9), etc, until you only visit the 100th door.

question: what state are the doors in after the last pass? which are open which are closed?

If the questions is expressed as is door N open, the answer is

bool doorOpen = (Math.Sqrt(doorNumber) % 1) == 0;

However, as part of going through the puzzle I noticed that there seemed to be a sequence in the offset of the open doors (1, 4, 9), which looked suspiciouly like odd number offsets: 1=1, 4=1+3, 9=4+5 i.e. the value of the offset of the next open door is the previous open door offset plus the next odd number.

Which when I applied it to larger doorsets worked as well. Looking at 50 doors, which doors are open (O) vs closed (c):
[OccOccccOccccccOccccccccOccccccccccOccccccccccccOc]

If you are given the problem of find all open doors in the set, then using the square root approach is O(n). So the square root approach would look like:
private static BitArray GenerateDoorTogleSeriesResult(int doors)
  {
   BitArray result = new BitArray(doors);

   for (int door = 1; door <= doors; door++)
    result[door - 1] = (Math.Sqrt(door) % 1) == 0;

   return result;
  }

whilst my approach looks like:

private static BitArray GenerateDoorTogleResult(int doors)
  {
   BitArray result = new BitArray(doors);

   int odd = 1;
   for (int door = odd; door <= doors; door += odd)
   {
     result[door - 1] = true;
     odd += 2; // next odd number
   }
   return result;
  }

The difference being that for 1,000 doors the square root apprach requires 1,000 square root calculation, whilst mine requires 31 summations. Or in big-oh O(√n).

The calling code looks like:
static void Main(string[] args)
  {
   const int DOORS = 10;
   BitArray suggestedAnswer = GenerateDoorTogleResult(DOORS);
   BitArray fastAnswer = GenerateDoorTogleSeriesResult(DOORS);

   Console.WriteLine("suggested:\n{0}", ShowDoors(suggestedAnswer));
   Console.WriteLine("fast\n{0}", ShowDoors(fastAnswer));
   Debug.Assert(ShowDoors(suggestedAnswer).CompareTo(ShowDoors(fastAnswer)) == 0);
  }

  private static string ShowDoors(BitArray doors)
  {
   StringBuilder sb = new StringBuilder(doors.Length+4);
   sb.Append("[");
   foreach (bool doorOpen in doors)
    sb.Append(doorOpen == true ? 'O' : 'c');
   sb.Append("]\n");

   return sb.ToString();
  }

I subsequently found the proof for this by Francesco Maurolico in 1575.

1575: "Arithmeticorum Libri Duo" by Francesco Maurolico is the first book
to prove a theorem by Mathematical Induction. The theorem was known 2000
years earlier by Pythagorus, but they proved it geometrically. The
theorem is that the sum of the first N odd numbers
1 + 3 + 5 + 7 + 9 + 11 + 13 +... + (2N-1) = N x N

Thursday, October 16, 2008

Silverlight puzzle update and issues


Updated silverlight puzzle to use the release version of silverlight 2.



The only issue with the upgrade was that using "storyBoard.SkipToFill()" does not stop the animation in the new version - this generated an exception:



A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.dll

Additional information: Operation is not valid on an active Animation or Storyboard. Root Storyboard must be stopped first.



This exception was caused by trying to resue the same animation, in the beta SkipToFill also stopped the animation. Adding a "storyBoard.Stop()" after the SkipToFill() fixed this. However this caused a side-effect. It appears that in the beta SkipToFill actually updates the DependencyProperty, what does this mean?

In my beta code I relied on using the animation to "move" the visual tile from one position to another. In the release version once the animation was removed (i.e. applied to a different object) the tile would snap back to its original position. My work around this was to set the tiles position before the animation - this would mean that the tile would be at the correct location after the animation (or if the animation was removed).

Friday, September 19, 2008

Fun with silverlight

Learning some heuristic techniques I needed a visual presentation of a puzzle.. so given I ws learning silverlight I wrote slider, which gives me a visual representation.

The puzzle code was based on looking at Laurence Moroney's silverlight book, see his blog (the book is a great introduction to silverlight). In fact all I kept was the clipping routine, the rest was rewritten to make a cleaner seperation between the game and the presentation, alá MVC:



Having written the code from scratch it shows how picking the correct data model and interfaces for the classes simplifies the code. I've not posted the code yet, I will after I've implemented the heuristic solver. The UI as it stands allows clicking on tiles to move, or using the keyboard (Up, Left, Down, Right). The UI also animates the slide movements.

Thursday, September 18, 2008

Finding duplicates in an array

One of the problems I stumbled across recently was writing an efficient way to detect if a non-sorted integer array contains duplicates. Or more formally: an unsorted array of size N that contains values between 0 and N-1, find if it contains duplicated elements.

The trick being to avoid having to sort the array, or O(N2) loop within loops.

My solution was to use a bit array the size of N, which would flag if we had seen a value or not - we run through all the values in the array and check its position in the bit array, if it contains a 0 we have not seen it before, or a 1 if we have (duplicate). If we have not seen it before we set its position to 1 and continue.

In C# the is a great class that makes bit manipulation even easier "BitArray", it provides an efficient array class for handling bit operation.

The code:

static void Main(string[] args)
{
int[] numbers = { 1, 2, 3, 1, 5 };

bool duplicates = IsArrayDuplicated(numbers);
}

private static bool IsArrayDuplicated(int[] numbers)
{
bool result = false;

BitArray bits = new BitArray(numbers.Length+1);

foreach (int n in numbers)
{
result = !bits[n];

if (!result)
break;

bits[n] = true;
}

return result;
}

Sunday, September 14, 2008

Rotating an array

Over at polymathprogrammer there is an article about rotating an array, which was a response to Mr Chen's entry on an interview question

Reading the answers got me thinking as I had recently written a roate method in order in implement tic-tac-toe (don't ask why). My solution then had been along the lines of one of the suggested solutions:


for (int y = 0; y < n; y++)
for (int x = 0; x < n; x++)
output[n - y - 1,x] = input[x,y];


though I used a mapping intead of a direct move.

What was interesting was that someone else had suggested that you could just do it using two mirror operations (mirror horizontal and then a diagonal) which was a very elegant way of doing it.

I liked this and it made me think was there another way to do it (as in how many ways can this problem be solved. And I came up with this, which takes advantage of using a single dimension array for holding two dimensional data:


1 &124; 2 &124; 3
-----------
4 &124; 5 &124; 6
-----------
7 &124; 8 &124; 9


stored as


int[] array = new int[] { 1, 2, 3, 4 , 5, 6, 7, 8, 9};


which if rotated looks likes:


int[] array = new int[] { 7, 4, 1, 8, 5, 2, 9, 6, 3};


which looks like the mapping:


destinationOffset += w;
if (destinationOffset > l)
destinationOffset = destinationOffset % w;


in effect, offset its new position by the width of the matrix (3) and wrap overflows back to the start. This looked good until number 4 as it collided with 1, so I just moved it back a step if it collided. Problem solved for 90° right:

gives:


7 &124; 4 &124; 1
-----------
8 &124; 5 &124; 2
-----------
9 &124; 6 &124; 3


Now you could just apply the function multiple times for 180°, 270° rotations, but looking at the resultant arrays shows two things:
180°: results is the original array in reverse order
270°: results is the 90° array in reverse order

So it looks like reversing an array is the same as a diagonal mirror! which means you need just two functions, a rotate and an array reverse (which is part of most array implementations, .Net as Arrray.Reverse)

Code for just the single rotation is:


int size = 3*3;
int[] array = new int[size];
for (int i = 0; i < size; i++)
array[i] = i + 1;

int l = array.Length;
int w = (int)Math.Sqrt(array.Length);

int[] destination = Rotate(array, l, w);


private static int[] Rotate(int[] array, int l, int w)
{
int[] destination = new int[l];
int destinationOffset = -1;
for (int sourceOffset = 0; sourceOffset < l; sourceOffset++)
{
destinationOffset += w;

// wrap around
if (destinationOffset > l)
{
destinationOffset %= w;
// collision avoidance
destinationOffset--;
}

destination[destinationOffset] = array[sourceOffset];

}

return destination;
}


and of course given we know the size of the array, and we can use XOR to do swaps in place

swap(a, b) =>
a = a ^ b; // a^= b;
b = b ^ a; // b ^= a;
a = a ^ b; // a ^= b;


then the fast version (without working at a bit level) might be:


private static void RotateInPlace4by4(int[] array)
{
// corners (1,4 moves around)
swap(ref array[3], ref array[15]);
swap(ref array[0], ref array[3]);
swap(ref array[0], ref array[12]);

// left inner (2 moves around)
swap(ref array[7], ref array[14]);
swap(ref array[1], ref array[7]);
swap(ref array[1], ref array[8]);

// right inner (3 moves around)
swap(ref array[11], ref array[13]);
swap(ref array[2], ref array[11]);
swap(ref array[2], ref array[4]);

// middle square (6,7,10,11)
swap(ref array[5], ref array[9]);
swap(ref array[6], ref array[10]);
swap(ref array[6], ref array[9]);
}

private static void swap(ref int a, ref int b)
{
a ^= b;
b ^= a;
a ^= b;
}

Thursday, August 28, 2008

A man, a plan, a canal - Panama!

Whilst viewing Gary Daniels and Evan Goldring - Mock whiteboard problem I noticed a strange thing. In the video they show an mock interview question and try to answer it. As Evan says in the video and afterwards his solution was not the best one. After this everyone posted their attempt (15 pages of comments at the moment).

The strange thing? not one is right! why? Gary hints towards the end that the solution needs to handle white spaces correctly - in effect suggesting the the function should calculate palindromes on the basis of words rather than byte symmetry. I'm off course not suggesting that my solutions are correct either, as the definition of palindrome was not defined fully enough for anyone to be able to judge if an answer is correct.

My first attempt ignored this hint and just went for a straightforward symmetry test:

static bool isPalindromeFirst(string subject)
{
bool result = false;

if (subject != null)
{
int endOffset = subject.Length - 1;
int midOffset = (int)Math.Ceiling(subject.Length / 2d);

for (int startOffset = 0; startOffset < midOffset; startOffset++, endOffset--)
{
result = (subject[startOffset] == subject[endOffset]);

if (!result)
break;
}
}

return result;
}
This solution appears generally correct for the byte symmetry approach, but I just don't feel happy with its lack of whitespace handling , so my second attempt is longer, but hopefully an evolutionary approach:


static bool isPalindrome(string subject)
{
const int STEP_FORWARD = 1;
const int STEP_BACKWARD = -STEP_FORWARD;
const int BEFORE_START = -1;
const int CHAR_AT_A_TIME = 1;
const int COMPARE_EQUALS = 0;

// assume its not a palindrome
bool result = false;

// how to compare
CompareInfo ci = CultureInfo.CurrentCulture.CompareInfo;
CompareOptions co =
CompareOptions.IgnoreCase |
CompareOptions.IgnoreKanaType |
CompareOptions.IgnoreNonSpace |
CompareOptions.IgnoreSymbols |
CompareOptions.IgnoreWidth; |

// null strings are not palindromes
if (subject != null)
{
int AFTER_END = subject.Length;

// single letter words are palindromes
result = (AFTER_END == 1 && IsPalindromeChar(subject[0]));

// start the comparison points at valid characters
int startOffset = GetNextValidCharacter(subject, BEFORE_START, STEP_FORWARD, AFTER_END);
int endOffset = GetNextValidCharacter(subject, AFTER_END, STEP_BACKWARD, BEFORE_START);

while (startOffset < endOffset)
{
result = ci.Compare(subject, startOffset, CHAR_AT_A_TIME, subject, endOffset, CHAR_AT_A_TIME, co) == COMPARE_EQUALS;

if (!result)
break;

// move the comparison points towards each other
startOffset = GetNextValidCharacter(subject, startOffset, STEP_FORWARD, endOffset);
endOffset = GetNextValidCharacter(subject, endOffset, STEP_BACKWARD, startOffset);
}
}

return result;
}

static int GetNextValidCharacter(string subject, int offset, int step, int bound)
{
if (offset != bound)
offset += step;

while (offset != bound && !IsPalindromeChar(subject[offset]))
offset += step;

return offset;
}

static bool IsPalindromeChar(char c)
{
return char.IsLetter(c) || char.IsDigit(c);
}



ps I'm sure "a" is a palindrome, but I suspect "b" is not... however this would mean getting into recognising words and all that entails...

result of running:



'(null)' palindrome? False
'' palindrome? False
'.' palindrome? False
'..' palindrome? False
'.,.' palindrome? False
'a' palindrome? True
'ab' palindrome? False
'aa' palindrome? True
'aaa' palindrome? True
'abc' palindrome? False
'aba' palindrome? True
'abca' palindrome? False
'abba' palindrome? True
'aabccbaa' palindrome? True
'a man, a plan, a canal - panama' palindrome? True
'a man, a plan, a canal - panama!' palindrome? True
'A Man, a Plan, a Canal - Panama' palindrome? True
'A man, a plan, a canal - Panama!' palindrome? True
'A man, a plan, a canal - Panamá!' palindrome? True
'A ma;n, a plan, a canal - Panamá!' palindrome? True
'A ma;;n, a plan, a canal - Panamá!' palindrome? True
'A ma;n, a plan, a canal - Pa;namá!' palindrome? True
'A ma;;n, a plan, a canal - Pa;;namá!' palindrome? True
'A ma;;n, a plan, a canal - Pa;;;namá!' palindrome? True
'!A man, a plan, a canal - Panamá!' palindrome? True
'!A man, a plan, a canal - Panamá' palindrome? True
'!!A man, a plan, a canal - Panamá' palindrome? True
'A man, a plan, a canal - Panamá!!' palindrome? True
'1' palindrome? True
'12' palindrome? False
'11' palindrome? True
'122' palindrome? False
'121' palindrome? True
'1221' palindrome? True
'12.21' palindrome? True
'..12.21' palindrome? True
Press any key to continue...