5 Best Ways to Check if a String Has All Characters with Same Frequency with One Variation Allowed in Python

Rate this post

πŸ’‘ Problem Formulation: When we deal with strings in Python, we might encounter a situation where we need to check if the characters in a string appear with the same frequency with at most one character allowed to deviate from this frequency. For example, given the string “aabbc”, the desired output would be True, because it’s possible to remove one ‘b’ to make all character frequencies the same.

Method 1: Using a Frequency Dictionary and Counters

This method involves creating a dictionary to store the frequency of each character in the string. We then compare the frequency of each character to see if they all match, allowing for one variation. We use the Counter from the collections module which makes counting frequencies easy and intuitive.

Here’s an example:

from collections import Counter

def check_frequencies(string):
    freq = Counter(string)
    values = list(freq.values())
    values_set = set(values)
    
    if len(values_set) == 1:
        return True
    if len(values_set) == 2:
        if values.count(min(values_set)) == 1 or values.count(max(values_set)) == 1:
            return True
    return False

print(check_frequencies("aabbc"))

Output:

True

This code snippet leverages the Counter from Python’s collections module to tally character occurrences. By converting the values into a set and checking their length, we deduce the pattern. Presence of only one frequency implies all characters occur equally, while two frequencies allow for evaluating if the variation is allowed.

Method 2: Using Sorting

By sorting the frequency counts of characters, we can then check if the frequencies either all match, or all but one match. This method involves first transforming the string into a frequency list, sorting it, and then applying the comparison logic.

Here’s an example:

from collections import Counter

def check_frequencies_sorted(string):
    freq = sorted(Counter(string).values())
    if freq[0] == freq[-2] == freq[1] or freq[-1] == freq[1] == freq[0] + 1:
        return True
    return False

print(check_frequencies_sorted("aabbc"))

Output:

True

This method sorts the counted frequencies of characters which makes it easy to determine if there’s a universal frequency that’s being followed or a single allowed variation by comparing the first and last elements of the sorted list.

Method 3: Without Using Collections

If for some reason you prefer not to or cannot use the collections module, you can still accomplish this task. Here, the frequency dictionary is manually updated, followed by logic similar to the previous methods but without the convenience of Counter.

Here’s an example:

def check_frequencies_manual(string):
    freq = {}
    for char in string:
        freq[char] = freq.get(char, 0) + 1
    freq_list = list(freq.values())
    unique_freq = set(freq_list)
    
    if len(unique_freq) == 1:
        return True
    if len(unique_freq) == 2 and (freq_list.count(min(unique_freq)) == 1 or freq_list.count(max(unique_freq)) == 1):
        return True
    return False

print(check_frequencies_manual("aabbc"))

Output:

True

This time, we construct our own frequency dictionary and follow through with a manual count. It’s slightly more work than using Counter but achieves the same result with no external dependencies.

Method 4: Optimized Space Complexity

This method aims to reduce space by using fixed-size arrays instead of a hash table when it’s known that the input string will contain only letters (ASCII lower or upper case), further optimizing the solution.

Here’s an example:

def check_frequencies_optimized(string):
    MAX_CHAR = 256
    freq = [0] * MAX_CHAR
    for char in string:
        freq[ord(char)] += 1
        
    non_zero_freq = [f for f in freq if f != 0]
    unique_freq = set(non_zero_freq)
    
    if len(unique_freq) == 1:
        return True
    if len(unique_freq) == 2 and (non_zero_freq.count(min(unique_freq)) == 1 or non_zero_freq.count(max(unique_freq)) == 1):
        return True
    return False

print(check_frequencies_optimized("aabbc"))

Output:

True

Instead of a dictionary, this example uses an array indexed by the ASCII value of the characters. After counting, we remove the zeros and apply the same checking logic. This method is faster and uses less space for certain types of strings.

Bonus One-Liner Method 5: Using Python’s Powerful List Comprehensions and min/max Functions

Python’s list comprehensions combined with functions min and max enable a concise one-liner to check for the frequency condition by leveraging the power of Python’s expressive syntax and built-in functions.

Here’s an example:

def check_frequencies_oneliner(string):
    return len(set(len([char for char in string if char == ch]) for ch in set(string))) in (1, 2)

print(check_frequencies_oneliner("aabbc"))

Output:

True

This one-liner is the epitome of Python’s ability to encapsulate complex logic into a single, albeit dense, line of code. It constructs a generator of frequencies for unique characters and then converts it to a set β€” all in one go.

Summary/Discussion

  • Method 1: Frequency Dictionary and Counters. Intuitive and straightforward. Relies on collections module. May not be the most space-efficient.
  • Method 2: Sorting. Simple logic after sorting. Slightly less straightforward due to the sorting step. Requires additional time for the sorting process.
  • Method 3: Manual Frequency Calculation. No dependencies, works in any Python environment. More verbose and manually intensive.
  • Method 4: Optimized Space Complexity. Efficient in both time and space for ASCII character sets. Not as flexible for inputs beyond ASCII characters.
  • One-Liner Method 5: Powerful List Comprehensions. Condensed code, great for Python enthusiasts. May sacrifice readability for brevity.