在我看来,我有一个更快的解决方案。这是带有 cmets 的代码...
import itertools
import string
import timeit
if __name__ == '__main__':
# Start timestamp
start_ts = timeit.default_timer()
#
# Small function to calculate the factorial of a number
# Used in debugging
#
# Math: the number of unique combinations of x elements from y elements is calculated as
# y! / (y - x)! / x!
#
# Or, in 'school' notation
#
# y!
# _____________
# (y - x)! . x!
#
fac = lambda num: 1 if num <= 1 else num * fac(num - 1)
#
# Open the file and read the content in memory as a list of strings
#
with open("words.txt", "r") as file:
words = file.readlines()
#
# Create a dictionary containing the 26 letters of the English alphabet
# For each of the letters, set the number the letter appears to 0
#
# I prefer to initialize this here instead of dynamically adding them to the dictionary later,
# as normally this text file will contain all letters and having to check if the element exists will take longer
#
appearances = {}
for letter in string.ascii_lowercase:
appearances[letter] = 0
#
# For each of the words, each of the unique letters, count them into appearances
# If a letter appears twice or even more, it does not matter. We count the words that contain the letter
# at least once. For our letter set, it does not matter whether the letter appears once or more
#
for word in words:
for letter in list(set(word.strip().lower())):
appearances[letter] += 1
# Debug: you will see Q has the least appearances, E has the most
print(appearances)
#
# Let's sort this. It's key to this algorythm
#
# In short:
#
# Suppose we only have 5 letters, A to E
# Suppose we have counted our appearances and this is how many times they show up
# A : 10
# B : 5
# C : 3
# D : 7
# E : 12
#
# Sorted:
# C : 3, B: 5, D : 7, A : 10, E : 12
#
# Suppose we need combinations of only 2 letters
# Take C + B
# In worst case, you have in total 8 words that contain either C or B. This is the case where no words have both.
# In best case, you have 5. This is the case where 3 words contain B and C, 2 words contain only B
#
# Given the above, it makes no sense to check any combination with A or E
# You know they have either 10 or 12 words. They can't beat B+C in number of appearances
# So don't include them in the combinations. This will significantly lower the number of combinations
#
# Given the above, you must include D, as you don't know how many words have either B or C (between 5 and 8)
#
# On the words.txt, this approach resulted in only 252 combinations to check. So "with brute" force, you only
# needed 252 iterations over the possible combinations of 5 characters. You can verify with the debug code
#
#
#
# appearances_sorted is a list, we can't calculate on it
#
appearances_sorted = sorted(appearances, key=lambda x: appearances[x])
print(appearances_sorted)
print(appearances_sorted[:5])
#
# Calculate the least amount possible. This is the sum of the 5 lowest appearances
# As we are looping over the first 5, we already put them in our list of combinations to check
#
sum_least = 0
appearances_least = {}
for k in appearances_sorted[:5]:
v = appearances[k]
sum_least += v
appearances_least[k] = v
print(sum_least)
print(appearances_least)
#
# For the rest of the sorted appearances, we add them, unless the appearance of the character by itself
# is already higher than the sum we calculated
#
for k in appearances_sorted[5:]:
if appearances[k] > sum_least:
break
appearances_least[k] = appearances[k]
print(appearances_least)
#
# Debug code to check the math against the len of the calculations Python will provide
#
# f1 = fac(len(appearances_least))
# f2 = fac(len(appearances_least) - 5)
# f3 = fac(5)
# print(f1 / f2 / f3)
#
#
# Create all the possible combinations using itertools
# One advantage is also that we can do this on a sorted list, the combinations with the smallest possible
# appearances appear first. But as said, as we don't know the words that have multiple letters combined, we
# cannot be sure we only need to check the first
#
combinations = list(itertools.combinations(appearances_least, 5))
# This will print 252 on the words.txt file
print(len(combinations))
#
# How many words in total do we have
# This total will be used as a starting point to see how a combination is done
# The worst combination possible will never be in more words than the file contains
#
total_words = len(words)
min_found = total_words
print(total_words)
#
# Just to avoid that PyCharm complains that best_combo might not be set later
#
best_combo = combinations[0]
#
# Loop over all the combos we have, as we cannot be sure on the words that have multiple letters
# When we calculated the appearances, we were calculating only per letter
#
for combo in combinations:
count_matches = 0
#
# Loop over the words, then over the letters in the combo
# If one of the letters is found, add the counter and stop the loop as it does not matter if other characters
# of the combo also appear. One is enough to count it.
#
#
for word in words:
for letter in combo:
if letter in word:
count_matches += 1
break
#
# If we already found more words than the minimum we have detected already, we can stop the loop. This
# combo will not be better, it will only get worse.
#
if count_matches > min_found:
break
#
# If we found a better one, store it
#
if count_matches < min_found:
best_combo = combo
min_found = count_matches
# End timestamp
end_ts = timeit.default_timer()
#
# Print the results
#
print(best_combo)
print(min_found)
print(end_ts - start_ts)
#
# I have:
#
# ('q', 'j', 'x', 'z', 'w')
# 17382
# 4.387889001052827
#
# Enjoy !