Project Euler problem 104 is to write an algorithm to determine the first Fibonacci number for which the first 9 digits are 1-9 pandigital and the last 9 digits are also pandigital, where pandigital means that these digits are a permutation of 1 to 9.
Because the answer is going to be a very large number the BigInt
class is the most convenient representation for storing and manipulating these numbers. Although the compute time and the amount of storage required will be dominated by the operations of adding, storing and converting these numbers to strings of digits, it is still worthwhile being careful how we create and store the Fibonacci numbers.
An obvious approach to generating the numbers in the Fibonacci sequence is to store the numbers in a vector, generating the i’th value from the previous two values and pushing it onto the end of the vector. On a modern computer this approach will be fine for a few hundred or a few thousand values but it will slow down terribly when requiring hundreds of thousands of Fibonacci numbers, as this problem does.
Consider instead storing the last two values in the sequence and updating them. The current state, stored as a vector of two BigInt
s, is initialized to a pair of ones and thereafter each step involves replacing the second last number by the sum of these two.
One way to do this is to shift the two values on every update, as in
function nextfib(st::Vector{BigInt})
n = st[1] + st[2]
st[1] = st[2]
st[2] = n
n
end
However, we may want to store the index into the sequence along with the value, in which case we could represent the state as a user-defined type.
type FibState
i::Int
s::Vector{BigInt}
FibState() = new(2,ones(BigInt,2))
end
The function FibState
defined within the FibState
type is an internal constructor. It is used to initialize the state to the second index with both the first and second values being 1, represented as a BigInt
.
When we increment a FibState
object we can alternate between replacing the first and the second values in the vector s
, according to the value of i
. We also provide extractors for the value and the index.
function increment(st::FibState)
s = st.s
st.i += 1
s[isodd(st.i) ? 1 : 2] = s[1] + s[2]
st
end
index(st::FibState) = st.i
value(st::FibState) = st.s[isodd(st.i) ? 1 : 2]
Let’s check that this works as intended.
st = FibState()
while index(increment(st)) <= 20
println(index(st),": ",value(st))
end
We are very close to having an iterator
which is a type that provides methods for length
, start
, next
and done
but we will leave that for another time
Methods for the string
function produce character strings from other types of objects. We can add a string
method for the FibState
type.
import Base.string # so we can add string methods
string(st::FibState) = string(value(st))
string(st) # recall that index is now 21
The string itself consists of an vector of bytes (the type Uint8
).
names("10946")
typeof("10946".data)
We could use the int
function to convert the digits in a string to integers but it is a bit cleaner to subtract the character representation of 0
from the elements.
show("10946".data - '0')
Next we need to determine if a vector of 9 unsigned integers constitutes a permutation. The isperm
function could be used to do this but if we are going to do so hundreds of thousands of times we may wish to be more concise.
We’ll show the function then discuss it.
function isdigitperm(A::Vector{Uint8})
(length(A) == 9) || return false
used = falses(9)
for a in A
d = a - '0'
(0 < d < 10) && (used[d] $= true) || return false
end
true
end
This special-purpose function checks a vector of bytes to determine if its length is exactly 9 and every byte is in the range ‘1’ to ‘9’ and no value in the range ‘1’ to ‘9’ occurs more than once. The expression
used[d] $= true
replaces used[d]
by the exclusive or of used[d]
and true
. In other words, it negates the logical value and stores the result back into the location at the same time. The first time a digit is flagged as used, the expression returns true
. The second time a digit is used it returns false
.
function first9pandigital()
st = FibState()
while length(string(increment(st))) < 9 end
while !isdigitperm(string(st).data[1:9])
increment(st)
end
st
end
first9pandigital()
@time first9pandigital();
Notice that we time the second execution of the function. The first execution can be much slower than subsequent executions because the function is compiled the first time it is called with an argument signature. (In the the case of first9pandigital
there is only one method definition and that is for an empty argument list.)
Instead of showing these potentially very large numbers, we create a show
method for the FibState
type that prints an abbreviated version.
import Base.show # so we can add show methods
function show(io::IO, st::FibState)
s = string(st)
println(io, "Fib[", st.i,"] = ",s[1:10],"...",s[end-9:end],
", ", length(s), " decimal digits")
end
show(first9pandigital())
function last9pandigital()
st = FibState()
while length(string(increment(st))) < 9 end
while !isdigitperm(string(st).data[end-8:end])
increment(st)
end
st
end
show(last9pandigital())
@time last9pandigital();
Finally, create a function that allows for checking either the first 9 digits or the last 9 digits or both.
function fibpandigit(first::Bool,last::Bool)
st = FibState(); while length(string(increment(st))) < 9 end
while !((!first || isdigitperm(string(st).data[1:9])) &&
(!last || isdigitperm(string(st).data[end-8:end])))
increment(st)
end
st
end
show(fibpandigit(true,false)) # should be same as first9pandigital
show(fibpandigit(false,true))
@time res = fibpandigit(true,true);
show(res)
Project Euler problem 104 is to write an algorithm to determine the first Fibonacci number for which the first 9 digits are 1-9 pandigital and the last 9 digits are also pandigital, where pandigital means that these digits are a permutation of 1 to 9.
Because the answer is going to be a very large number the BigInt
class is the most convenient representation for storing and manipulating these numbers. Although the compute time and the amount of storage required will be dominated by the operations of adding, storing and converting these numbers to strings of digits, it is still worthwhile being careful how we create and store the Fibonacci numbers.
An obvious approach to generating the numbers in the Fibonacci sequence is to store the numbers in a vector, generating the i’th value from the previous two values and pushing it onto the end of the vector. On a modern computer this approach will be fine for a few hundred or a few thousand values but it will slow down terribly when requiring hundreds of thousands of Fibonacci numbers, as this problem does.
Consider instead storing the last two values in the sequence and updating them. The current state, stored as a vector of two BigInt
s, is initialized to a pair of ones and thereafter each step involves replacing the second last number by the sum of these two.
One way to do this is to shift the two values on every update, as in
function nextfib(st::Vector{BigInt})
n = st[1] + st[2]
st[1] = st[2]
st[2] = n
n
end
However, we may want to store the index into the sequence along with the value, in which case we could represent the state as a user-defined type.
type FibState
i::Int
s::Vector{BigInt}
FibState() = new(2,ones(BigInt,2))
end
The function FibState
defined within the FibState
type is an internal constructor. It is used to initialize the state to the second index with both the first and second values being 1, represented as a BigInt
.
When we increment a FibState
object we can alternate between replacing the first and the second values in the vector s
, according to the value of i
. We also provide extractors for the value and the index.
function increment(st::FibState)
s = st.s
st.i += 1
s[isodd(st.i) ? 1 : 2] = s[1] + s[2]
st
end
index(st::FibState) = st.i
value(st::FibState) = st.s[isodd(st.i) ? 1 : 2]
Let’s check that this works as intended.
st = FibState()
while index(increment(st)) <= 20
println(index(st),": ",value(st))
end
We are very close to having an iterator
which is a type that provides methods for length
, start
, next
and done
but we will leave that for another time
Methods for the string
function produce character strings from other types of objects. We can add a string
method for the FibState
type.
import Base.string # so we can add string methods
string(st::FibState) = string(value(st))
string(st) # recall that index is now 21
The string itself consists of an vector of bytes (the type Uint8
).
names("10946")
typeof("10946".data)
We could use the int
function to convert the digits in a string to integers but it is a bit cleaner to subtract the character representation of 0
from the elements.
show("10946".data - '0')
Next we need to determine if a vector of 9 unsigned integers constitutes a permutation. The isperm
function could be used to do this but if we are going to do so hundreds of thousands of times we may wish to be more concise.
We’ll show the function then discuss it.
function isdigitperm(A::Vector{Uint8})
(length(A) == 9) || return false
used = falses(9)
for a in A
d = a - '0'
(0 < d < 10) && (used[d] $= true) || return false
end
true
end
This special-purpose function checks a vector of bytes to determine if its length is exactly 9 and every byte is in the range ‘1’ to ‘9’ and no value in the range ‘1’ to ‘9’ occurs more than once. The expression
used[d] $= true
replaces used[d]
by the exclusive or of used[d]
and true
. In other words, it negates the logical value and stores the result back into the location at the same time. The first time a digit is flagged as used, the expression returns true
. The second time a digit is used it returns false
.
function first9pandigital()
st = FibState()
while length(string(increment(st))) < 9 end
while !isdigitperm(string(st).data[1:9])
increment(st)
end
st
end
first9pandigital()
@time first9pandigital();
Notice that we time the second execution of the function. The first execution can be much slower than subsequent executions because the function is compiled the first time it is called with an argument signature. (In the the case of first9pandigital
there is only one method definition and that is for an empty argument list.)
Instead of showing these potentially very large numbers, we create a show
method for the FibState
type that prints an abbreviated version.
import Base.show # so we can add show methods
function show(io::IO, st::FibState)
s = string(st)
println(io, "Fib[", st.i,"] = ",s[1:10],"...",s[end-9:end],
", ", length(s), " decimal digits")
end
show(first9pandigital())
function last9pandigital()
st = FibState()
while length(string(increment(st))) < 9 end
while !isdigitperm(string(st).data[end-8:end])
increment(st)
end
st
end
show(last9pandigital())
@time last9pandigital();
Finally, create a function that allows for checking either the first 9 digits or the last 9 digits or both.
function fibpandigit(first::Bool,last::Bool)
st = FibState(); while length(string(increment(st))) < 9 end
while !((!first || isdigitperm(string(st).data[1:9])) &&
(!last || isdigitperm(string(st).data[end-8:end])))
increment(st)
end
st
end
show(fibpandigit(true,false)) # should be same as first9pandigital
show(fibpandigit(false,true))
@time res = fibpandigit(true,true);
show(res)
Similar Notebooks