Puzzle #7

 9th July 2023 at 7:01pm

First part

This puzzle is a little more complicated than usual. I did not take too long to find a solution that would fit the examples,

def get_three_sections(
        line: str
        ) -> list[str, str, str]:
    LEFT_BRACKET = line.index('[')
    RIGHT_BRACKET = line.index(']')
    return [line[:LEFT_BRACKET],
            line[LEFT_BRACKET + 1:RIGHT_BRACKET],
            line[RIGHT_BRACKET + 1:]]

def get_all_substrings_from_section(s: str):
    return list(map(lambda i: s[i:i + 4],
                          range(0, len(s) - 3)))

def substring_is_abba(s: str):
    return (s[0] == s[3] and 
            s[1] == s[2] and
            s[0] != s[1])

def abba_exists(section: str) -> bool:
    return any(map(substring_is_abba,
                   get_all_substrings_from_section(section)))
        
def has_tls(line: str):
    left, middle, right = get_three_sections(line)
    return not abba_exists(middle) and \
            (abba_exists(left) or \
            abba_exists(right))

print(list(map(has_tls,
               EXAMPLE.splitlines())))

but, of course, I overlooked the proper format of the input, which is of the form (xxx[yyy])+xxx; the xxx[yyy] blocks appear at least once, and then there's surely one last block outside of square brackets. This means that not only the get_three_sections function doesn't work anymore, I will have to abstract the code a little further to make it work for any given length of input. This is not necessarily a big problem, but I'm also thinking of further optimising the code; however, I'll try to progress as much as I can without committing to early optimisations, which can bring problems very early. I will, however, still try to do this functionally.


def get_all_substrings_from_section(
        s: str
    ) -> list[str]:
    return list(map(lambda i: s[i:i + 4],
                          range(0, len(s) - 3)))

def substring_is_abba(s: str) -> bool:
    return (s[0] == s[3] and 
            s[1] == s[2] and
            s[0] != s[1])

def produce_list_of_sections(
        line: str
        ) -> list[str]:
    """given a line of the form ([a-z]+\[[a-z]+\])+[a-z]+,
    returns a list of all strings outside and inside
    the square brackets, in the pattern
    [inside, outside,...,inside]"""
    return re.split(r'\[|\]', line)
    
def abba_exists_in_section(section: str) -> bool:
    return any(map(substring_is_abba,
                   get_all_substrings_from_section(section)))
        
def has_tls(line: str):
    all_sections = produce_list_of_sections(line)
    supernet = all_sections[::2]
    hypernet = all_sections[1::2]
    return all(map(lambda sec: not abba_exists_in_section(sec),
                   supernet)) and \
           any(map(lambda sec: abba_exists_in_section(sec),
                   hypernet))

print(len(list(filter(lambda x: x is True,
                 map(has_tls, INPUT)))))

I knew there would be a better way to do the final print instruction, and Phind gave me a hint.

print(sum(has_tls(x) for x in INPUT))

Much better, in fact, and the rest of the advice came for free:

Use generator expressions (x for x in list) instead of list comprehension when you don't need the list itself, but want to process each element in the list.

Second part

The second part was a little trickier, but I'm proud of my approach and the results. I had some trouble halfway,

def generate_all_aba(net: list):
    substrings = list(map(get_all_substrings_from_section,
                      (section for section in net)))
    return filter(lambda x: substring_is_aba(x),
                  substrings
                  )

(I was assigning variables so they could get printed for debugging). Here, the map call was creating a two-dimensional array; the moment this got fixed, all of the rest came in place very quickly.

def produce_list_of_sections(
        line: str
        ) -> list[str]:
    """given a line of the form ([a-z]+\[[a-z]+\])+[a-z]+,
    returns a list of all strings outside and inside
    the square brackets, in the pattern
    [inside, outside,...,inside]"""
    return re.split(r'\[|\]', line)

def get_all_substrings_from_section(
        s: str
    ) -> list[str]:
    return map(lambda i: s[i:i + 3],
                          range(0, len(s) - 2))

def substring_is_aba(s: str) -> bool:
    return s[0] == s[2]

def generate_all_aba(net: list):
    return (s for s in chain.from_iterable(get_all_substrings_from_section(section)
                                           for section in net)
            if substring_is_aba(s))

def match_aba_to_bab(aba: str, bab: str) -> bool:
    return aba[0] == bab[1] and bab[0] == aba[1]
        
def has_ssl(line: str):
    all_sections = produce_list_of_sections(line)
    supernet = all_sections[::2]
    hypernet = all_sections[1::2]
    return any(map(lambda x: match_aba_to_bab(*x),
                   product(generate_all_aba(hypernet),
                            generate_all_aba(supernet))))

print(sum(has_ssl(x) for x in INPUT))

This was nice. Tools like Phind are really invaluable; I feel like I'm learning so much over this process. I start by reading the exercise, then sketching some approaches on paper – more often than not, this will already trigger some debugging because I realise there is a mismatch with two functions, or something of that kind – and then, I code. Phind comes in handy from time to time if I have any specific issue I'm not understanding. Programming is going to change a lot in the forthcoming years.