Skip to content

Solution to “Searching for Bobby Fisher”

In Roddy's own words:

"I was using the "Find" method on an unsorted TStringlist... which you're not supposed to do. Sometimes it works (my initial test case had the items in my list already sorted, just by coincidence), but usually it doesn't..."

So, Roddy had two options:

  1. {...}
  2. Names.LoadFromFile('names.txt');
  3. Names.Sorted := True;
  4. {...}

...or...

  1. //Instead of using Names.find, do the following:
  2. Result := Names.IndexOf('Bobby Fisher');

Thanks again to Roddy for suggesting this puzzle. If you'd like to submit an idea for a puzzle, email me at jacob(at)twodesk.com.

Searching for Bobby Fisher

Special thanks to puzzle regular Roddy Pratt for suggesting the idea for this week's puzzle.

Roddy (*Names may have been changed to protect the innocent) had code similar to the following:

  1. function FindBobbyFisher: Integer;
  2. var
  3.   Names: TStringList;
  4.   I: Integer;
  5. begin
  6.   Names := TStringList.Create;
  7.   Names.LoadFromFile('names.txt');
  8.   if not Names.Find('Bobby Fisher', I) then
  9.     Result := -1
  10.   else
  11.     Result := I;
  12.   Names.Free
  13. end;

Roddy's code worked at first, but soon Bobby Fisher could not be found. What is Roddy missing?

Answer to “Include”

The problem here was with the {$INCLUDE} directives.  The following line compiles in the IDE, but not from the command line (or from a build tool that uses the command line compiler):

{$INCLUDE ../includes/dotnet.inc}

The fix?  Use backslashes in the file path, not forward slashes:

{$INCLUDE ..\includes\dotnet.inc}

Include

Kyle put the following code in his file:

  1. {$IFDEF CLR}
  2.   {$INCLUDE ../includes/dotnet.inc}
  3. {$ELSE}
  4.   {$INCLUDE ../includes/win32.inc}
  5. {$ENDIF}

During testing, everything worked as expected, but after checking in all the files, he got an email from the build machine telling him he broke the build.

What did he do wrong?

Answer to “Sing a Song”

This puzzle came about a couple weeks ago when I was in a church. I looked at the hymn board and discovered that there were so many "1"s on the board that they had run out, and someone had turned a "7" upside-down and covered the horizontal part with masking tape. I thought, "there must be a simple way to know how many of each digit card to include." I came up with one method, but thought it would be interesting to see if anyone else could come up with something better.

Here's the method I used:

  1. Create an array containing one string for each song in the songbook
  2. Sort the array by the number of occurrences of the digit in question
  3. Get the number of occurrences of the digit in the first X strings in the array, where X is the number of songs in the service.

Here's my code:

  1. function CountOccurrences(S: string; AInt: Integer): Integer;
  2. var
  3.   I: Integer;
  4. begin
  5.   Result := 0;
  6.   for I := 1 to Length(S) do
  7.     if S[I] = Chr(AInt + 48) then
  8.       Inc(Result);
  9. end;
  10.  
  11. function GetCount(SongsInBook, SongsInService, Digit: Integer): Integer;
  12. var
  13.   I, J: Integer;
  14.   Numbers: array of string;
  15.   TempStr: string;
  16. begin
  17.   //Make sure the numbers are as expected
  18.   Assert(SongsInService <SongsInBook);
  19.   Assert((Digit <10) and (Digit> -1));
  20.   //First, fill an array with all the numbers in the book
  21.   SetLength(Numbers, SongsInBook);
  22.   for I := 0 to SongsInBook - 1 do
  23.   begin
  24.     Numbers[I] := IntToStr(I + 1);
  25.   end;
  26.   //Sort the array by the number of occurences of the digit in question. We'll
  27.   //just use a simple sort.
  28.   for I := 0 to SongsInBook - 1 do
  29.   begin
  30.     for J := I + 1 to SongsInBook - 1 do
  31.     begin
  32.       if CountOccurrences(Numbers[J], Digit)>=
  33.          CountOccurrences(Numbers[I], Digit) then
  34.       begin
  35.         TempStr := Numbers[I];
  36.         Numbers[I] := Numbers[J];
  37.         Numbers[J] := TempStr;
  38.       end;
  39.     end;
  40.   end;
  41.   //Finally, get the number of digits in the top SongsInService "worst" songs
  42.   TempStr := '';
  43.   for I := 0 to SongsInService - 1 do
  44.     TempStr := TempStr + Numbers[I];
  45.   Result := CountOccurrences(TempStr, Digit);
  46. end;
  47.  
  48. function GetDigitCounts(SongsInBook, SongsInService: Integer): THymnBoard;
  49. {var
  50.   I: Integer;}
  51. begin
  52.   Result.N0 := GetCount(SongsInBook, SongsInService, 0);
  53.   Result.N1 := GetCount(SongsInBook, SongsInService, 1);
  54.   Result.N2 := GetCount(SongsInBook, SongsInService, 2);
  55.   Result.N3 := GetCount(SongsInBook, SongsInService, 3);
  56.   Result.N4 := GetCount(SongsInBook, SongsInService, 4);
  57.   Result.N5 := GetCount(SongsInBook, SongsInService, 5);
  58.   Result.N6 := GetCount(SongsInBook, SongsInService, 6);
  59.   Result.N7 := GetCount(SongsInBook, SongsInService, 7);
  60.   Result.N8 := GetCount(SongsInBook, SongsInService, 8);
  61.   Result.N9 := GetCount(SongsInBook, SongsInService, 9);
  62.  
  63. //The following also works, and is much shorter, but is less readable:
  64. {  for I := 0 to 9 do
  65.     Integer(Pointer(Integer(@Result) + I * sizeof(Integer))^) :=
  66.       GetCount(SongsInBook, SongsInService, I);}
  67. end;

There are a couple of optimizations that could be done here that I didn't do for the sake of readability. One is that the array doesn't necessarily need to be regenerated each time, but really only needs to be re-sorted for each digit. The array could be generated only once.

Also, a better sorting algorithm than simple sort could obviously be used, but the sorting isn't the point here, so I decided to keep the code as simple as possible.

Sing a song

You've been hired by a company that creates hymn boards - a plaque that goes on the wall of a church showing which songs are to be sung during a church service:

Hymn BoardYour task is to create an algorithm to determine the minimum number of each digit card (the little cards with a single digit, 0-9, on them) to include with each hymn board. You are given the number of songs in the songbook, and the maximum number of songs that can be sung during a service. A song is never repeated during a service.

For example, if there are 10 songs in the book, and two songs can be sung during a service, then you need exactly two 1s, and one of everything else (two 1s in case the two songs are 1 and 10). That one is easy enough to do by hand, but most books will have a few hundred songs, and at least three songs are sung at most services. Your algorithm should be universal.

Here's your skeleton code:

  1. type THymnBoard = record
  2.   N0, N1, N2, N3, N4, N5, N6, N7, N8, N9: Integer;
  3. end;
  4.  
  5. function GetDigitCounts(SongsInBook, SongsInService: Integer): THymnBoard;
  6. begin
  7.   //You fill in here
  8. end;

Answer to “Through the eye of the needle”

Steven pointed out that Ray's code did not gracefully handle the application closing. If the application closed while there were still transactions processing, the application would simply appear to hang, with its user interface frozen, until either all the transactions were processed, or the user killed the process.

The solution is to check Application.Terminated in the thread loop:

  1. while TransactionsPending and not Application.Terminated do
  2.   //etc...
  3. end;

With this check in, when the application is closed, the thread will finish processing the transaction it's processing, and then gracefully exit.

Through the eye of the needle

Ray was a junior developer at a large, nameless bank. One of the applications his group maintained was one that settled certain transactions. Ray was tasked with seeing if he could make the application run any faster. He discovered that the major bottleneck was processing on a network server, and decided that if he made his application multithreaded, it could settle multiple transactions at once, and it would be faster. He created a new thread object and wrote the following Execute method:

  1. procedure TProcessingThread.Execute;
  2. var
  3.   Trans: TAccountTransaction;
  4. begin
  5.   while TransactionsPending do
  6.   begin
  7.     try
  8.       Trans := TAccountTransaction.Create;
  9.       try
  10.         GetNextPendingTransaction(Trans);
  11.         Trans.Settle;
  12.       except
  13.         on E: Exception do
  14.           LogError(Format('Transaction %d failed: %s', [Trans.ID, E.Message]));
  15.       end;
  16.     finally
  17.       Trans.Free;
  18.     end;
  19.   end;
  20. end;

When Steven, a senior developer looked at the code, he pulled Ray aside and gently explained something very important that Ray had missed. What did Steven tell Ray?

The puzzle has the week off

Delphi Puzzles is taking the week off this week.

The site is being moved to a new server, and to make sure that we don't lose any comments in the process, there is no puzzle this week. It will be back in full force, better-than-ever, next week.

--Jacob

Solution to “Bluff the Programmer”

The valid code is option #1:

  1. Memo1.Lines.Add(Format('1: %s' + ^M + '2: %s', [Edit1.Text, Edit2.Text]);

^M is a Delphi code construct that compiles, but I haven't found it documented anywhere. The ^M is character - analogue to #13#10 (or \n in c parlance). The + operators aren't even necessary. You can do 'string1'^M'string2' and get the same result.

I wouldn't encourage using this in code, but it's good to know that it exists.