-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
183 lines (135 loc) · 7.15 KB
/
app.py
File metadata and controls
183 lines (135 loc) · 7.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import streamlit as st
from solving.puzzle import Puzzle
from solving.solver import Solver
from words.dictionary import Dictionary
from words.word import Word
from time import perf_counter
import difflib
APP_NAME = "WordLadder"
MINIMUM_WORD_LENGTH = 2
MAXIMUM_WORD_LENGTH = 15
MINIMUM_LADDER_LENGTH = 1
MAXIMUM_LADDER_LENGTH = 10
def green(msg):
return f'<span style="color: green;">{msg}</span>'
def red(msg):
return f'<span style="color: red;">{msg}</span>'
if "page_start" not in st.session_state:
st.session_state.page_start = 0
if "solutions" not in st.session_state:
st.session_state.solutions = None
if "user_solution" not in st.session_state:
st.session_state.user_solution = None
if "user_solution_input" not in st.session_state:
st.session_state.user_solution_input = ""
if "max_ladder_length" not in st.session_state:
st.session_state.max_ladder_length = False
if "button" not in st.session_state:
st.session_state.button = False
def main():
st.title(APP_NAME)
st.markdown("Generate a word ladder from a **starting word** to an **ending word** by changing one letter at a time. 🔠")
with st.sidebar:
st.header("Settings")
start_word_input = st.text_input("Enter start word:", value=st.session_state.get("start_word_input", ""))
end_word_input = st.text_input("Enter end word:", value=st.session_state.get("end_word_input", ""))
st.session_state.max_ladder_length = st.number_input(
"Max ladder length (optional)", min_value=MINIMUM_LADDER_LENGTH, max_value=MAXIMUM_LADDER_LENGTH,
value=8, step=1
)
limit = st.number_input("Solutions to display at once", min_value=1, max_value=10, value=5)
st.session_state["start_word_input"] = start_word_input
st.session_state["end_word_input"] = end_word_input
if start_word_input:
word_length = len(start_word_input)
if MINIMUM_WORD_LENGTH <= word_length <= MAXIMUM_WORD_LENGTH:
with st.spinner("Loading dictionary..."):
dictionary = Dictionary(word_length)
st.session_state['dictionary'] = dictionary
st.session_state['dictionary_load_time'] = perf_counter()
if st.button("Solve") or st.session_state.button:
st.session_state.button = True
if 'dictionary' not in st.session_state:
st.markdown(red("⚠️ Please enter a valid start word length between 2 and 15 characters."), unsafe_allow_html=True)
return
dictionary = st.session_state['dictionary']
dictionary_load_time = (perf_counter() - st.session_state['dictionary_load_time']) * 1000
start_word = validate_word(dictionary, start_word_input)
end_word = validate_word(dictionary, end_word_input)
if not start_word or not end_word:
return
st.markdown(f"Took {green('%.2fms' % dictionary_load_time)} to load dictionary.", unsafe_allow_html=True)
puzzle = Puzzle(start_word, end_word)
if st.session_state.max_ladder_length == -1:
start = perf_counter()
min_ladder = puzzle.calculate_minimum_ladder_length()
took = (perf_counter() - start) * 1000
if min_ladder is None:
st.markdown(red(f"Cannot solve '{start_word}' to '{end_word}' (took {took:.2f}ms to determine that)."), unsafe_allow_html=True)
return
st.session_state.max_ladder_length = min_ladder
st.markdown(f"Took {green('%.2fms' % took)} to determine minimum ladder length of {green(min_ladder)}.", unsafe_allow_html=True)
solver = Solver(puzzle)
with st.spinner("Solving the puzzle..."):
start = perf_counter()
solutions = solver.solve(st.session_state.max_ladder_length)
took = (perf_counter() - start) * 1000
st.session_state.solutions = [[str(word) for word in solution.ladder] for solution in solutions]
if len(solutions) == 0:
st.markdown(red(f"Took {took:.2f}ms to find no solutions (explored {solver.explored_count} solutions)."), unsafe_allow_html=True)
else:
# Limit the number of solutions displayed based on the 'limit' input from the settings
limited_solutions = st.session_state.solutions[:limit]
st.markdown(f"Took {green('%.2fms' % took)} to find {green(len(limited_solutions))} solutions (explored {green(solver.explored_count)} solutions).", unsafe_allow_html=True)
user_solution_input = st.text_area("Enter your word ladder solution (comma separated):", st.session_state.user_solution_input)
st.session_state.user_solution_input = user_solution_input
if st.button("Submit Solution"):
if st.session_state.user_solution_input:
user_solution_list = [word.strip().upper() for word in st.session_state.user_solution_input.split(",")]
# st.write(f"User's solution (cleaned): {user_solution_list}")
st.session_state.user_solution = user_solution_list
if any([user_solution_list == solution for solution in limited_solutions]):
st.success("Congratulations! Your solution is correct.")
else:
st.error("Your solution is incorrect. Here are the closest solutions:")
closest_solutions = find_closest_solutions(user_solution_list, limited_solutions)
for solution in closest_solutions:
st.markdown(" ➔ ".join(solution), unsafe_allow_html=True)
else:
st.write("⚠️ Please enter a word ladder solution before submitting.")
def validate_word(dictionary, word_input):
word = dictionary.get(word_input.upper())
if word is None:
st.markdown(red(f"⚠️ Word '{word_input}' does not exist!"), unsafe_allow_html=True)
elif word.is_island:
st.markdown(red(f"⚠️ Word '{word_input}' is an island word (cannot change single letter to form another word)."), unsafe_allow_html=True)
else:
return word
return None
def find_closest_solutions(user_solution, solutions):
solutions_with_ratio = [
(solution, difflib.SequenceMatcher(None, user_solution, solution).ratio())
for solution in solutions
]
solutions_with_ratio.sort(key=lambda x: x[1], reverse=True)
closest_solutions = [solution for solution, ratio in solutions_with_ratio]
return closest_solutions
def highlight_changes_in_ladder(ladder):
highlighted_ladder = []
for i in range(len(ladder) - 1):
word1 = ladder[i]
word2 = ladder[i + 1]
for j in range(len(word1)):
if word1[j] != word2[j]:
highlighted_word = (
word1[:j] +
f'<span style="color: yellow; font-weight: bold;">{word1[j]}</span>' +
word1[j+1:]
)
highlighted_ladder.append(highlighted_word)
break
highlighted_ladder.append(" ➔ ")
highlighted_ladder.append(ladder[-1])
return "".join(highlighted_ladder)
if __name__ == "__main__":
main()