How to convert a string into a ranged string where it shortens the string for better readability

Viewed 39

I have a function mixrange which makes a string like '1,2,4-6,8' and converts it into '1,2,4,5,6,8'. Now how can I do the exact opposite where I have an input '1,2,4,5,6,8' and it converts it into '1,2,4-6,8'? The function type casts it to int and a list (which is not a problem as i can convert it back to a normal string).

def mixrange(s):
    r = []
    for i in s.split(','):
        if '-' not in i:
            r.append(int(i))
        else:
            l,h = map(int, i.split('-'))
            r+= range(l,h+1)
    return r
4 Answers

I thought this was a nice exercise, so I solved it. Not necessarily the neatest solution, but hopefully works:

s = "1,2,4,5,6,8,11,12,13,14,15,16,22,23"
numbers = [int(e) for e in s.split(",")]

def first_block_end(numbers): # returns the last index of the block of consecutive numbers starting with the first number
    if len(numbers) == 1:
        return 0
    i = 0
    while i + 1 < len(numbers) and numbers[i + 1] == numbers[i] + 1:
        i += 1
    return i

result = []

while len(numbers) > 0:
    idx = first_block_end(numbers)
    if idx == 0:
        result.append(str(numbers[0]))
    elif idx == 1:
        result.append(str(numbers[0]))
        result.append(str(numbers[1]))
    else:
        result.append(str(numbers[0]) + "-" + str(numbers[idx]))
    numbers = numbers[idx + 1:]

result = ",".join(result)

print(result)

The decoding is quite straightforward but encoding seems a little more complex. Here's my offering:

def encode(string):
    numbers = list(map(int, string.split(',')))
    sequences = [[0, 0]]
    for i, v in enumerate(numbers[1:], 1):
        if v == numbers[i-1] + 1:
            sequences[-1][1] = i
        else:
            sequences.append([i, i])
    result = []
    for seq in sequences:
        if seq[1] > seq[0] + 1:
            result.append(f'{numbers[seq[0]]}-{numbers[seq[1]]}')
        else:
            for i in range(seq[0], seq[1]+1):
                result.append(str(numbers[i]))
    return ','.join(result)


def decode(string):
    result = []
    for token in string.split(','):
        lo, *hi = token.split('-')
        if hi:
            result += range(int(lo), int(*hi)+1)
        else:
            result.append(int(lo))
    return ','.join(map(str, result))

v = decode('1,2,4-6,8-10,12,13,15-20')
print(v)

w = encode(v)
print(w)

Output:

1,2,4,5,6,8,9,10,12,13,15,16,17,18,19,20
1,2,4-6,8-10,12,13,15-20

Here is another short way:

def to_range(s):
    if not s:       # handle empty string
        return ''
    prev = float('-inf')
    out = []
    i = 0
    for v in map(int,s.split(',')): # loop over each number
        if v == prev+1:             # if previous + 1, count
            i+=1
        else:                       # new group
            if i>0:                 # if successive add last as range
                out[-1] += f'-{prev}'
            out.append(str(v))      # add next start
            i = 0
        prev = v
    if i>0:                         # handle last number
        out[-1] += f'-{prev}'
    return ','.join(out)            # return as string

example:

to_range('1,2,4,5,6,8,1,2,3,4')
# '1-2,4-6,8,1-4'

to_range('1,2,3,3')
# '1-3,3'

Edit: I edited the function according to your needs, as I understood. You were meant to show it as a range if the elements were having an increment of 1. This is what I achieved.

def exact_oposite(nums):
    data_str = nums.split(',')
    start = 0
    end = None
    output_list = []
    lst_len = len(data_str)
    for index, num in enumerate(data_str):
        if index+1 != lst_len:
            if int(data_str[index+1]) - int(num) > 1:
                if start is None:
                    start = data_str[index+1]
                    output_list.append(num)
                else:
                    end = index
                    if start == end:
                        output_list.append(data_str[start])
                    else:
                        if end-start <= 1:
                            output_list.append(data_str[start])
                            output_list.append(data_str[end])
                        else:
                            output_list.append(f'{data_str[start]}-{data_str[end]}')
                    start = index+1
                    end = None
            else:
                if start is None and end is None:
                    output_list.append(num)
        else:
            if end is None and start is not None:
                end = index
                if start == end:
                    output_list.append(data_str[start])
                else:
                    if end-start <= 1:
                        output_list.append(data_str[start])
                        output_list.append(data_str[end])
                    else:
                        output_list.append(f'{data_str[start]}-{data_str[end]}')
            else:
                output_list.append(num)

    return ",".join(output_list)
Related