2
\$\begingroup\$

This was the first kata I found hard so I wanted to ask about it. Any ways I could I have done it better?

here's the question -> Most frequently used words in a text

def check_alphabet(string): #checks if the given string contains at least one alphabet
    for i in string:
        if i.isalpha():
            return True
    return False

def top_3_words(text):    
    text += " " # the for loop ends before it can check the final word
    text = text.lower() # given in the problem
    
    current_word = ""
    
    words = {
    }
    
    letter_index = 0
    
    for letter in text:
        if letter.isalpha() or letter == "'": # make the word
            current_word += letter
            letter_index += 1
            
        elif letter_index == 0: # this happens if there is more than one whitespace
            pass
        
        else: # add the word into the dictionary
            
            if not check_alphabet(current_word): # filter out words only made out of '
                current_word = ""
                continue
            
            if current_word in words.keys(): # if the word has already come out before
                words[current_word] += 1
            else: #if the word is new
                words.update({current_word : 1})
                
            current_word = "" #reset
            letter_index = 0
            
    words = sorted(words.items(), key = lambda x: x[1], reverse= True) # sort the dictionary
    
    if len(words) == 0:
        return []
    elif len(words) == 1:
        return [words[0][0]]
    elif len(words) == 2:
        return [words[0][0], words[1][0]]
    elif len(words) >= 3:
        return [words[0][0], words[1][0], words[2][0]]
\$\endgroup\$
2
  • \$\begingroup\$ It would help for you to embed the text of the problem statement in your question. \$\endgroup\$
    – Reinderien
    Commented Dec 26, 2023 at 15:51
  • \$\begingroup\$ check_alphabets needlessly reinvents any() \$\endgroup\$
    – tripleee
    Commented Jan 1 at 11:02

1 Answer 1

1
\$\begingroup\$

The good: No globals, use of functions, reasonable variable names.

You can make better use of the standard library - regexes and Counter will make this easier.

+= on what will already be a long string may be expensive, because Python needs to recreate the entire string as a new object (strings are immutable).

If you wanted to keep the dictionary approach, defaultdict(int) would make this easier; but again Counter does all of this for you.

The question also asks

Bonus points (not really, but just for fun): [...] Avoid sorting the entire array of unique words.

You're doing this in your words =, but this can be avoided (again by the use of Counter).

words.update({current_word : 1}) doesn't need to call update, nor does it need to form a literal dictionary; just words[current_word] = 1.

Add the test cases from the problem statement to your question.

Suggested

import re
from collections import Counter

r'''
A word is a string of letters (A to Z) optionally containing one or more apostrophes (') in ASCII
Apostrophes can appear at the start, middle or end of a word ('abc, abc', 'abc', ab'c are all valid)
Any other characters (e.g. #, \, / , . ...) are not part of a word and should be treated as whitespace.
Matches should be case-insensitive, and 
'''
WORD_PAT = re.compile(
    r"(?ai)"     # ASCII, case-insensitive
    r"[a-z']+"   # a-z or ', one or more
)


def top_n_words(text: str, n: int = 3) -> list[str]:
    # the words in the result should be lowercased
    words = WORD_PAT.findall(text.lower())
    counter = Counter(words)
    return [
        word
        for word, freq in counter.most_common(n)
    ]


def test() -> None:
    assert top_n_words(
        '''
        In a village of La Mancha, the name of which I have no desire to call to
        mind, there lived not long since one of those gentlemen that keep a lance
        in the lance-rack, an old buckler, a lean hack, and a greyhound for
        coursing. An olla of rather more beef than mutton, a salad on most
        nights, scraps on Saturdays, lentils on Fridays, and a pigeon or so extra
        on Sundays, made away with three-quarters of his income.
        '''
    ) == ['a', 'of', 'on']

    assert top_n_words(
        'e e e e DDD ddd DdD: ddd ddd aa aA Aa, bb cc cC e e e'
    ) == ['e', 'ddd', 'aa']

    assert top_n_words(
        "  //wont won't won't"
    ) == ["won't", "wont"]


if __name__ == '__main__':
    test()
\$\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.