Loops usage on Little Sister Vocab before it's teached?

newbie, i started Little Sister’s Vocabulary exercise and i got really stuck on task 2
i tried this:

def make_word_groups(vocab_words):
    prefix = vocab_words[0]
    first = vocab_words[1]
    second = vocab_words[2]
    third = vocab_words[3]
    fourth = vocab_words[4]
    fifth = vocab_words[5]
    sixth = vocab_words[6]
    seventh = vocab_words[7]
    eighth = vocab_words[8]
    ninth = vocab_words[9]
    return '::'.join([prefix,prefix+first,prefix+second,prefix+third,prefix+fourth,prefix+fifth,prefix+sixth,prefix+seventh,prefix+eighth,prefix+ninth])

and it works for tests 2 and 3, but not for 4 and 5 (since number of words change). if i increase the number (to 11), it works for tests 4 and 5, but not for 2 and 3 since it’s out of index range
i finally decided to look up the solution, and kinda surprised it uses those “for in” loops though i haven’t learned them yet. iirc it’s 2 lessons ahead
what confused me even more is the hint saying “believe it or not, str.join() is all you need here”, but it actually needs loops too
well that’s it, sorry if this sounded arrogant, i’m just kinda frustrated that i had to look up the solution on this one

copy pasted from Discord

I used a list comprehension for this, because that’s how it seemed natural to me to solve it.

I just had a peek at the exemplar solution in the GitHub repo. I smacked my forehead.

You don’t actually need a loop. In fact you don’t actually need to modify the input list at all: providing your join string is sufficiently clever.

1 Like

A word in my defense :slightly_smiling_face: as the exercise co-author and track maintainer. The solution here is not “clever” - its SOP for Python strings . But I can see how it would be confounding if you are new to Python and it certainly isn’t obvious. :slightly_smiling_face:

Open to suggestions on what might work better in the introduction text/exercise. But I don’t think looping/comprehensions should be moved earlier in the track, nor do I think .join() should be omitted.

This was a very hard exercise to write (I think we went through about 10 iterations for the .join() part), and we really struggled with how we would go over what we considered necessary (+, .join(), indexing, reverse indexing, slicing, splitting, some common sequence operations) WITHOUT resorting to explaining loops or comprehensions.

An earlier version of the exercise did resort to looping, but that was quite awkward as well. In the end, introduction.md was written to include an example of the solution since it is counter-intuitive when folx are primed to think “loop” for these sorts of scenarios:

If a list, tuple, set or other collection of individual strings needs to be combined into a single str, <str>.join(<iterable>), is a better option:

# str.join() makes a new string from the iterables elements. 
>>> chickens = ["hen", "egg", "rooster"] 
>>> ' '.join(chickens) 'hen egg rooster' 

# Any string can be used as the joining element. 
>>> ' :: '.join(chickens) 'hen :: egg :: rooster' 
>>> ' 🌿 '.join(chickens) 'hen 🌿 egg 🌿 rooster'

We finally settled on the current version in the hope that most students would follow the example in the intro & that would eventually help them understand that many many functions/methods/constructors in Python either iterate themselves, or return iterators - so the go-to for iteration is not always setting up a formal loop construct (and it is definitely NOT to set up an independent loop counter!!).

.join() is (allegedly) faster than the equivalent loop using += in almost every circumstance (here is an older SO post that has some detail and some insight on the confusion). Once you understand how it works, it is also more readable and less verbose than a loop.

Since I couldn’t find any specific stats against Python 3.11.5, I will leave it to you to play with timeit, if you are inclined. But beware of the details of the SO post: some of the core Python devs optimized certain += cases, but not all and not … reliably. :smile:

It is all a bit … sticky, since the mechanics of how .join() works are quite a bit more complicated than your average loop. So we didn’t want to go into deep detail on .join() - we wanted to show an example that students could copy and have success with early in their journey.

1 Like

Addendum. Sitting with this a bit, I do think the examples in introduction.md could use one more that is closer to how the exemplar is constructed.

So I’ll put that together in a PR, and post here to see if that helps. :smile:

4 Likes

Here is the PR: [Little Sister's Vocab]: Fixed up Code Examples for `str.join()` & Added an Additional Hint. by BethanyG · Pull Request #3995 · exercism/python · GitHub

Just remembered I will need to also change the about.md for the concept, but will wait to see if there are objections/changes needed before doing the changes there.

LMK your thoughts/suggestions/issues. :smile: Many thanks!

2 Likes

okay, thanks for the answers! happy to see there was other solution than the one including loops. i’ll try to solve it again without them, and with the new hint and example! :smile:

1 Like

Hi @Ringi,

Thank you for your patience, and for good-naturedly putting up with all the … shenanigans. :smile:

The PR hasn’t been merged yet, but I’ll put the updated example here for you. It’s the last two chunks of code that apply:

# str.join() makes a new string from the iterables elements.
>>> chickens = ["hen", "egg", "rooster"] #lists are iterable
>>> ' '.join(chickens)
'hen egg rooster'

# Any string can be used as the joining element.
>>> ' :: '.join(chickens)
'hen :: egg :: rooster'

>>> ' 🌿 '.join(chickens)
'hen 🌿 egg 🌿 rooster'


# Any iterable can be used as input.
>>> flowers = ("rose", "daisy", "carnation")  #tuples are iterable
>>> '*-*'.join(flowers)
'rose*-*daisy*-*carnation'

>>> flowers = {"rose", "daisy", "carnation"}  #sets are iterable, but output order is not guaranteed.
>>> '*-*'.join(flowers)
'rose*-*carnation*-*daisy'

>>> phrase = "This is my string"  #strings are iterable, but be careful!
>>> '..'.join(phrase)
'T..h..i..s.. ..i..s.. ..m..y.. ..s..t..r..i..n..g'


# Separators are inserted **between** elements, but can be any string (including spaces).
# This can be exploited for interesting effects.
>>> under_words = ['under', 'current', 'sea', 'pin', 'dog', 'lay']
>>> separator = ' ⤴️ under'
>>> separator.join(under_words)
'under ⤴️ undercurrent ⤴️ undersea ⤴️ underpin ⤴️ underdog ⤴️ underlay'

# The separator can be composed different ways, as long as the result is a string.
>>> upper_words = ['upper', 'crust', 'case', 'classmen', 'most', 'cut']
>>> separator = ' 🌟 ' + upper_words[0]
>>> separator.join(upper_words)
 'upper 🌟 uppercrust 🌟 uppercase 🌟 upperclassmen 🌟 uppermost 🌟 uppercut'

And the corresponding hints:

  1. Add prefixes to word groups
  • Believe it or not, str.join() is all you need here. A loop is not required.
  • The tests will be feeding your function a list. There will be no need to alter this list if you can figure out a good delimiter string.
  • Remember that delimiter strings go between elements and “glue” them together into a single string. Delimiters are inserted without space, although you can include space characters within them.
  • Like str.split(), str.join() can process an arbitrary-length string, made up of any unicode code points. Unlike str.split(), it can also process arbitrary-length iterables like list, tuple, and set.

Let us know if this helps, and if you have any additional questions or issues.

2 Likes

Putting this here for the morbidly curious who wander by. :smile: This is the Python internal implementation of str.join(): stringlib join.

took me a while, but finally made it! ended up with this:

def make_word_groups(vocab_words):
    prefix = " :: " + vocab_words[0]
    return prefix.join(vocab_words)

and now i see how i made things complicated for myself :sweat_smile: . the examples were very useful, thank you!

1 Like

how to do spoilers is documented here: Some tips for using Discourse efficiently [Wiki]

2 Likes