Books Algorithm Dealing with Square Digit Sums A little context:


  
  Book has 411 Pages
  Read a random number of pages on the first day which is unknown
  Number of pages to read next day is the square of the sum of the digits of the page currently on
  
  I.e. on Page 36: read 81 pages next day
  On day 6 The square answer directly adds to current page number to make 411
  Finish Book
  
  
  How many pages did she read on the first day and how many pages did
  she read on the 6 days?


This is what I have so far:

from itertools import accumulate, count

def read_book( start ):
    for i in range( 6 ): yield start; start += sum( int( digit ) for digit in str( start ) ) ** 2

def find_start( pages ):
    for start in count( 1 ):
        for read in read_book( start ): pass
        if read == pages: return start

previous_answer, start = 0, find_start( 411 )
for i, read in enumerate( read_book( start ), 1 ):
    print( "Pages read on day", i ,"was", read-previous_answer ,"\t(Now on page", str(read) + ")\n")
    previous_answer += read-previous_answer


Output:


  Pages read on day 1 was 61 (Now on page 61)
  
  Pages read on day 2 was 49 (Now on page 110)
  
  Pages read on day 3 was 4 (Now on page 114)
  
  Pages read on day 4 was 36 (Now on page 150)
  
  Pages read on day 5 was 36 (Now on page 186)
  
  Pages read on day 6 was 225 (Now on page 411)


Is there a more efficient way of writing this using fewer lines of code?
Can someone see if there is a more efficient way of writing this using fewer lines of code?


I suppose your focus is not literally on the part about "fewer lines of code".
You could use more semicolons to squeeze more lines onto a single line,
but that would violate PEP 20, the Zen of Python,
even more than it already does.

Your current solution is essentially a brute force algorithm,
counting up from the beginning until you find a suitable start page from which the last page will be reached on Day 6.
The algorithm is well implemented,
and I don't know a better way.
I can suggest some other improvements though.

Coding style

You should follow PEP8, the Python coding style guide.
In particular:


Do break lines in loops
Avoid using semicolon to separate multiple statements
Don't put spaces around method parameters, for example write read_book(start) instead of read_book( start )


Generalize

It would be nice to make the implementation more general.

For example, the target pages 411 and the required number of days would be nice to put in constants, to make them easier to play with and less magic.

Also,
instead of executing code in the global namespace,
the best practice is wrap your main code in a main() method,
and call that from within if __name__ == '__main__':, like this:

def main():
    # main implementation

if __name__ == '__main__':
    main()


Robustness

The find_start method counts up from 1 until the target page is reached,
or else...forever.
It would better to set an upper limit, like this:

def find_start(pages):
    for start in range(1, pages):
        read = start
        for read in read_book(start):
            pass
        if read == pages:
            return start
    raise ValueError("Impossible to reach page {} exactly".format(pages))


Suggested implementation

Putting the above suggestions together:

PAGES = 411
DAYS_TO_READ = 6


def read_book(start):
    for _ in range(DAYS_TO_READ):
        yield start
        start += sum(int(digit) for digit in str(start)) ** 2


def find_start(pages):
    for start in range(1, pages):
        read = start
        for read in read_book(start):
            pass
        if read == pages:
            return start
    raise ValueError("Impossible to reach page {} exactly".format(pages))


def main():
    previous_answer, start = 0, find_start(PAGES)
    for i, read in enumerate(read_book(start), 1):
        print("Pages read on day {} was {}\t(Now on page {})\n".format(
            i, read - previous_answer, read))
        previous_answer += read - previous_answer


if __name__ == '__main__':
    main()