Reducible.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # File: Reducible.py
  2. # Description: Input a list of words and find the largest sized word
  3. # that can be reduced to smaller words. Then print all the words of
  4. # this size in alphabetical order.
  5. # Student Name: Anna Dougharty
  6. # Student UT EID: amd5933
  7. # Course Name: CS 313E
  8. # Unique Number: 52600
  9. # Date Created: 10/21/2021
  10. # Date Last Modified: 10/21/2021
  11. import sys
  12. # Input: takes as input a positive integer n
  13. # Output: returns True if n is prime and False otherwise
  14. def is_prime(n):
  15. if n == 1:
  16. return False
  17. limit = int(n ** 0.5) + 1
  18. div = 2
  19. while div < limit:
  20. if n % div == 0:
  21. return False
  22. div += 1
  23. return True
  24. # Input: takes as input a string in lower case and the size
  25. # of the hash table
  26. # Output: returns the index the string will hash into
  27. def hash_word(s, size):
  28. hash_idx = 0
  29. for j in range(len(s)):
  30. letter = ord(s[j]) - 96
  31. hash_idx = (hash_idx * 26 + letter) % size
  32. return hash_idx
  33. # Input: takes as input a string in lower case and the constant
  34. # for double hashing
  35. # Output: returns the step size for that string
  36. # USE A SMALL PRIME NUMBER FOR CONSTANT
  37. def step_size(s, const):
  38. return const - (hash_word(s, const))
  39. # Input: takes as input a string and a hash table
  40. # Output: no output; the function enters the string in the hash table,
  41. # it resolves collisions by double hashing
  42. def insert_word(s, hash_table):
  43. # find index of new word that string will hash into
  44. word_idx = hash_word(s, len(hash_table))
  45. # if this location in hash table is empty
  46. if hash_table[word_idx] == '':
  47. # hash word into table
  48. hash_table[word_idx] = s
  49. else:
  50. # find step size for double hashing (if collision occurs)
  51. step_val = step_size(s, 3)
  52. # only increase index by step value if table at index is NOT empty
  53. while hash_table[word_idx] != '':
  54. word_idx = (word_idx + step_val) % len(hash_table)
  55. # once an empty index has been found, hash word into the table
  56. hash_table[word_idx] = s
  57. # Input: takes as input a string and a hash table
  58. # Output: returns True if the string is in the hash table
  59. # and False otherwise
  60. def find_word(s, hash_table):
  61. word_idx = hash_word(s, len(hash_table))
  62. tracking_idx = word_idx
  63. if hash_table[tracking_idx] == s:
  64. return True
  65. else:
  66. # find step size for double hashing (if collision occurs)
  67. step_val = step_size(s, 1)
  68. count = 0
  69. # increase by step size until s at index is found
  70. while tracking_idx < len(hash_table) and hash_table[tracking_idx] != s and count < 3:
  71. tracking_idx = (tracking_idx + step_val) % (len(hash_table))
  72. if tracking_idx == word_idx:
  73. return False
  74. count += 1
  75. if tracking_idx >= len(hash_table):
  76. return False
  77. # return T/F if word is in hash table at tracking index
  78. return s == hash_table[tracking_idx]
  79. # Input: string s, a hash table, and a hash_memo
  80. # recursively finds if the string is reducible
  81. # Output: if the string is reducible it enters it into the hash memo
  82. # and returns True and False otherwise
  83. # LOOK INTO MEMOIZATION FOR THIS
  84. # Avoiding extra recursions in is_reducible by
  85. # checking for cases where it is impossible for
  86. # the word to be reducible
  87. def is_reducible(s, hash_table, hash_memo):
  88. # if the word 's' is the letter 'a' or 'i' or 'o',
  89. # then it has reached its final form
  90. if s == 'a' or s == 'i' or s == 'o':
  91. return True
  92. # if the word is found in the hash memo, then it is
  93. # by definition already reducible
  94. elif find_word(s, hash_memo):
  95. return True
  96. # if it still contains at least one of the three letters
  97. # 'a', 'i', or 'o', then it has potential to be reducible
  98. elif 'a' in s or 'i' in s or 'o' in s:
  99. # if word cannot be found
  100. if find_word(s, hash_table) is False:
  101. return False
  102. # check each of the smaller words
  103. for i in range(len(s)):
  104. new_word = s[:i] + s[i + 1:]
  105. # recursively call for each sub-word
  106. if is_reducible(new_word, hash_table, hash_memo):
  107. insert_word(new_word, hash_memo)
  108. return True
  109. # if the word fails all criteria above then it is not reducible
  110. else:
  111. return False
  112. # Input: string_list a list of words
  113. # Output: returns a list of words that have the maximum length
  114. def get_longest_words(string_list):
  115. max_word_length = 0
  116. # increase max length until largest size is found
  117. for word in string_list:
  118. if len(word) > max_word_length:
  119. max_word_length = len(word)
  120. # append words of max size to the list
  121. max_word_list = []
  122. for word in string_list:
  123. if len(word) == max_word_length:
  124. max_word_list.append(word)
  125. # sort list alphabetically
  126. max_word_list.sort()
  127. return max_word_list
  128. def main():
  129. # create an empty word_list
  130. word_list = []
  131. # read words from words.txt and append to word_list
  132. for line in sys.stdin:
  133. line = line.strip()
  134. word_list.append(line)
  135. # find length of word_list
  136. length_words = len(word_list)
  137. # determine prime number N that is greater than twice
  138. # the length of the word_list
  139. prime_num = (length_words * 2) + 1
  140. while is_prime(prime_num) is False:
  141. prime_num += 1
  142. # create an empty hash_list
  143. hash_table = []
  144. # populate the hash_list with N blank strings
  145. for i in range(prime_num):
  146. hash_table.append('')
  147. # hash each word in word_list into hash_list
  148. # for collisions use double hashing
  149. for word in word_list:
  150. insert_word(word, hash_table)
  151. # create an empty hash_memo of size M
  152. # we do not know a priori how many words will be reducible
  153. # let us assume it is 10 percent (fairly safe) of the words
  154. # then M is a prime number that is slightly greater than
  155. # 0.2 * size of word_list
  156. hash_memo = []
  157. hash_memo_size = int((len(word_list) * 0.2) + 1)
  158. while is_prime(hash_memo_size) is False:
  159. hash_memo_size += 1
  160. # populate the hash_memo with M blank strings
  161. for i in range(hash_memo_size):
  162. hash_memo.append('')
  163. # create an empty list reducible_words
  164. reducible_word_list = []
  165. # for each word in the word_list recursively determine
  166. # if it is reducible, if it is, add it to reducible_words
  167. # as you recursively remove one letter at a time check
  168. # first if the sub-word exists in the hash_memo. if it does
  169. # then the word is reducible and you do not have to test
  170. # any further. add the word to the hash_memo.
  171. for word in word_list:
  172. if is_reducible(word, hash_table, hash_memo):
  173. reducible_word_list.append(word)
  174. # find the largest reducible words in reducible_words
  175. reducible_word_largest = get_longest_words(reducible_word_list)
  176. # print the reducible words in alphabetical order
  177. # one word per line
  178. for word in reducible_word_largest:
  179. print(word)
  180. if __name__ == "__main__":
  181. main()