CodeEval Justify the Text

For each string of text given in input, format it so that it cover the full size of the line, given as 80. The space between words should be always the same, the extra amount of blanks should go first to the right. We don't have to reformat the last part of the string.
This is CodeEval problem #177, and here you are going to see my Python 3 solution.

I have converted the given sample in a python unit test, adding an extra test, that gave me problems at my first solution and caused the partial rejection of my code:
def test_provided_1(self):
    self.assertEqual('Hello, World!', solution('Hello, World!'))

def test_provided_2(self):
    text_in =  'The precise 50-digits value of Pi is 3.14159265358979323846264338327950288419716939937510.'
    text_out = 'The         precise         50-digits        value        of        Pi        is\n' \
    self.assertEqual(text_out, solution(text_in))

def test_provided_3(self):
    text_in = 'But he who would be a great man ought to regard, not himself or his interests, but what is just,' \
              ' whether the just act be his own or that of another. Next as to habitations. Such is the tradition.'
    text_out = 'But  he  who would be a great man ought to regard, not himself or his interests,\n' \
               'but what is just, whether the just act be his own or that of another. Next as to\n' \
               'habitations. Such is the tradition.'
    self.assertEqual(text_out, solution(text_in))

def test_extra(self):
    text_in = 'This is the language of naval warfare, and is anything but worthy of extraordinary praise.'
    text_out = 'This  is  the  language  of  naval  warfare,  and  is  anything  but  worthy  of\n' \
               'extraordinary praise.'
    self.assertEqual(text_out, solution(text_in))
I approached the problem considering each time a chunk of string, the biggest one possible accordingly to the line size (80). Then I reformatted that chunk accordingly to the requirements, and put this line in a result list. Finally, if there are more words, I push them as last line in the result. And it is just a matter of joining the result to give it back to the caller:
def solution(text):
    result = []
    while len(text) > 80:
        pos = 0

        # ...

        text = text[pos + 1:]

    if text:
    return '\n'.join(result)
I left out the code in the while loop. I need to find where is the "pos" that I use to split the current chunk and so reducing the text given in input, and push to result the formatted line.

Finding where to split

Assuming that the separator between words is just a plain blank, it is easy to loop on the text looking for the one closer to the limit:
pos = 0
while True:
    next_pos = text.find(' ', pos + 1)
    if next_pos == -1 or next_pos > 80:
    pos = next_pos
line = text[0:pos]
Remember that the python string find() method returns the position where the passed token is found, or minus one for not found. As second parameter it is possible to pass where to start to search for it.
Looping I have found the splitting point, so I can extract "line" from "text".

Formatting the line

The general meaning of this part is not complicated, however I spent some time to get the current settings here and tweaking with the settings to get it right. Here a good set of test is an invaluable support.
words = line.split() # 1
blanks = 80 - len(line) + len(words) # 2
size = (blanks // (len(words) - 1)) # 3
extra = blanks % (len(words) - 1) - 1 # 4
for i in range(extra):
    words[i] += ' '
result.append((' ' * size).join(words))
1. This is easy. I get a list of all the words in the line.
2. I calculate how many blanks I need to add to the words to get the expected length.
3. I am going to add as separator between words a block of blanks. Here I am calculating its size.
4. A few words at the beginning of the line may have an extra blank. Here I see how many of them, and then I add a blank to each of them.

CodeEval accepted my solution with full marks. I pushed the test case and the python script to GitHub.

No comments:

Post a Comment