''.join isn't working for me on 7.15


#1

Hey everyone,

Would someone mind checking out my code here? The .join thing doesn't seem to be working for me. Thanks:

def reverse(text):
    letters = []
    for item in text:
        letters.append(item)
    count = len(letters)
    while count > 0:
        count = count - 1
        x = letters[count]
        print ''.join(x)

print reverse("abcd")


#2

letters is a list of characters, so letters[count] will be a character, which you assign to x. A single character isn't much to join together.

Also, the list constructor will iterate through whatever value you pass to it as an argument and add each element iterated through to the new list, and because iterating through a string will give each character, you can create a list of a string's characters like this:

list('this is a string')

If you think about what your function is supposed to do, then you'll probably conclude that you should only have a single loop, you have two!

And if we really want to get this right, we should take into account that insertion and removal is fast at the end of a list, but gets slower the further from the end the operation is done. This doesn't really matter in this exercise where only short strings will be tested, but it becomes very important if the strings are very large, like for a book.
(Whenever something is added or removed, all following elements have to also move to make room for the element to be inserted or to fill up the room after the removed element)

So if we wish to only insert characters at the end of the list, that means that whatever we put in first will be first. Since we are reversing something that means that we'll want to iterate through the original backwards (the range function can provide indexes to match this)


#3

Hey thanks @ionatan, this was really helpful. Ended up putting the items in a new string, and then joining that string rather than having them print individually, worked like a charm. See here:

def reverse(text):
    letters = []
    revletters = []
    for item in text:
        letters.append(item)
    count = len(letters)
    while count > 0:
        count = count - 1
        revletters.append(letters[count])
    x = ''.join(revletters)
    return x

reverse("Python!")


#4

What you do with letters you can do with text directly


#5

Something like this?

def reverse(text):
    r = ""
    for c in xrange(len(text)-1, -1, -1):
        r += text[c]
    return r
    
print reverse("Python!")     # !nohtyP

It seemed awfully slow in the l a b. Haven’t tested it on long strings, yet.

Edit: My oops. Should be xrange. Edited. Not slow at all.

Source: PEP 0322 – Reverse Iteration


#6

That shouldn't be slow, not in CPython. (That performance hack when there's only a single reference to the string)
Perhaps labs doesn't use CPython?
You should consider strings to be immutable, and use a list and then join the characters together.


#7

Getting closer?

def reverse(text):
    r = []
    t = list(text)
    for c in xrange(len(t)-1, -1, -1):
        r.append(t[c])
    return ''.join(r)

print reverse("Always obey your parents, when they are present.\
This is the best policy in the long run, because if you don’t, \
they will make you. Most parents think they know better than you \
do, and you can generally make more by humoring that superstition \
than you can by acting on your own better judgment. \
*Sam Clemens, aka, Mark Twain*")

Lightening fast.


#8

It’s Python 2.7.2. Any relation?


#9

range vs xrange .. shouldn't matter. xrange makes more sense conceptually but we're already in the time complexity of range since we're stepping through each character in the text.

You're not using t other than for its length. You don't need it.

And of course, normally we would use:

return text[::-1]

or

return reversed(text)

If labs manages to quickly "adds" a million characters, one a time to an empty string, then it's probably CPython. We shouldn't be doing that anyway, better to code what we mean to happen.

So don't do:

r = ''
for _ in range(1000000):
    r += 'a'

instead do:

r = []
for _ in range(1000000):
    r.append('a')
''.join(r)

And better yet, use built-ins when available for the task


#10

My mistake. Edited.

I can see what you mean about the built-ins

Code
'''
def reverse(text):
    r = ""
    for c in xrange(len(text)-1, -1, -1):
        r += text[c]
    return r
'''
'''
def reverse(text):
    r = []
    t = list(text)
    for c in xrange(len(t)-1, -1, -1):
        r.append(t[c])
    return ''.join(r)
'''

def reverse(text):
    #return reversed(text)
    return text[::-1]

print reverse("Always obey your parents, when they are present.\
This is the best policy in the long run, because if you don’t, \
they will make you. Most parents think they know better than you \
do, and you can generally make more by humoring that superstition \
than you can by acting on your own better judgment. \
*Sam Clemens, aka, Mark Twain*")

.

return text[::-1]

ran the above paragraph before my finger left the mouse button.

What does this mean?

return reversed(text)   # <reversed object at 0x21ff2c>

#11

Rather than printing something, you returned it. I've run into that problem a few times.


#12

@tbhesswebber The function should return a string (and not print anything). But that value isn't a string. If you want to print the result of the function, then do exactly that: print reverse('hello') - printing is otherwise pretty useless if you want to use the result in other parts of your code.

Oh. I thought it would only do that in Python3.

Instead of returning a string, reversed returns an iterator, which needs to be consumed to produce the characters in reversed order. str.join will accept iterables/iterators:

''.join(reversed(text))

(a list is an iterable, so a list of characters treated the same way as the reversed object)

an example:

i = reversed('hello')
print next(i) # o
print next(i) # l
print next(i) # l
print next(i) # e
print next(i) # h
print next(i) # StopIteration exception, meaning it's exhausted

The above is essentially what a for-loop does. If the value is not an iterator but is iterable, then an iterator needs to be requested first by doing: iter('hello')

We could for example make an iterable integer, let's say it should count from 0 up/down to itself by inheriting everything that int has, and adding the __iter__ attribute:

class MyInt(int):
    def __iter__(self):
        i = 0
        while i != self:
            yield i
            i += 1 if self > 0 else -1

# Try iterating "through" our integer:
for n in MyInt(4):
    print n

output:

0
1
2
3

A whole lot is going on there.

The first line says that MyInt is a class and it inherits int. So instances of MyInt are also int's - this is what allows me to use != and > and also why I can do MyInt(4) - because int(4) creates the int 4 and I'm inheriting that behaviour.

The yield statement turns __iter__ into a generator, When it is called, its code does not run. Instead, an iterator is created and returned. Each time next vaule is requested from that iterator, the code in __iter__ will run until it hits yield - at which point it returns a value and pauses its execution state so that it can continue where it left off next time a value is requested.

Once control reaches the end of the code in __iter__, a StopIteration gets raised and that's how one knows that it has been exhausted.

-

A result of that reversed does not return a string, but an iterator, is that it will finish in the same amount of time regardless of the size of what is to be reversed. Because all it does is to create an iterator - the actual reversing only happens as you request the values one at a time.


#13

It will take me a couple more reads, but I get the gist on the first reading so am confident this will crystalize. Thank you, @ionatan . I'm very sure I will enjoy studying the topic now that I'm armed with this.