Euler Problem 54

Euler Problem 54 asks:

‘In the card game poker, a hand consists of five cards and are ranked,
‘from lowest to highest, in the following way:

‘    * High Card: Highest value card.
‘    * One Pair: Two cards of the same value.
‘    * Two Pairs: Two different pairs.
‘    * Three of a Kind: Three cards of the same value.
‘    * Straight: All cards are consecutive values.
‘    * Flush: All cards of the same suit.
‘    * Full House: Three of a kind and a pair.
‘    * Four of a Kind: Four cards of the same value.
‘    * Straight Flush: All cards are consecutive values of same suit.
‘    * Royal Flush: Ten, Jack, Queen, King, Ace, in same suit.

‘The cards are valued in the order:
‘2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace.

‘If two players have the same ranked hands then the rank made up of the
‘highest value wins; for example, a pair of eights beats a pair of fives
‘(see example 1 below). But if two ranks tie, for example, both players
‘have a pair of queens, then highest cards in each hand are compared
‘(see example 4 below); if the highest cards tie then the next highest
‘cards are compared, and so on.

‘Consider the following five hands dealt to two players:
‘Hand  Player 1            Player 2             Winner
‘1     5H 5C 6S 7S KD      2C 3S 8S 8D TD
‘      Pair of Fives       Pair of Eights       Player 2
‘2     5D 8C 9S JS AC      2C 5C 7D 8S QH
‘      Highest card Ace    Highest card Queen   Player 1
‘3     2D 9C AS AH AC      3D 6D 7D TD QD
‘      Three Aces          Flush with Diamonds  Player 2
‘4     4D 6S 9H QH QC      3D 6D 7H QD QS
‘      Pair of Queens      Pair of Queens
‘      Highest card Nine   Highest card Seven   Player 1
‘5     2H 2D 4C 4D 4S      3C 3D 3S 9S 9D
‘      Full House          Full House
‘      With Three Fours    with Three Threes    Player 1

‘The file, poker.txt, contains one-thousand random hands dealt to
‘two players. Each line of the file contains ten cards (separated
‘by a single space): the first five are Player 1’s cards and the last
‘five are Player 2’s cards. You can assume that all hands are valid
‘(no invalid characters or repeated cards), each player’s hand is in
‘no specific order, and in each hand there is a clear winner.

‘How many hands does Player 1 win?

I saw the problem as six tasks:

  1. Open the file
  2. Populate the hands
  3. Populate the cards and assign values
  4. Sort the cards to recognize the patterns
  5. Score the hands
  6. Compare scores and keep a running total for Player 1

I set up a User-defined Type of Card, and then a User-defined Type of Hand, which holds, natually, 5 cards, the string (display) that it came in as, plus a value to compare against the other hand. User-defined Types nicely work with the With…End With syntax construction.

Euler provides the hands as a line of the file containing both hands. I parsed the file into cards, and picked off the cards’ value and suit. I gave a Jack 11, Queen 12, King 13 and Ace 14. I sorted the cards for the purpose of scoring the hand. A Royal Flush got the absolute max of 9. The values went down by 1 until a single high card was zero, with each card of the hand getting evaluated also, to break ties. The most important card’s value was multiplied by 10^-2, the second by 10^-4, the third by 10^-6, the fourth by 10^-8, and the fifth by 10^-10. The most important card is not the highest card, but the one that makes the hand. A King-high straight flush (KQJT9) would score 8.1312111009, while 3 treys plus A2 for three-of-a-kind gets 3.0303031402. Tie breaking is built-in. My dad, who won the rent money back from the landlord playing poker, would have asked how come a Royal Flush (AKQJT and also a straight flush) isn’t 8.1413121110 (could have been) and I’d say, “Well, Dad, it just isn’t.” With that as the scoring system in mind, I passed the hand down the possible fits until it matched, and then set a GoFarther flag to false to stop work, so to speak. Finally, I compared Player One’s hand against Player 2’s, and kept tally.

The lowest possible score is 0.0705040302. That’s the worst hand (75432 of different suits) you can get. In poker, suits only count for flushes, and are never a tie breaker. Tie’s are broken by a highcard comparison, and some ties can be imagined that remain tied down through all the cards (JJ998 for example). When that happenes, the pot is split. You can’t have 4-of-a-kind, 3-of-a-kind, or fullhouse ties. Many a fullboat loses to a higher fullboat’s trio.

It ends up that Euler didn’t include any 5-high straights (Ace as low) but I put it in. The code runs in 46ms on my machine. I used LT for the left angle bracket, and GT for the right angle bracket. It is by far the longest problem by far in lines of code, attested to by many who solved it before, and it was made longer by me in time because I forgot a pattern.

Option Explicit
Option Base 1
 
Public Type Card
    Face        As String * 2
    Value       As Byte
    Suit        As String * 1
End Type
 
Public Type Hand
    Display     As String * 14
    Cards(1 To 5)    As Card
    Value       As Double
End Type
 
Sub Problem_054()
    Const T     As Byte = 10
    Const J     As Byte = 11
    Const Q     As Byte = 12
    Const K     As Byte = 13
    Const A     As Byte = 14
    Dim Hands(1000, 2) As Hand
    Dim h       As Long   ‘for number of hands
   Dim c       As Long   ‘for number of cards
   Dim p       As Long   ‘for number of players
   Dim i As Long, Line As String, secs As Single
    Dim Answer As Long, Adder As Double, TEMP As Card
    Dim IsFlush As Boolean, IsRoyal As Boolean, IsStraight As Boolean
    Dim Is4kind As Boolean, Is3Kind As Boolean, GoFarther As Boolean
    Const text  As String = “D:/Downloads/Euler/Poker.txt”
 
    secs = Timer
 
    h = 1
    Open text For Input As #1   ‘1000 hands or lines as: p1 p1 p1 p1 p1 p2 p2 p2 p2 p2
   Do While Not EOF(1)
       Line Input #1, Line
       Hands(h, 1).Display = Left(Line, 14)
       Hands(h, 2).Display = Right(Line, 14)
       h = h + 1
    Loop
    Close #1
 
    For h = 1 To 1000   ‘number of hands
      For p = 1 To 2   ‘number of players
         i = 1
          For c = 1 To 5   ‘number of cards
            With Hands(h, p)
                .Cards(c).Face = Trim(Mid(.Display, i, 3))
                i = i + 3
                .Cards(c).Suit = Right(.Cards(c).Face, 1)
                Select Case Left(.Cards(c).Face, 1)
                   Case Is < = 9
                      .Cards(c).Value = CLng(Left(.Cards(c).Face, 1))
                   Case “T”
                      .Cards(c).Value = T
                   Case “J”
                      .Cards(c).Value = J
                   Case “Q”
                      .Cards(c).Value = Q
                   Case “K”
                      .Cards(c).Value = K
                   Case “A”
                      .Cards(c).Value = A
                End Select
             End With
          Next c
       Next p
    Next h
 
    For h = 1 To 1000
       For p = 1 To 2
          For c = 1 To 4
             For i = c + 1 To 5
                With Hands(h, p)
                   If .Cards(c).Value LT .Cards(i).Value Then   ‘5-card decending sort
                    TEMP = .Cards(i)
                     .Cards(i) = .Cards(c)
                     .Cards(c) = TEMP
                  End If
               End With
            Next i
         Next c
      Next p
   Next h
 
    For h = 1 To 1000
      For p = 1 To 2
         IsFlush = False
         IsRoyal = False
         IsStraight = False
         Is4kind = False
         Is3Kind = False
         GoFarther = True
         With Hands(h, p)
            .Value = 0
            Adder = 0
 
            For c = 1 To 5   ‘Royal (AKQJT)
              If .Cards(c).Value = 15 – c Then
                  IsRoyal = True   ‘also straight
              Else
                  IsRoyal = False
                  Exit For
               End If
            Next c
 
            For c = 1 To 4   ‘Flushes
              If .Cards(c).Suit = .Cards(c + 1).Suit Then
                  IsFlush = True
               Else
                  IsFlush = False
                  Exit For
               End If
            Next c
 
            If IsFlush And IsRoyal Then
               .Value = 9   ‘absolute max – The Royal Flush (AKQJT)
              GoFarther = False
            End If
 
            If GoFarther And IsFlush Then    ‘Straight Flushes
              Adder = 0
               For c = 1 To 4   ‘to King-high straight flush (KQJT9)
                 If .Cards(c).Value – 1 = .Cards(c + 1).Value Then
                     IsStraight = True
                     Adder = Adder + (.Cards(c).Value * 10 ^ (-2 * c))
                     .Value = 8 + Adder   ‘max 8.1312111009
                    GoFarther = False
                  Else
                     IsStraight = False
                     GoFarther = True
                     Exit For
                  End If
               Next c
               If IsStraight Then .Value = .Value + (.Cards(5).Value * (10 ^ -10))
            End If
 
            If GoFarther And IsFlush Then   ‘Ace-low straight flushes
              If .Cards(1).Value = 14 Then
                  Adder = 1 * (10 ^ -10)
                  For c = 2 To 5   ‘5-high straight flush (5432A)
                    If .Cards(c).Value = 7 – c Then
                        IsStraight = True
                        Adder = Adder + (.Cards(c).Value * 10 ^ (-2 * (c – 1)))
                        .Value = 8 + Adder   ‘max 8.0504030201
                       GoFarther = False
                     Else
                        IsStraight = False
                        GoFarther = True
                        Exit For
                     End If
                  Next c
               End If
            End If
 
            If GoFarther Then
               Adder = 0
               For c = 1 To 3   ‘4 of a Kind – 4 cards High (AAAAK)
                 If .Cards(c).Value = .Cards(c + 1).Value Then
                     Is4kind = True
                     Adder = Adder + (.Cards(c).Value * 10 ^ (-2 * c))
                     .Value = 7 + Adder   ‘max 7.1414141413
                    GoFarther = False
                  Else
                     Is4kind = False
                     GoFarther = True
                     Exit For
                  End If
               Next c
               If Is4kind Then .Value = .Value + (.Cards(4).Value * (10 ^ -8))
               If Is4kind Then .Value = .Value + (.Cards(5).Value * (10 ^ -10))
            End If
 
            If GoFarther Then
               Adder = 0
               For c = 2 To 4   ‘4 of a kind – 4 cards low (AKKKK)
                 If .Cards(c).Value = .Cards(c + 1).Value Then
                     Is4kind = True
                     Adder = Adder + (.Cards(c).Value * 10 ^ (-2 * (c – 1)))
                     .Value = 7 + Adder   ‘max 7.1313131314
                    GoFarther = False
                  Else
                     Is4kind = False
                     GoFarther = True
                     Exit For
                  End If
               Next c
               If Is4kind Then .Value = .Value + (.Cards(5).Value * (10 ^ -8))
               If Is4kind Then .Value = .Value + (.Cards(1).Value * (10 ^ -10))
            End If
 
            If GoFarther Then   ‘Full House tie goes to higher trio
              If .Cards(1).Value = .Cards(2).Value And .Cards(2).Value = .Cards(3).Value Then
                  If .Cards(4).Value = .Cards(5).Value Then
                     .Value = 6 + (.Cards(1).Value * (10 ^ -2))   ‘max 6.14 (AAAKK)
                    GoFarther = False
                  End If
               ElseIf .Cards(3).Value = .Cards(4).Value And .Cards(4).Value = .Cards(5).Value Then
                  If .Cards(1).Value = .Cards(2).Value Then
                     .Value = 6 + (.Cards(3).Value * (10 ^ -2))   ‘max 6.13 (AAKKK)
                    GoFarther = False
                  End If
               End If
            End If
 
            If GoFarther And IsFlush Then   ‘Flushes (AKQJ9)
              For c = 1 To 5
                  Adder = Adder + (.Cards(c).Value * 10 ^ (-2 * c))
                  .Value = 5 + Adder   ‘max 5.1413121109
              Next c
               GoFarther = False
            End If
 
            If GoFarther Then     ‘Straights
              Adder = 0
               For c = 1 To 4   ‘to Ace-high straight (AKQJT)
                 If .Cards(c).Value – 1 = .Cards(c + 1).Value Then
                     IsStraight = True
                     Adder = Adder + (.Cards(c).Value * 10 ^ (-2 * c))
                     .Value = 4 + Adder   ‘max 4.1413121110
                    GoFarther = False
                  Else
                     IsStraight = False
                     GoFarther = True
                     Exit For
                  End If
               

Euler Problem 43

Euler Problem 43 asks:

‘The number, 1406357289, is a 0 to 9 pandigital number because it
‘is made up of each of the digits 0 to 9 in some order, but it also
‘has a rather interesting sub-string divisibility property.

‘Let d_(1) be the 1st digit, d_(2) be the 2nd digit, and so on.
‘In this way, we note the following:

‘    * d_(2)d_(3)d_(4)=406 is divisible by 2
‘    * d_(3)d_(4)d_(5)=063 is divisible by 3
‘    * d_(4)d_(5)d_(6)=635 is divisible by 5
‘    * d_(5)d_(6)d_(7)=357 is divisible by 7
‘    * d_(6)d_(7)d_(8)=572 is divisible by 11
‘    * d_(7)d_(8)d_(9)=728 is divisible by 13
‘    * d_(8)d_(9)d_(10)=289 is divisible by 17

‘Find the sum of all 0 to 9 pandigital numbers with this property.

This problem, over the course of a week, gave me fits. The smallest 0-to-9 pandigital number is 1023456789. The largest is 9876543210. To check every number between them loops 8 billion, 853 million+ times. That’s not doable in “Euler time” of under one minute on a reasonable computer. That’s the “brutest” of brute force approaches, and I knew it was wrong. By inspecting the example case, swapping the 1 and 4 does not change the modulo 2 computation, and swapping the 0 and 6 does not affect the modulo 2 or 3 computations. So, there’s 4 answers there. Turns out there are two more.

It wasn’t until I looked at the problem from inside out that I came up with an approach. The 10th digit has to be whatever is left. The 9th digit has to be whatever is not used in the first 8, and the 8th digit has to be whatever is not used in the first 7, etc. This gave me 10 loops. The following code runs in 20.5 seconds on my MacBookPro running Excel 2002 under Parallels.

Sub Problem_043()
 
   Dim T       As Single
   Dim Answer  As Variant
   Dim d234    As String
   Dim d345    As String
   Dim d456    As String
   Dim d567    As String
   Dim d678    As String
   Dim d789    As String
   Dim d890    As String
   Dim TEMP    As String
   Dim L10     As Long
   Dim L09     As Long
   Dim L08     As Long
   Dim L07     As Long
   Dim L06     As Long
   Dim L05     As Long
   Dim L04     As Long
   Dim L03     As Long
   Dim L02     As Long
   Dim L01     As Long
 
   T = Timer
   For L01 = 1 To 9
      For L02 = 0 To 9
         Select Case L02
            Case L01: GoTo Next_L02
         End Select
         For L03 = 0 To 9
            Select Case L03
               Case L01, L02: GoTo Next_L03
            End Select
            For L04 = 0 To 9
               Select Case L04
                  Case L01, L02, L03: GoTo Next_L04
               End Select
               For L05 = 0 To 9
                  Select Case L05
                     Case L01, L02, L03, L04: GoTo Next_L05
                  End Select
                  For L06 = 0 To 9
                     Select Case L06
                        Case L01, L02, L03, L04, L05: GoTo Next_L06
                     End Select
                     For L07 = 0 To 9
                        Select Case L07
                           Case L01, L02, L03, L04, L05, L06: GoTo Next_L07
                        End Select
                        For L08 = 0 To 9
                           Select Case L08
                              Case L01, L02, L03, L04, L05, L06, L07: GoTo Next_L08
                           End Select
                           For L09 = 0 To 9
                              Select Case L09
                                 Case L01, L02, L03, L04, L05, L06, L07, L08: GoTo Next_L09
                              End Select
                              For L10 = 0 To 9
                                 Select Case L10
                                    Case L01, L02, L03, L04, L05, L06, L07, L08, L09: GoTo Next_L10
                                 End Select
                                 TEMP = L01 & L02 & L03 & L04 & L05 & L06 & L07 & L08 & L09 & L10
                                 d234 = Mid(TEMP, 2, 3)
                                 If CLng(d234) Mod 2 = 0 Then
                                    d345 = Mid(TEMP, 3, 3)
                                    If CLng(d345) Mod 3 = 0 Then
                                       d456 = Mid(TEMP, 4, 3)
                                       If CLng(d456) Mod 5 = 0 Then
                                          d567 = Mid(TEMP, 5, 3)
                                          If CLng(d567) Mod 7 = 0 Then
                                             d678 = Mid(TEMP, 6, 3)
                                             If CLng(d678) Mod 11 = 0 Then
                                                d789 = Mid(TEMP, 7, 3)
                                                If CLng(d789) Mod 13 = 0 Then
                                                   d890 = Mid(TEMP, 8, 3)
                                                   If CLng(d890) Mod 17 = 0 Then
                                                      Answer = CDec(Answer) + CDec(TEMP)
                                                   End If
                                                End If
                                             End If
                                          End If
                                       End If
                                    End If
                                 End If
Next_L10:
                              Next L10
Next_L09:
                           Next L09
Next_L08:
                        Next L08
Next_L07:
                     Next L07
Next_L06:
                  Next L06
Next_L05:
               Next L05
Next_L04:
            Next L04
Next_L03:
         Next L03
Next_L02:
      Next L02
   Next L01
 
   Debug.Print Answer; ”  Time:”; Timer – T
 
End Sub

Loop L01 only has to go from 1 to 9, since there are no leading zeros. The other 9 loops are from 0 to 9. I thank Steve Bullen for writing the Smart Indenter. There nothing above that speaks elegance to me. Looks brutish in it’s own right. I couldn’t noodle out any special significance to the modulo divisors being the first 7 primes.

…mrt

Euler Problem 89

What to do on a snowy night? No, not that! How about solving Euler Problem 89?

Euler 89 asks:

‘The rules for writing Roman numerals allow for many ways of writing each number
‘(see FAQ: Roman Numerals).
‘However, there is always a “best” way of writing a particular number.

‘For example, the following represent all of the legitimate ways of writing the number
‘sixteen:

‘IIIIIIIIIIIIIIII
‘VIIIIIIIIIII
‘VVIIIIII
‘XIIIIII
‘VVVI
‘XVI

‘The last example being considered the most efficient, as it uses the least number of numerals.

‘The 11K text file, roman.txt (right click and ‘Save Link/Target As…’), contains one thousand
‘numbers’written in valid, but not necessarily minimal, Roman numerals; that is, they are
‘arranged in descending units and obey the subtractive pair rule (see FAQ for the definitive
‘rules for this problem).

‘Find the number of characters saved by writing each of these in their minimal form.

‘Note: You can assume that all the Roman numerals in the file contain no more than four
‘consecutive identical units.

The tasks are 5:

  1. Read the file
  2. Parse the Numerals to find the decimal value
  3. Rebuild the Numerals in simplest form
  4. Apply the complete subtractive rules
  5. Use a running total to count keystrokes saved (differences in lengths)

I parsed each numeral from right to left, checking against what was the last operation to determine if the character in question represented an increase or decrease, since the basic rules were followed. Ran in 15 ms. Fast enough. Here’s the code.

Option Explicit
Option Base 1
Sub Problem_089C()
   Const I     As Long = 1
   Const V     As Long = 5
   Const X     As Long = 10
   Const L     As Long = 50
   Const C     As Long = 100
   Const D     As Long = 500
   Const M     As Long = 1000
 
   Dim T       As Single
   Dim Value   As Long
   Dim Answer  As Long
   Dim LastAdd As Long
   Dim Delta   As Long
   Dim TESTstr As String
   Dim TEMPstr As String
   Dim TEMPlng As Long
   Dim j       As Long
   Dim k       As Long
   Dim Romans(1000) As String
   Dim L1      As Long
   Dim L2      As Long
   Dim numIs   As Long
   Dim numVs   As Long
   Dim numXs   As Long
   Dim numLs   As Long
   Dim numCs   As Long
   Dim numDs   As Long
   Dim numMs   As Long
   Const text  As String = “C:DownloadsEuler
oman.txt”

 
   T = Timer
   j = 1
   Open text For Input As #1   ‘1000 lines–Task 1
  Do While Not EOF(1)
      Line Input #1, Romans(j)
      j = j + 1
   Loop
   Close #1
 
   For k = 1 To 1000
      Value = 0
      TEMPstr = Romans(k)
      L1 = Len(TEMPstr)
      LastAdd = 0   ‘to capture what was last addition
     For j = Len(TEMPstr) To 1 Step -1   ‘parsing right to left–Task 2
        Delta = 0   ‘what to add
        TESTstr = Mid(TEMPstr, j, 1)
         Select Case TESTstr
            Case “I”
               If LastAdd > I Then
                  Delta = Delta – I
               Else
                  Delta = Delta + I
               End If
            Case “V”
               If LastAdd > V Then
                  Delta = Delta – V
               Else
                  Delta = Delta + V
               End If
            Case “X”
               If LastAdd > X Then
                  Delta = Delta – X
               Else
                  Delta = Delta + X
               End If
            Case “L”
               If LastAdd > L Then
                  Delta = Delta – L
               Else
                  Delta = Delta + L
               End If
            Case “C”
               If LastAdd > C Then
                  Delta = Delta – C
               Else
                  Delta = Delta + C
               End If
            Case “D”
               If LastAdd > D Then
                  Delta = Delta – D
               Else
                  Delta = Delta + D
               End If
            Case “M”
               Delta = Delta + M
         End Select
         Value = Value + Delta   ‘Value will be the decimal equivalent
        LastAdd = Delta   ‘Delta captured
     Next j   ‘numeral is parsed

      TEMPstr = “”
      TEMPlng = Value   ‘taking Value and rebuilding it in simplest Roman form–Task 3
     numMs = Int(TEMPlng / M)   ‘counting 1000’s
     TEMPlng = TEMPlng – numMs * M
      numDs = Int(TEMPlng / D)   ‘counting 500’s
     TEMPlng = TEMPlng – numDs * D
      numCs = Int(TEMPlng / C)   ‘counting 100’s
     TEMPlng = TEMPlng – numCs * C
      numLs = Int(TEMPlng / L)   ‘counting 50’s
     TEMPlng = TEMPlng – numLs * L
      numXs = Int(TEMPlng / X)   ‘counting 10’s
     TEMPlng = TEMPlng – numXs * X
      numVs = Int(TEMPlng / V)   ‘counting 5’s
     TEMPlng = TEMPlng – numVs * V
      numIs = TEMPlng   ‘1’s are what’s left
     For j = 1 To numMs
         TEMPstr = TEMPstr & “M”
      Next j
      For j = 1 To numDs
         TEMPstr = TEMPstr & “D”
      Next j
      For j = 1 To numCs
         TEMPstr = TEMPstr & “C”
      Next j
      For j = 1 To numLs
         TEMPstr = TEMPstr & “L”
      Next j
      For j = 1 To numXs
         TEMPstr = TEMPstr & “X”
      Next j
      For j = 1 To numVs
         TEMPstr = TEMPstr & “V”
      Next j
      For j = 1 To numIs
         TEMPstr = TEMPstr & “I”
      Next j
      ‘TEMPstr now in simplest form
     ‘applying subtractive rules–Task 4
     TEMPstr = Replace(TEMPstr, “MCCCC”, “MCD”)
      TEMPstr = Replace(TEMPstr, “DCCCC”, “CM”)
      TEMPstr = Replace(TEMPstr, “CCCC”, “CD”)
      TEMPstr = Replace(TEMPstr, “CXXXX”, “CXL”)
      TEMPstr = Replace(TEMPstr, “LXXXX”, “XC”)
      TEMPstr = Replace(TEMPstr, “XXXX”, “XL”)
      TEMPstr = Replace(TEMPstr, “LIIII”, “LIV”)
      TEMPstr = Replace(TEMPstr, “XIIII”, “XIV”)
      TEMPstr = Replace(TEMPstr, “VIIII”, “IX”)
      TEMPstr = Replace(TEMPstr, “IIII”, “IV”)
 
      L2 = Len(TEMPstr)
 
      Answer = Answer + L1 – L2   ‘L1 – L2 is keystokes saved per numeral–Task 5
  Next k
 
   Debug.Print Answer; ”  Time:”; Timer – T
 
End Sub

Those are all “greater thans” and ampersands. I wanted to use the ROMAN() function, but it breaks a some ridiculously low number like 3999. This one took a while. Version 089B stuffed a spreadsheet as a prototype with the value and the rebuilt number, and then deciphered the rebuild to make sure the going in and going out were the same. It also showed where the subtractive rules as implemented might screw up…and cost key strokes. V089A tried to parse in pairs. Don’t go that way…that’s what ate up the day.

…mrt

Euler Problem 52

Well, congratulate me. I’m now an official Euler blockhead. Technically, I’m a Euler Level 2 Cube, but blockhead seems more appropriate ;-) since a Cube is 50 problems below a Level 3 Novice octohedron.

Euler Problem 52 put me over the top, with 50+ problems solved. Problem 52 asks:

‘It can be seen that the number, 125874, and its double, 251748,
‘contain exactly the same digits, but in a different order.

‘Find the smallest positive integer, x, such that 2x, 3x, 4x, 5x,
and 6x, contain the same digits.

Absolute brute force. Little imagination. Going for that blockhead gusto. 4.9 seconds on my MacBook Pro running Parallels. I got tired of the Mac Excel VBE is a big way. It’s stuck at VBA5, and things like SmartIndenter don’t work there. Anyway, here’s my code:

Sub Problem_052()
   Dim i       As Variant
   Dim SAT     As Boolean
   Dim Answer  As String
   Dim T       As Single
   Dim TEMP    As Variant
 
   T = Timer
   SAT = False
   i = 1
   Do
      TEMP = SortString(i)
      If TEMP = SortString(i * 2) Then
         If TEMP = SortString(i * 3) Then
            If TEMP = SortString(i * 4) Then
               If TEMP = SortString(i * 5) Then
                  If TEMP = SortString(i * 6) Then
                     SAT = True
                     Answer = i
                  End If
               End If
            End If
         End If
      End If
      i = i + 1
   Loop Until SAT
 
   Debug.Print Answer; ”  Time:”; Timer – T
 
End Sub

Here’s my SortString() function, which may show some imagination, and gets used again. It places alphanumerics in order. I use it as above to see if strings have the same content, but in a different order.

Function SortString(ByVal str) As String
   Dim i       As Long
   Dim j       As Long
   Dim TEMP    As String * 1
 
   j = 1
   For i = Len(str) – 1 To 1 Step -1
      str = Left(str, 2 * j – 1) & Chr(32) & Right(str, i)
      j = j + 1
   Next i
   
   str = Split(str)
 
   For i = LBound(str) To UBound(str) – 1
      For j = i + 1 To UBound(str)
         If str(i) > str(j) Then
            TEMP = str(j)
            str(j) = str(i)
            str(i) = TEMP
         End If
      Next j
   Next i
 
   For i = LBound(str) To UBound(str)
      SortString = SortString & str(i)
   Next i
 
End Function

No grief about the bubble sort now ;-) . These strings are a dozen or so characters long…

…mrt

Euler Problem 102

Euler Problem 102 asks:

‘Three distinct points are plotted at random on a Cartesian plane, for which
‘-1000 LTE x, y LTE 1000, such that a triangle is formed.

‘Consider the following two triangles:

‘A(-340,495), B(-153,-910), C(835,-947)

‘X(-175,41), Y(-421,-714), Z(574,-645)

‘It can be verified that triangle ABC contains the origin, whereas
‘triangle XYZ does not.

‘Using triangles.txt (right click and ‘Save Link/Target As…’), a 27K text
‘file containing the co-ordinates of one thousand “random” triangles, find
‘the number of triangles for which the interior contains the origin.

‘NOTE: The first two examples in the file represent the triangles in the
‘example given above.

In the above, LTE is “less than or equal”, to outsmart the html bugs.

Any triangle that has all-positive x-coordinates, or all-negative x-coordinates, or similarly all-positive or all-negative y-coordinates, can not contain the origin. A pre-screen will throw all those out. When I first solved this problem, I looked for triangles with both a positive and a negative y-intercept, coupled with both a positive and negative x-intercept, and counted those triangles. That was a successful strategy. LINEST() will return x-intercepts it you swap known-xs and known-ys in the formula. The code looked like this:

YIntercept(3, 1) = Application.WorksheetFunction.Index( _
                            Application.WorksheetFunction.LinEst(Known_Ys, Known_Xs), 2)
 
XIntercept(3, 1) = Application.WorksheetFunction.Index( _
                            Application.WorksheetFunction.LinEst(Known_Xs, Known_Ys), 2)

It’s right out of the Help for LINEST(), or at least the y-intercept part is. However, after I checked my answer in I read another strategy that was just so flat-out neat that I coded it up. It uses Heron’s Law which calculates the area of a triangle based on a calculation of the semi-perimeter, or 1/2 the sum of the sides: The area of a triangle, given the lengths a,b,c of the sides, is A = sqrt s*(s-a)*(s-b)*(s-c), where s is the semiperimeter 0.5*(a+b+c). If we calculate the area of the suspect triangle, and then the area of the three sub-triangles with a common vertex of the origin, if the sum of the sub-triangle areas equals the area of the whole, then the origin must be within the suspect triangle. That code looked like this:

Option Explicit
Option Base 1
Sub Problem_102B()
   Dim Triangle(1000) As Variant
   Dim i       As Long
   Dim j       As Long
   Dim Most    As Long
   Dim Impossible As Boolean
   Dim IsTest  As Boolean
   Dim x(3)    As Long  ‘X Coordinates
  Dim Y(3)    As Long  ‘Y Coordinates
  Dim d(3)    As Double   ‘Distance between points
  Dim O(3)    As Double   ‘Distance from points to (0,0)
  Dim Area    As Double   ‘Triangle area
  Dim a(3)    As Double   ‘Sub-triangle area
  Dim T       As Single
   Dim Answer  As Long
   Const text  As String = “D:DownloadsEuler riangles.txt”
 
   T = Timer
   IsTest = False
   If IsTest Then
      Most = 2
   Else
      Most = 1000
   End If
 
   i = 1
   Open text For Input As #1   ‘1000 lines, comma delimited
  Do While Not EOF(1)
      Line Input #1, Triangle(i)
      Triangle(i) = Split(Triangle(i), “,”)   ‘zero-based array
     i = i + 1
   Loop
   Close #1
 
   For i = 1 To Most
      Impossible = False
 
      x(1) = Triangle(i)(0)   ‘zero-based array
     Y(1) = Triangle(i)(1)
      x(2) = Triangle(i)(2)
      Y(2) = Triangle(i)(3)
      x(3) = Triangle(i)(4)
      Y(3) = Triangle(i)(5)
 
   ‘For Triangles all above or all below the X-axis
     If Y(1) > 0 And Y(2) > 0 And Y(3) > 0 Then Impossible = True
      If Y(1) < 0 And Y(2) < 0 And Y(3) < 0 Then Impossible = True
   ‘For Triangles all to left of or all to right of the Y-axis
     If Not Impossible Then ‘yet
        If x(1) > 0 And x(2) > 0 And x(3) > 0 Then Impossible = True
         If x(1) < 0 And x(2) < 0 And x(3) < 0 Then Impossible = True
      End If
 
      If Impossible Then GoTo Next_i
 
      d(1) = TriSides(CDbl(x(1) – x(2)), CDbl(Y(1) – Y(2)))
      d(2) = TriSides(CDbl(x(2) – x(3)), CDbl(Y(2) – Y(3)))
      d(3) = TriSides(CDbl(x(1) – x(3)), CDbl(Y(1) – Y(3)))
      Area = Heron(d(1), d(2), d(3))
 
      O(1) = TriSides(CDbl(x(1)), CDbl(Y(1)))
      O(2) = TriSides(CDbl(x(2)), CDbl(Y(2)))
      O(3) = TriSides(CDbl(x(3)), CDbl(Y(3)))
      a(1) = Heron(d(1), O(1), O(2))
      a(2) = Heron(d(2), O(2), O(3))
      a(3) = Heron(d(3), O(1), O(3))
 
      If CSng(Area) = CSng(a(1) + a(2) + a(3)) Then
         Answer = Answer + 1
      End If
 
Next_i:
   Next i
 
   Debug.Print Answer; ”  Time:”; Timer – T
End Sub

Those are the escape code for > and < . Can’t figure out how to get it right inside the vb blocks. We can escape it outside, but we truncate if we use the angle brackets, and don’t render the brackets right if we escape. It’s been a conundrum for a while.

I used two functions, TriSides(), which returns the square root of the sum or difference of two squares, so I could do Pythagorean math, and Heron(), which implements Heron’s law:

Function TriSides(a As Double, b As Double, Optional Sum)
   If IsMissing(Sum) Then Sum = True
   If Sum Then
      TriSides = Sqr(a ^ 2 + b ^ 2)
   Else
      TriSides = Sqr(Abs(a ^ 2 – b ^ 2)) ‘ order doesn’t matter
  End If
End Function
 
Function Heron(a As Double, b As Double, C As Double) As Double
‘Use Heron ‘s formula for the area of a triangle
‘given the lengths a,b,c of the sides
‘A = sqrt s*(s-a)*(s-b)*(s-c)
‘where s is the semiperimeter 0.5*(a+b+c).

   Dim s       As Double
   s = 0.5 * (a + b + C)
   Heron = Sqr(s * (s – a) * (s – b) * (s – C))
End Function

I admire those who saw the application of Heron going in. One thing that I don’t understand is why I had to convert doubles to singles at the end for the areas to total per Heron. I presume is has to do with the square root routine, but I invite comment. Code ran in one-tenth the time of 102A, at about .01 seconds.

…mrt

Euler Problem 22

Euler Problem 22 asks:

‘Using names.txt (right click and ‘Save Link/Target As…’), a 46K text file containing over
‘five-thousand first names, begin by sorting it into alphabetical order. Then working out the
‘alphabetical value for each name, multiply this value by its alphabetical position in the list
‘to obtain a name score.

‘For example, when the list is sorted into alphabetical order, COLIN, which is worth
‘3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. So, COLIN would obtain a score of
‘938 * 53 = 49714.

‘What is the total of all the name scores in the file?

The general task is to time the calculation.
The specific tasks are:

  1. Open the file
  2. Clean it up (it’s one long line of data, with names wrapped in quotes, and comma-delimited, as in …,”COLIN”,…)
  3. Sort the names
  4. Determine each name’s alphabetical value
  5. Multiply the position by the value
  6. Sum the scores

Here is my code:

Option Explicit
Sub Problem_022()
Dim NameArray As Variant
   Dim TEMP    As String
   Dim T       As Single
   Dim i       As Long
   Dim j       As Long
   Dim Score   As Long
   Dim Answer  As Long
   Const namestext As String = “D:DownloadsEuler
ames.txt”

 
   T = Timer ‘start timing
  Open namestext For Input As #1 ‘ open the file
  Do While Not EOF(1)
      Line Input #1, TEMP
   Loop
   Close #1
   TEMP = VBA.Replace(TEMP, Chr(34), vbNullString) ‘strip quotes — chr(34)

   NameArray = Split(TEMP, “,”)   ‘TEMP a comma delimited file, split on the comma
  ‘creating an array to sort
  ‘BubbleSort
  For i = LBound(NameArray) To UBound(NameArray) – 1
      For j = i To UBound(NameArray)
         If NameArray(i) &gt; NameArray(j) Then
            TEMP = NameArray(j)
            NameArray(j) = NameArray(i)
            NameArray(i) = TEMP
         End If
      Next j
   Next i
 
   For i = LBound(NameArray) To UBound(NameArray)
      Score = LexValue(CStr(NameArray(i))) ‘computes the alphabetic value
     Answer = Answer + (Score * (i + 1))   ‘ NameArray is zero-based
     ‘multiplies and sums
     Score = 0
   Next i
 
   Debug.Print Answer; ”  Time:”; Timer – T
 
End Sub

NameArray is zero-based. Euler’s names aren’t. The first name has position 1. We need to offset by 1.
The alphabetic value is just the sum of the ascii codes (offset by 64, so “A” gets 1) for each letter. This little function does that. It’ll be used in other Euler problems.

Function LexValue(Word As String) As Long
   Dim i       As Long
   For i = 1 To Len(Word)
      LexValue = LexValue + (Asc(Mid(Word, i, 1)) – 64)
   Next i
End Function

This runs in 15 seconds
…mrt

Bubble Sorts

Two problems so far, Eulers 22 and 29, have required sorts, at least for my implementations. Euler 22 sorts over 5000 name strings, and Euler 29 sorts nearly 10,000 numerics. In both cases I used a bubble sort. Bubble sorts get their name from the image of the greater-valued item “bubbling up” to its place in line.

Bubble sorts have some advantages and disadvantages. The BIG disadvantage is that no matter how nearly-sorted the list is at start, you will still go through it (n-1)^2 times, n being the number of items. The advantages of bubble sorts are:

  1. They’re easy to code. Typically just nine lines. And
  2. To make a descending sort, you reverse just one inequality

To do a bubble sort, you need three things:

  1. An indexed list or array with a sortable value or property
  2. Explicit or implicit knowledge of the count or quantity of items to sort. You may know the count (n) because you set it, or the computer knows it via Item.count or UBound(Item), for example
  3. A TEMP variable of the same type as being sorted, often also called SWAP

If you think of sorting a deck of cards, the outer loop sorts cards from 1 to 51, and the inner loop compares those values with cards from 2 to 52. With fast computers, bubble sorts are probably “fast enough.” Both problems for me took less than 20 seconds.

This is my implementation of a bubble sort. You’ll see it used in the next post, on Euler 22.

Sub BubbleItUp()   ‘an ascending sort

   Const n     As Long = 5
   Dim i       As Long
   Dim j       As Long
   Dim Char(n) As String * 1
   Dim TEMP    As String * 1
 
   For i = 1 To n
      Char(i) = CStr(n + 1 – i)
   Next i
 
   Debug.Print Char(1); Char(2); Char(3); Char(4); Char(5)
   For i = 1 To n – 1
      For j = i + 1 To n
         If Char(i) &gt; Char(j) Then   ‘flip the inequality for a descending sort
           TEMP = Char(j)
            Char(j) = Char(i)
            Char(i) = TEMP
         End If
      Next j
      Debug.Print Char(1); Char(2); Char(3); Char(4); Char(5)
   Next i
 
End Sub

…mrt

Euler Problem 19

Euler Problem 19 asks:

‘You are given the following information, but you may prefer to do some
‘research for yourself.

‘    * 1 Jan 1900 was a Monday.
‘    * Thirty days has September,
‘      April, June and November.
‘      All the rest have thirty-one,
‘      Saving February alone,
‘      Which has twenty-eight, rain or shine.
‘      And on leap years, twenty-nine.
‘    * A leap year occurs on any year evenly divisible by 4, but not on a
‘century unless it is divisible by 400.

‘How many Sundays fell on the first of the month during the twentieth
‘century (1 Jan 1901 to 31 Dec 2000)?

Wow. First thought: Euler actually got the century right. Math-minded indeed. Second thought: I need a day counter, a week counter, a month counter, a leap year checker. That’s a lot of conditionals. No wonder this is here. Third thought: I’m doing this in Excel. Piece of cake. This may be the only one aimed right at us, if not intentionally. Here’s my code. Ran in under a second.

Sub Problem_019()  
   Dim Start   As Date
   Dim Answer  As Long
   Dim T       As Single
 
   T = Timer
   Start = DateSerial(1901, 1, 1)
 
   Do While Start &lt;  DateSerial(2001, 1, 1)
      If Weekday(Start) = vbSunday And Day(Start) = vbSunday Then
         Answer = Answer + 1
      End If
      Start = Start + 1
   Loop
 
   Debug.Print Answer; ”  Time:”, Timer – T
 
End Sub

Coded it up. It ran the first time, and I checked in with the right answer. I was feeling so good until I saw the pencil and paper approach of those who’d solved it …100 years with 12 months per year over 7 days

…mrt