Euler Problem 54 asks:
‘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:
- Open the file
- Populate the hands
- Populate the cards and assign values
- Sort the cards to recognize the patterns
- Score the hands
- 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 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:
‘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.
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:
‘(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:
- Read the file
- Parse the Numerals to find the decimal value
- Rebuild the Numerals in simplest form
- Apply the complete subtractive rules
- 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 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:
‘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:
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.
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:
‘-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:
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 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:
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:
‘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:
- Open the file
- Clean it up (it’s one long line of data, with names wrapped in quotes, and comma-delimited, as in …,”COLIN”,…)
- Sort the names
- Determine each name’s alphabetical value
- Multiply the position by the value
- Sum the scores
Here is my code:
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) > 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.
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:
- They’re easy to code. Typically just nine lines. And
- To make a descending sort, you reverse just one inequality
To do a bubble sort, you need three things:
- An indexed list or array with a sortable value or property
- 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
- 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.
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) > 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:
‘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.
Dim Start As Date
Dim Answer As Long
Dim T As Single
T = Timer
Start = DateSerial(1901, 1, 1)
Do While Start < 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