Number of words from the end of the list on a line

Viewed 62

Given a list of words, for example, W = ["one", "two", "cup", "zombie"] and the length of a line L, I need to find out how many words starting from the end of the list I can place on that line assuming that there is one space between each two words. For example, for this W and L = 13 the answer is 2, because len("zombie") + space + len("cup") = 10, and if we add "two", we need to add len("two") + space which will make up to 14 characters.

Here's my solution:

def words_last_line(L, W):
    n_words = 0 # number of words that can be placed on the line
    num_char = 0 # number of characters including spaces that can be placed on the line
    for i in range(len(W) - 1, -1, -1):
        num_char += len(W[i]) + 1  # 'add' the current word
        n_words += 1               # increase the number of words
        if num_char > L:           # we exceed the number of characters in L
            num_char -= 1          # maybe it is because of the space
        if num_char > L:           # we still exceed
            n_words -= 1           # the last word we added does not belong to the line
            break                  # break the loop
    return n_words

Description: I traverse the list backwards and each step I add a word and potential space. If I exceed L I first 'remove' that space (subtract 1 from the total number of characters that are in my potential line) and if I still exceed L it means that the last word I counted does not fit the line and I need to break the loop and return the word.

Questions: 1. What would be a more Pythony way of doing it? 2. I have if num_char > L following the same line and I have a hunch that it is not good coding. How can I fix that?

4 Answers
  1. You could iterate over W[::-1] instead of over the range and then using W[i]. Then do
for word in W[::-1]:
   num_char = len(word) + 1
   ...
  1. No, nothing really wrong with it. It does what you need it to do, and your comment makes it clear why there's a seemingly repeated if condition: there isn't, because num_char was changed between the two ifs

Something like this maybe?

def words_last_line(length, words):
    return len(next(group for group in (words[i:] for i in range(0, len(words))) if len(" ".join(group)) <= length))

print(words_last_line(13, ["one", "two", "cup", "zombie"]))

Output:

2
>>> 

Here's an alternative that makes use of itertools

W = ["one", "two", "cup", "zombie"]                                                                                                                                                                                                        
L = 10                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                           
# Figure out the length of each word                                                                                                                                                                                                       
lengths = [len(w) for w in W]                                                                                                                                                                                                              
                                                                                                                                                                                                                                           
# Need to account for spaces between words so add one                                                                                                                                                                                      
# to the length of each word except for the last                                                                                                                                                                                           
lengths[:-1] = [w + 1 for w in lengths[:-1]]                                                                                                                                                                                               
                                                                                                                                                                                                                                           
# Accumulate the lengths in reverse                                                                                                                                                                                                        
acc = list(itertools.accumulate(reversed(lengths)))                                                                                                                                                                                        
                                                                                                                                                                                                                                           
# Figure out how many words to take from the end                                                                                                                                                                                           
num_words = len(list(itertools.takewhile(lambda w: w <= L, acc)))                                                                                                                                                                          
                                                                                                                                                                                                                                           
# Take the number of words from the end of the list                                                                                                                                                                                        
print(W[-num_words:])                           

I don't know if this can be considered more pythonic, but it's a clean way of doing it:

def words_last_line(L, W):
    W = W[::-1]
    L += 1
    for i in range(len(W)):
        L -= len(W[i]) + 1
        if L <= 0:
            return len(W) - i

W = ["one", "two", "cup", "zombie"]
L = 13
print(words_last_line(L,W))
#2

Note that I'm not calculating the length of each word like other answers posted here.

Related