aboutsummaryrefslogtreecommitdiff
path: root/challenge-329/ysth/python/ch-2.py
diff options
context:
space:
mode:
Diffstat (limited to 'challenge-329/ysth/python/ch-2.py')
-rw-r--r--challenge-329/ysth/python/ch-2.py113
1 files changed, 113 insertions, 0 deletions
diff --git a/challenge-329/ysth/python/ch-2.py b/challenge-329/ysth/python/ch-2.py
new file mode 100644
index 0000000000..444e31c8d3
--- /dev/null
+++ b/challenge-329/ysth/python/ch-2.py
@@ -0,0 +1,113 @@
+import sys
+from string import ascii_letters
+import random
+from enum import IntEnum
+
+def longest_nice_substring(string: str) -> str:
+
+ # longest nice substring found so far
+ longest_nice_substring: str = ''
+
+ # characters that we know can't form part of a not yet found nice substring
+ never_nice: set[str] = set(string) - set(string.swapcase())
+
+ # loop over starting indexes while it is possible to find a longer nice substring
+ next_starting_index: int = 0
+ while len(string) - next_starting_index > len(longest_nice_substring):
+ starting_index: int = next_starting_index
+ c: str = string[starting_index]
+ next_starting_index += 1
+
+ # can we rule out any substring starting here?
+ if c in never_nice:
+ continue
+ # shortest substring that may be nice will end with the opposite character
+ ending_index: int = string.find(c.swapcase(), next_starting_index) + 1
+ if ending_index == 0:
+ never_nice.add(c)
+ continue
+
+ # what we know so far about substrings starting at this starting index
+ substring_prefix: str = string[starting_index:ending_index]
+ nice: set[str] = set(substring_prefix) & set(substring_prefix.swapcase())
+ not_yet_nice: set[str] = set(substring_prefix) - nice
+
+ # found a nice substring?
+ if not not_yet_nice:
+ if len(substring_prefix) > len(longest_nice_substring):
+ longest_nice_substring = substring_prefix
+ # any nice strings starting in the middle of or just after this nice
+ # string will also be nice if started at the beginning of it, so
+ # this iteration of the outer loop will find them and the next
+ # iteration can be farther on
+ next_starting_index = ending_index + 1
+
+ # look for longer nice substrings starting at this starting index
+ for ending_index in range(ending_index, len(string)):
+ c = string[ending_index]
+
+ # character we know there's no match for later?
+ if c in never_nice:
+ break
+
+ # character not yet seen in this substring?
+ if c not in nice and c not in not_yet_nice:
+ # completes an upper/lower pair?
+ c_opposite: str = c.swapcase()
+ if c_opposite in not_yet_nice:
+ not_yet_nice.remove(c_opposite)
+ nice.update(c, c_opposite)
+ else:
+ not_yet_nice.add(c)
+
+ # found a nice substring?
+ if not not_yet_nice:
+ if ending_index - starting_index >= len(longest_nice_substring):
+ longest_nice_substring = string[starting_index:ending_index+1]
+ # any nice strings starting in the middle of or just after this
+ # nice string will also be nice if started at the beginning of
+ # it, so this iteration of the outer loop will find them and the
+ # next iteration can be farther on
+ next_starting_index = ending_index + 2
+
+ return longest_nice_substring
+
+def simple_longest_nice_substring(string: str) -> str:
+ longest_nice_substring: str = ''
+ for starting_index in range(0, len(string)):
+ nice: set[str] = set()
+ not_yet_nice: set[str] = set()
+ for ending_index in range(starting_index, len(string)):
+ c: str = string[ending_index]
+ if c not in nice and c not in not_yet_nice:
+ if c.swapcase() in not_yet_nice:
+ not_yet_nice.remove(c.swapcase())
+ nice.update(c, c.swapcase())
+ else:
+ not_yet_nice.add(c)
+ if not not_yet_nice and ending_index - starting_index + 1 > len(longest_nice_substring):
+ longest_nice_substring = string[starting_index:ending_index+1]
+ return longest_nice_substring
+
+def main() -> None:
+ inputs: list[str] = sys.argv[1:]
+
+ # if given command line arguments, do those
+ if len(inputs):
+ for string in inputs:
+ print(f'{string:<30} -> {longest_nice_substring(string)}')
+
+ # otherwise, run an endless test of random values, comparing to simple version (stopping if an unexpected difference was found)
+ else:
+ checked: int = 0
+ while True:
+ checked += 1
+ string = ''.join(random.choice(ascii_letters) for i in range(random.randint(0,200)))
+ if simple_longest_nice_substring(string) != longest_nice_substring(string):
+ print(f'string: {string}\nsimple: {simple_longest_nice_substring(string)}\ncomplex: {longest_nice_substring(string)}')
+ break
+ if checked % 1000 == 0:
+ print(f'{checked}', end='\r')
+
+if __name__ == '__main__':
+ main()