def common_count(t0, t1):
"returns the length of the longest common prefix"
for i, pair in enumerate(zip(t0, t1)):
if pair[0] != pair[1]:
return i
return i
def group_by_longest_prefix(iterable):
"given a sorted list of strings, group by longest common prefix"
longest = 0
out = []
for t in iterable:
if out: # for there are previous entries
# determine length of prefix in common with prev line
common = common_count(t, out[-1])
# if the current entry has a shorted prefix, output the previous
# entries then start a new group
if common < longest:
yield out
longest = 0
out = []
# otherwise, just update the target prefix length
else:
longest = common
# add the current entry to the group
out.append(t)
# ouput remaining entries as the last group
if out:
yield out
text = """
TOKYO-BLING.1 H02-AVAILABLE
TOKYO-BLING.1 H02-MIDDLING
TOKYO-BLING.1 H02-TOP
TOKYO-BLING.2 H04-USED
TOKYO-BLING.2 H04-AVAILABLE
TOKYO-BLING.2 H04-CANCELLED
WAY-VERING.1 H03-TOP
WAY-VERING.2 H03-USED
WAY-VERING.2 H03-AVAILABLE
WAY-VERING.1 H03-CANCELLED
"""
T = sorted(t.strip() for t in text.split("\n") if t)
for L in group_by_longest_prefix(T):
print L
ZGVmIGNvbW1vbl9jb3VudCh0MCwgdDEpOgogICJyZXR1cm5zIHRoZSBsZW5ndGggb2YgdGhlIGxvbmdlc3QgY29tbW9uIHByZWZpeCIKICBmb3IgaSwgcGFpciBpbiBlbnVtZXJhdGUoemlwKHQwLCB0MSkpOgogICAgaWYgcGFpclswXSAhPSBwYWlyWzFdOgogICAgICByZXR1cm4gaQogIHJldHVybiBpCgpkZWYgZ3JvdXBfYnlfbG9uZ2VzdF9wcmVmaXgoaXRlcmFibGUpOgogICJnaXZlbiBhIHNvcnRlZCBsaXN0IG9mIHN0cmluZ3MsIGdyb3VwIGJ5IGxvbmdlc3QgY29tbW9uIHByZWZpeCIKICBsb25nZXN0ID0gMAogIG91dCA9IFtdCgogIGZvciB0IGluIGl0ZXJhYmxlOgogICAgaWYgb3V0OiAjIGZvciB0aGVyZSBhcmUgcHJldmlvdXMgZW50cmllcyAKCiAgICAgICMgZGV0ZXJtaW5lIGxlbmd0aCBvZiBwcmVmaXggaW4gY29tbW9uIHdpdGggcHJldiBsaW5lCiAgICAgIGNvbW1vbiA9IGNvbW1vbl9jb3VudCh0LCBvdXRbLTFdKQoKICAgICAgIyBpZiB0aGUgY3VycmVudCBlbnRyeSBoYXMgYSBzaG9ydGVkIHByZWZpeCwgb3V0cHV0IHRoZSBwcmV2aW91cyAKICAgICAgIyBlbnRyaWVzIHRoZW4gc3RhcnQgYSBuZXcgZ3JvdXAKICAgICAgaWYgY29tbW9uIDwgbG9uZ2VzdDoKICAgICAgICB5aWVsZCBvdXQKICAgICAgICBsb25nZXN0ID0gMAogICAgICAgIG91dCA9IFtdCiAgICAgICMgb3RoZXJ3aXNlLCBqdXN0IHVwZGF0ZSB0aGUgdGFyZ2V0IHByZWZpeCBsZW5ndGgKICAgICAgZWxzZToKICAgICAgICBsb25nZXN0ID0gY29tbW9uCiAgICAKICAgICMgYWRkIHRoZSBjdXJyZW50IGVudHJ5IHRvIHRoZSBncm91cAogICAgb3V0LmFwcGVuZCh0KQoKICAjIG91cHV0IHJlbWFpbmluZyBlbnRyaWVzIGFzIHRoZSBsYXN0IGdyb3VwCiAgaWYgb3V0OgogICAgeWllbGQgb3V0Cgp0ZXh0ID0gIiIiClRPS1lPLUJMSU5HLjEgSDAyLUFWQUlMQUJMRQpUT0tZTy1CTElORy4xIEgwMi1NSURETElORwpUT0tZTy1CTElORy4xIEgwMi1UT1AKVE9LWU8tQkxJTkcuMiBIMDQtVVNFRApUT0tZTy1CTElORy4yIEgwNC1BVkFJTEFCTEUKVE9LWU8tQkxJTkcuMiBIMDQtQ0FOQ0VMTEVECldBWS1WRVJJTkcuMSBIMDMtVE9QCldBWS1WRVJJTkcuMiBIMDMtVVNFRApXQVktVkVSSU5HLjIgSDAzLUFWQUlMQUJMRQpXQVktVkVSSU5HLjEgSDAzLUNBTkNFTExFRAoiIiIKClQgPSBzb3J0ZWQodC5zdHJpcCgpIGZvciB0IGluIHRleHQuc3BsaXQoIlxuIikgaWYgdCkKCmZvciBMIGluIGdyb3VwX2J5X2xvbmdlc3RfcHJlZml4KFQpOgogIHByaW50IEwKCg==
['TOKYO-BLING.1 H02-AVAILABLE', 'TOKYO-BLING.1 H02-MIDDLING', 'TOKYO-BLING.1 H02-TOP']
['TOKYO-BLING.2 H04-AVAILABLE', 'TOKYO-BLING.2 H04-CANCELLED', 'TOKYO-BLING.2 H04-USED']
['WAY-VERING.1 H03-CANCELLED', 'WAY-VERING.1 H03-TOP']
['WAY-VERING.2 H03-AVAILABLE', 'WAY-VERING.2 H03-USED']