I wrote a program that given an input string returns a list of tuples in which each element of the tuple is a word given by dropping some number of characters of the original string, preserving order.
For example:
'valley' -> [('alley',), ('valley',)]
'friction' -> [('fiction',), ('friction',), ('ricin',)]
'university' -> [('unvest',), ('nesty',), ('unsty',), ...]
'freetrade' -> [..., ('erade',), ..., ('free', 'tade'), ..., ('free', 'trade'), ...('feer',), ...]
The algorithm I've written works as follows
- Find the powerset of the characters in the string. This gives us a set of lists of characters, conceptually given by "dropping" some number of characters of the input string, preserving order.
powerset('abc') = {(),('a',),('a', 'b'),('a', 'b', 'c'),('a', 'c'),('b',),('b', 'c'),('c',)}
- Next, for each element of the powerset, we perform a "powersplit" in which we find every possible split (or, inversely, concatenation).
powerset(('a', 'b', 'c')) = {('a', 'b', 'c'), ('a', 'bc'), ('ab', 'c'), ('abc',)}
- Finally, for each tuple produced by the powersplit, we check if the contents of the tuple are in our dictionary.
contents_in_dictionary(dictionary, ('free', 'trade')) = True
The following is my code
from functools import reduce
from itertools import chain, combinations, islice, zip_longest
def powerset(data):
return {z for z in chain.from_iterable((x for x in combinations(data, r)) for r in range(len(data)+1))}
def slices(data, indices):
return [[x for x in islice(data, *list(y))] for y in zip_longest(chain([0], indices), chain(indices, [len(data)]))]
def powersplit(data):
return {tuple([''.join(x) for x in slices(data, indices)]) for indices in powerset(range(1, len(data)))}
def get_dictionary(min_length):
return {line.rstrip('\n').lower() for line in open('/usr/share/dict/words') if len(line.rstrip('\n')) >= min_length}
def contents_in_dictionary(dictionary, in_string):
return all([y in dictionary for y in in_string])
def acro_word(in_string):
candidates = reduce(set.union, [powersplit(partial) for partial in powerset(in_string)])
dictionary = get_dictionary(4)
return [x for x in candidates if contents_in_dictionary(dictionary, x)]
My questions are:
- Am I missing any obvious / not complex improvements to my algorithm?
- Am I missing any obvious pythonic data structures / functions that I can take advantage of?
- How can I
- Make this generally faster. As you add characters to the input string, it takes exponentially (I believe) longer to return.
- Turn everything into a generator and stream results to the client.