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:
- Create an array containing one string for each song in the songbook
- Sort the array by the number of occurrences of the digit in question
- 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:
function CountOccurrences(S: string; AInt: Integer): Integer; var I: Integer; begin Result := 0; for I := 1 to Length(S) do if S[I] = Chr(AInt + 48) then Inc(Result); end; function GetCount(SongsInBook, SongsInService, Digit: Integer): Integer; var I, J: Integer; Numbers: array of string; TempStr: string; begin //Make sure the numbers are as expected Assert(SongsInService <SongsInBook); Assert((Digit <10) and (Digit> -1)); //First, fill an array with all the numbers in the book SetLength(Numbers, SongsInBook); for I := 0 to SongsInBook - 1 do begin Numbers[I] := IntToStr(I + 1); end; //Sort the array by the number of occurences of the digit in question. We'll //just use a simple sort. for I := 0 to SongsInBook - 1 do begin for J := I + 1 to SongsInBook - 1 do begin if CountOccurrences(Numbers[J], Digit)>= CountOccurrences(Numbers[I], Digit) then begin TempStr := Numbers[I]; Numbers[I] := Numbers[J]; Numbers[J] := TempStr; end; end; end; //Finally, get the number of digits in the top SongsInService "worst" songs TempStr := ''; for I := 0 to SongsInService - 1 do TempStr := TempStr + Numbers[I]; Result := CountOccurrences(TempStr, Digit); end; function GetDigitCounts(SongsInBook, SongsInService: Integer): THymnBoard; {var I: Integer;} begin Result.N0 := GetCount(SongsInBook, SongsInService, 0); Result.N1 := GetCount(SongsInBook, SongsInService, 1); Result.N2 := GetCount(SongsInBook, SongsInService, 2); Result.N3 := GetCount(SongsInBook, SongsInService, 3); Result.N4 := GetCount(SongsInBook, SongsInService, 4); Result.N5 := GetCount(SongsInBook, SongsInService, 5); Result.N6 := GetCount(SongsInBook, SongsInService, 6); Result.N7 := GetCount(SongsInBook, SongsInService, 7); Result.N8 := GetCount(SongsInBook, SongsInService, 8); Result.N9 := GetCount(SongsInBook, SongsInService, 9); //The following also works, and is much shorter, but is less readable: { for I := 0 to 9 do Integer(Pointer(Integer(@Result) + I * sizeof(Integer))^) := GetCount(SongsInBook, SongsInService, I);} 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.
Post a Comment