aboutsummaryrefslogtreecommitdiff
path: root/challenge-329/ysth/python/ch-2.py
blob: 444e31c8d3f961f5081a265844c5b0d1a912ac47 (plain)
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
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()