2
\$\begingroup\$

I have made a code to check if a word chain is correct or not.

  • The first input is the amount of words that will following

  • The first and last letter of every word should be the same

  • Therefore a chain can look like this: Alaska - baobab - Cadillac - drievuldigheid - Enigma - fotografencollectief - Gasthuisberg

    • If the chain is incorrect the output should be the wrong word, else it should be 'word chain is correct'.

As you'll be thinking, this is an excercise for school. I have exam in a few days and I'm doing some excercises.

My code looks like this:

import string
words = int(input())
alphabet = string.ascii_lowercase
output = ''

for i in range(words):
    woord = input()

    #for the first word
    if i == 0:
        #make that the alpabet last letter is the starting letter of the word
        alphabet = alphabet[alphabet.index(woord[0].lower())+1:] + alphabet[0:alphabet.index(woord[0].lower())+1]

    #check if the last letter of the word is equal to the first  letter
    #check if the last letter of the word is equal to the last letter of the alphabet
    if woord[0].lower() == woord[-1].lower() and alphabet[-1] == woord[0].lower():
        #if so: move the alphabet one Index
        alphabet = alphabet[1:] + alphabet[:1]

    elif output == '':
        output = woord

if output == '':
    print('word chain is correct')
else:
    print(output)

I'm wondering if there is some room for improvement, as it looks my code isn't pythonic at all.

\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

A few remarks:

  • You can do the .lower() one time by using woord = input().lower()
  • You could check if they are in alphabetic order by keeping track of the previous one like this:

    previouschar=''
    for s in ('aaa', 'bbb', 'ddd', 'efe', 'geg'):
        if previouschar != '' and ord(s[0]) != ord(previouschar) + 1:
            print('Out of order')
        previouschar = s[0]
    
\$\endgroup\$
2
\$\begingroup\$

Just my two cents:

  • Use functions. The sooner you get used to using functions, the better. Having a single, monolithic block of code that does everything is not something that you want to get used to.

  • Use comments to explain why not what. If some line of code needs to be explained on what it's doing, you probably want to write it in another way.

  • You should check that the user actually inputs a number as the first value

  • As @Carra suggested, you can use input().lower(), but keep in mind that in that case, you'll lose the exact input of what the user entered. It's not a problem in this case because it's just a simple exercise, but I'd say just keep the user input in a separate variable and do your operations on that.

  • Naming matters, words suggests that the variable contains the words, while it actually contains the amount of words. Maybe something like word_count is more appropriate.

  • You don't need any special case for the first word

  • You also don't need to move the "alphabet index", you already have an index in your loop.

So, I would do something like this:

import string

alphabet = string.ascii_lowercase

def check_words(word_count):
    for i in range(word_count):
        woord = input().lower()
        if woord[0] != woord[-1] or alphabet[i] != woord[0]:
            return woord
            break
    return None

#TODO: Add the check that it's a number
result = check_words(int(input()))

if result is None:
    print('word chain is correct')
else:
    print(result)

Optionally:

  • You don't need to import string just for the alphabet in this case, you just need the starting point and that's fixed.
  • The last if can be written as a single statement, but I don't really like that syntax, I say that just for completeness.

So, it would become:

def check_words(word_count):
    start_alphabet_index = ord('a') # This means 97
    for i in range(word_count):
        woord = input().lower()
        if woord[0] != woord[-1] or chr(start_alphabet_index) != woord[0]:
            return woord
            break
        start_alphabet_index += 1
    return None

#TODO: Add the check that it's a number
print(check_words(int(input())) or 'word chain is correct')
\$\endgroup\$
1
\$\begingroup\$

Create reusable functions

There's currently no way to import (parts of) the code and reuse it in a different program. Other than that, splitting up your logic into functions will make it easier to test and debug.

For instance, rather than getting the alphabet and adding the letters to it one by one and checking if the current first letter matches the last in that list, use a generator function to get the first next function each time:

def alphabet_counter():
    for letter in string.ascii_lowercase:
        yield letter

and use it as follows:

expected_letter = alphabet_counter()
if word[0] == next(expected_letter):
    # the current word's first letter matches the expected letter of the alphabet. 

This also means you don't have to use a special check for the very first letter, because the first time you use the next(expected_letter) call, you get a, the second time b, etc.

The next step would be to add if __name__ == '__main__': to your function. This will allow you to either import the code in the file, or to simply run it as a script. For example:

def is_valid_wordchain():
    # code here ...


if __name__ == '__main__':
    is_valid_wordchain()

You can now import your function to another script / project and reuse the code, or you can run it as a script and is_valid_wordchain() will be called and executed.


  • As Carra's answer already pointed out, you could call lower() once on the word, and then check in the if-statements without having to call lower all the time.
  • Be consistent in your variable naming. You used the Dutch woord as variable name for a word, but the rest of your variables / output is in English.
  • Never asssume that input given from the user is OK. Build in checks to make sure that when expecting an integer, you get an integer. For example in your code:
for i in range(words):
    word = input()
    # rest of code ..

.. it is assumed that the given input is correct. I could enter an integer and the program crashes. The same goes for the words = int(input()). If I enter some string on the first line when running the program, I get a ValueError: invalid literal for int() with base 10: 'some string' which crashed the program.

  • Since it is immediately checked upon entering the word if the wordchain is still correct, why not stop the program? If I were to enter 10+ words, but it already silently fails on the 2nd word, I'd still have to enter 8+ words to find out.

The program with the proposed changes

import re
import string

def is_valid_word(word):
    """Checks whether or not the given string is a valid word."""
    return isinstance(word, str) and re.match(r'\b[\w\']+\b', word)


def get_word_amount():
    """Get the amount of words in the wordchain from the user."""
    while True:
        try:
            return int(input('How many words are in the chain? '))
        except ValueError:
            print('Not a valid number, try again.\n')


def get_word_from_user(count):
    """Get a single word from the user."""
    while True:
        word = input('Please type word #{}: '.format(count))

        if is_valid_word(word):
            return word
        else:
            print('Not a valid word, try again.\n')


def alphabet_counter():
    """Iterate through the alphabet"""
    for letter in string.ascii_lowercase:
        yield letter


def is_valid_wordchain():
    """
    Allow the user to enter a number of words to be checked if it forms
    a valid wordchain.

    A wordchain is correct if for every word in the chain, the first
    and last letter match, and the words are in alphabetical order. 
    """
    word_amount = get_word_amount()
    expected_letter = alphabet_counter()

    word_list = []  # only for record keeping; has no value in checking.
    for number in range(word_amount):
        word = get_word_from_user(number + 1).lower()

        if word[0] != word[-1] or word[0] != next(expected_letter):
            print('Incorrect wordchain. Failed at "{}"'.format(word))
            return
        else:
            word_list.append(word)
    print('Correct wordchain!\n{}'.format(word_list))


if __name__ == '__main__':
    is_valid_wordchain()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.