from itertools import islice, zip_longest
import string
from typing import Iterator, Sequence
def sliding_window_of[ T] ( window_length: int , iterable: Sequence[ T] ) -> Iterator[ tuple [ T, *tuple [ T | None , ...] ] ] :
assert window_length >= 1
window_its = [
islice( iterable, i, None , None )
for i in range ( window_length)
]
yield from zip_longest( *window_its) # type:ignore
def get_opts( options: str ) -> list [ str ] :
# validations
if not all ( opt == '/' or opt in string .ascii_lowercase for opt in options) :
raise ValueError ( "options string must be only lowercase letters or slashes" )
if options.startswith ( '/' ) or options.endswith ( '/' ) :
raise ValueError ( "must not start of end with a slash" )
if any ( window == ( '/' , '/' ) for window in sliding_window_of( 2 , options) ) :
raise ValueError ( "a slash cannot be followed by another slash" )
triples = sliding_window_of( 3 , options)
opts = list [ str ] ( )
# first case is special because we need to get the left-most character
match next( triples, None ) :
case ( a, '/' , str ( b) ) :
opts.append ( a+b)
case ( a, _, '/' ) :
opts.append ( a)
case ( a, str ( b) , _) :
opts.extend ( ( a, b) )
for triple in triples:
match triple:
case ( '/' , _, _) | ( _, _, '/' ) :
pass
case ( a, '/' , str ( b) ) :
opts.append ( a+b)
case ( _, str ( a) , _) :
opts.append ( a)
return opts
for opts in [ "abcd/efgh/ijk" , "abc//def" , "invalid!chars" , "/a" , "b/" ] :
try :
print ( f"{opts} => {get_opts(opts)}" )
except ValueError as ve:
print ( f"{opts} => {ve!r}" )
ZnJvbSBpdGVydG9vbHMgaW1wb3J0IGlzbGljZSwgemlwX2xvbmdlc3QKaW1wb3J0IHN0cmluZwpmcm9tIHR5cGluZyBpbXBvcnQgSXRlcmF0b3IsIFNlcXVlbmNlCgoKZGVmIHNsaWRpbmdfd2luZG93X29mW1RdKHdpbmRvd19sZW5ndGg6IGludCwgaXRlcmFibGU6IFNlcXVlbmNlW1RdKSAtPiBJdGVyYXRvclt0dXBsZVtULCAqdHVwbGVbVCB8IE5vbmUsIC4uLl1dXToKICAgIGFzc2VydCB3aW5kb3dfbGVuZ3RoID49IDEKICAgIHdpbmRvd19pdHMgPSBbCiAgICAgICAgaXNsaWNlKGl0ZXJhYmxlLCBpLCBOb25lLCBOb25lKQogICAgICAgIGZvciBpIGluIHJhbmdlKHdpbmRvd19sZW5ndGgpCiAgICBdCiAgICB5aWVsZCBmcm9tIHppcF9sb25nZXN0KCp3aW5kb3dfaXRzKSAgIyB0eXBlOmlnbm9yZQoKCmRlZiBnZXRfb3B0cyhvcHRpb25zOiBzdHIpIC0+IGxpc3Rbc3RyXToKICAgICMgdmFsaWRhdGlvbnMKICAgIGlmIG5vdCBhbGwob3B0ID09ICcvJyBvciBvcHQgaW4gc3RyaW5nLmFzY2lpX2xvd2VyY2FzZSBmb3Igb3B0IGluIG9wdGlvbnMpOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIm9wdGlvbnMgc3RyaW5nIG11c3QgYmUgb25seSBsb3dlcmNhc2UgbGV0dGVycyBvciBzbGFzaGVzIikKCiAgICBpZiBvcHRpb25zLnN0YXJ0c3dpdGgoJy8nKSBvciBvcHRpb25zLmVuZHN3aXRoKCcvJyk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigibXVzdCBub3Qgc3RhcnQgb2YgZW5kIHdpdGggYSBzbGFzaCIpCgogICAgaWYgYW55KHdpbmRvdyA9PSAoJy8nLCAnLycpIGZvciB3aW5kb3cgaW4gc2xpZGluZ193aW5kb3dfb2YoMiwgb3B0aW9ucykpOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoImEgc2xhc2ggY2Fubm90IGJlIGZvbGxvd2VkIGJ5IGFub3RoZXIgc2xhc2giKQoKICAgIHRyaXBsZXMgPSBzbGlkaW5nX3dpbmRvd19vZigzLCBvcHRpb25zKQogICAgb3B0cyA9IGxpc3Rbc3RyXSgpCgogICAgIyBmaXJzdCBjYXNlIGlzIHNwZWNpYWwgYmVjYXVzZSB3ZSBuZWVkIHRvIGdldCB0aGUgbGVmdC1tb3N0IGNoYXJhY3RlcgogICAgbWF0Y2ggbmV4dCh0cmlwbGVzLCBOb25lKToKICAgICAgICBjYXNlIChhLCAnLycsIHN0cihiKSk6CiAgICAgICAgICAgIG9wdHMuYXBwZW5kKGErYikKICAgICAgICBjYXNlIChhLCBfLCAnLycpOgogICAgICAgICAgICBvcHRzLmFwcGVuZChhKQogICAgICAgIGNhc2UgKGEsIHN0cihiKSwgXyk6CiAgICAgICAgICAgIG9wdHMuZXh0ZW5kKChhLCBiKSkKCiAgICBmb3IgdHJpcGxlIGluIHRyaXBsZXM6CiAgICAgICAgbWF0Y2ggdHJpcGxlOgogICAgICAgICAgICBjYXNlICgnLycsIF8sIF8pIHwgKF8sIF8sICcvJyk6CiAgICAgICAgICAgICAgICBwYXNzCiAgICAgICAgICAgIGNhc2UgKGEsICcvJywgc3RyKGIpKToKICAgICAgICAgICAgICAgIG9wdHMuYXBwZW5kKGErYikKICAgICAgICAgICAgY2FzZSAoXywgc3RyKGEpLCBfKToKICAgICAgICAgICAgICAgIG9wdHMuYXBwZW5kKGEpCgogICAgcmV0dXJuIG9wdHMKCgpmb3Igb3B0cyBpbiBbImFiY2QvZWZnaC9pamsiLCAiYWJjLy9kZWYiLCAiaW52YWxpZCFjaGFycyIsICIvYSIsICJiLyJdOgogICAgdHJ5OgogICAgICAgIHByaW50KGYie29wdHN9ID0+IHtnZXRfb3B0cyhvcHRzKX0iKQogICAgZXhjZXB0IFZhbHVlRXJyb3IgYXMgdmU6CiAgICAgICAgcHJpbnQoZiJ7b3B0c30gPT4ge3ZlIXJ9IikK