# Slicing a string into three pieces, and also controlling/manipulating through loops

# Filename: P05_Strings.py
# Author: Joseph Chua
# Date: 10/18/2016
# Purpose: Slicing a string into THREE pieces.
# Other: http://repeatercreeper.me

#=======================================================================
# Logic - Assignment #1
#=======================================================================
userInp = input("Enter Here: ")

while (len(userInp) % 3) != 0: # Keep adding whitespace until divisible by 3
userInp += " " # Concatenation of whitespace

def split(str, num):
return [ str[i:i+num] for i in range(0, len(str), num) ]

print ("Original String: " + userInp)
# Creates 3 variables to store each slices. Sliced using 'split' function.
slice1, slice2, slice3 = split(userInp, int(len(userInp) / 3))

# Prints the results in THREE slices.
print("Slice #1: " + slice1)
print("Slice #2: " + slice2)
print("Slice #3: " + slice3)

#=======================================================================
# Logic - Assignment #2
#=======================================================================
quote = input("Enter a quote: ")
i = 0

if (len(quote.split(' '))) >= 2:
splitQuote = quote.split(' ')
while i != 3:
if i == 0:
print("Split: ", splitQuote)
elif i == 1:
ccString = ""
for j in range(0, len(splitQuote)):
ccString += splitQuote[j]
print("Concatinate: ", ccString)
else:
charUse = ","
print("Join: ", charUse.join(splitQuote))

i += 1
else:
print("You entered an invalid quote!")


I'm mostly looking at my Assignment #2. It looks like it has plenty of room for improvements, but if you find something to improve on Assignment #1 to make it more concise that would be amazing.

So for Assignment #2 objective was as follows (copy & paste directly from the book):

Activity 2: Write the Python code to split, join, and concatenate a string.
• a) Allow the user to input a string (a famous quote) that will be manipulated within a While loop.
• a. Print the string that was entered.
• b) Within a WHILE loop that iterates three (3) times, manipulate the string.
1. If the loop iteration is the first time, split the quote into separate words and print the result of the split operation.
2. If the loop iteration is the second time, concatenate the split string (list of words) in an order you choose and print the result of the concatenation operation.
3. If the loop iteration is the third time, join the split string content (list of words) with a character(s) of your choosing and print the result of the join operation.
• The assignment makes little sense. A loop that runs three times, doing three different things, should not be written as a loop. Oct 28 '16 at 4:43
• @200_success Yeah that's what I thought as well. Oct 28 '16 at 23:11

I concur with @200_success, looping three times and doing something different every loop is totally pointless. Regardless, if I had to use a loop for it, I would use for i in range(3) and not a while loop to do it.

I do not agree with your if (len(quote.split(' '))) >= 2:. For me this is a totally legit quote:

Awesome!

-- Barney Stinson, How I Met Your Mother

Also, the split is done twice here, at least do it before the check.

And actually, all of your code does not care if the length is 1 or even 0:

# Length 1
>>> "Awesome!".split()
["Awesome!"]
>>> " ".join(["Awesome!"])
"Awesome!"
#Length 0
>>> "".split()
[]
>>> " ".join([])
''


Note that str.split splits on white-space by default, so there is no need to do split(" "), unless you specifically do not want to also split on newlines. Incidentally, I would consider this a bug in your code. The quote Awesome\nSuit! would be recognized as one word by your code.

The only part where it is not obvious that a length 0 quote is fine is

ccString = ""
for j in range(0, len(splitQuote)):
ccString += splitQuote[j]
print("Concatinate: ", ccString)


But range(0, 0) returns [] and a for loop over an empty list does nothing. So this is also safe. And I would suggest a different approach here, anyways.

This code is equivalent to print("Concatinate: ", "".join(splitQuote). But that's really boring and just a special case of the next loop, with an empty character. I think using random.sample would be slightly more interesting:

import random
...
print("Concatinate: ", "".join(random.sample(splitQuote, len(splitQuote))


To improve maintainability and readability, I would put the two assignments into separate functions, where the docstring describes what the assignment was. Then I would add:

if __name__ == '__main__':
assignment1()
assignment2()


Last, but not least, you should have a look at PEP8, Python's official style-guide. One thing it recommends is using lower_case for variables (and functions) and not camelCase.

Since @Graipher gave good advices for assignment 2, I’m going to focus on assignment 1.

You don't provide the details of the assignment, but reading at the comments and the code, we can conclude that you need to split a user-provided string into 3 equal pieces. Now what do you do when the length of said string is not divisible by 3? You pad with extra spaces.

First of, since you use slices, it's absolutely not necessary:

>>> 'test'[:10000]
'test'


Second, I find it way too unbalanced in case the length of the string is of the form $3\times n+1$: two of the slices are 2 characters longer than the third one, where you could have had only one of the slices being 1 character longer than the other ones. But I’ll get to it later.

Now it's time to write an efficient splitter function. Let's write it in a way that the provided string is divisible by the number of required chunks. But most importantly, let's write it that accept the number of chunks as parameters, and not relly on the caller to compute it. And don't forget to document it:

def split(sentence, num_chunks):
"""Split the given sentence into num_chunk pieces.

If the length of the sentence is not exactly divisible by
"""

chunk_size = len(sentence) // num_chunks
return [
sentence[chunk_size * i:chunck_size * (i+1)]
for i in range(num_chunks)
]


We can then use this function to perform the assignment:

def assigment1():
"""Ask the user a sentence and slice it in 3 equal parts.

Print each part individually.
"""

sentence = input('Enter a sentence: ')
print('Original sentence:', sentence)
for num, slice in enumerate(split(sentence, 3), 1):
print('Slice #', num, ': ', slice, sep='')


You'll note that I used a for loop instead of 3 separate variables: it allows to easily change the number of chunks without changing the whole code. In fact, it would probably be better to let this number be a parameter of the function with a default value of 3:

def assigment1(num_chunks=3):
"""Ask the user a sentence and slice it in num_chunks equal parts.

Print each part individually.
"""

sentence = input('Enter a sentence: ')
print('Original sentence:', sentence)
for num, slice in enumerate(split(sentence, num_chunks), 1):
print('Slice #', num, ': ', slice, sep='')


Lastly, we are going to take into account the extra letters in split instead of discarding them. As a first approximation, to bring back your behaviour, let me introduce divmod which, as its name suggest, returns both the result of the division and the remainder of said division:

def split(sentence, num_chunks):
"""Split the given sentence into num_chunk pieces.

If the length of the sentence is not exactly divisible by
num_chunks, the last slice will be shorter.
"""

chunk_size, remainder = divmod(len(sentence), num_chunks)
if remainder:
chunk_size += 1

return [
sentence[chunk_size * i:chunck_size * (i+1)]
for i in range(num_chunks)
]


And build upon that to balance things a bit, since splitting a 16-characters long sentence into 5 parts would yield 4 slices of length 4 and an empty slice. The key idea is to use slices of varying lengths. The first ones should be 1 character longer than the last ones. How many exactly? The remainder of the division of the length by the number of chunks is what we need:

size, remainder = divmod(len(sentence), num_chunks)
chunks_sizes = [size + 1] * remainder + [size] * (num_chunks - remainder)


Should get us started. To figure out what is in chunks_sizes, consider len(sentence) to be of the form $\text{num_chunks}\times n + m$. So chunks_sizes will be $n+1$, $m$ times followed by enough $n$ to have a list of size num_chunks.

But, since we are slicing from the previous offset for the given length, we need to accumulate the chunk sizes to know from which offset to start each time. We would then need to zip offsets and chunks sizes together to create our slices:

def split(sentence, num_chunks):
"""Split the given sentence into num_chunk pieces.

If the length of the sentence is not exactly divisible by
num_chunks, some slices will be 1 character shorter than
the others.
"""

size, remainder = divmod(len(sentence), num_chunks)
chunks_sizes = [size + 1] * remainder + [size] * (num_chunks - remainder)
offsets = [sum(chunks_size[:i]) for i in range(len(chuncks_sizes))]

return [sentence[o:o+s] for o, s in zip(offsets, chunks_sizes)]


Computing offsets that way is not very efficient as it runs in $O(n^2)$ but I find it somewhat clearer than using itertools.accumulate and padding the results with the first offset of 0.

• Hey, isn't the double forward slash meant to say that it's a comment? IT's not working for me. I know it works for PHP, but it's not working for Python 3.x. Nov 3 '16 at 13:22
• @RepeaterCreeper Comments in python starts with #. The // operator is integral division, meaning 3 // 2 is 1 and 3 / 2` is 1.5 Nov 3 '16 at 13:24
• Oh my bad. I got confused there as PHP uses it. Thanks for the clarification! Nov 3 '16 at 13:25