Search code examples
pythonstringlistinteger

Convert list of integers into a specific string format


I'm dealing with a list of integers that represent the pages in which a keyword was found. I would like to build block of code that converts this list into a string with a specific format that follow some simple rules. Single integers are converted into string. Consecutive integers are considered as intervals (left bound, hyphen, right bound) and then converted into string. Each conversion is comma separated. This is an example of input and desired output:

input = [4, 5, 6, 7, 8, 9, 10, 22, 23, 26, 62, 63, 113, 137, 138, 139]
desired_output = "4-10, 22, 23, 26, 62, 63, 113, 137-139"

I wrote this code:

res = [4, 5, 6, 7, 8, 9, 10, 22, 23, 26, 62, 63, 113, 137, 138, 139]

if len(res)>0:    
    resStr = str(res[0])
    isConsecutive = False
    for index in range(1, len(res)):
        diff = res[index] - res[index-1]
        if diff == 1:
            isConsecutive = True
            if index == len(res)-1:
                resStr = resStr + "-" + str(res[index])
            continue
        else:
            if isConsecutive:
                isConsecutive = False
                resStr = resStr + "-" + str(res[index-1]) + ", " + str(res[index])
            else:
                resStr = resStr + ", " + str(res[index])
        

print(res)
print(resStr)

This code gives me as a result:

4-10, 22-23, 26, 62-63, 113, 137-139

It doesn't recognize that only two consecutive numbers have not be considered as an interval: "22, 23" and not "22-23", as well as "62, 63" and not "62-63". How can be solved this issue? Is there a simpler or more efficient way to perform the conversion?


Solution

  • Track first and last of a range of consecutive numbers, collect the string of the range when a number is non-consecutive. I used a sub-function to avoid repeated code:

    res = [4, 5, 6, 7, 8, 9, 10, 22, 23, 26, 62, 63, 113, 137, 138, 139]
    
    def span(numbers):
        if len(numbers) == 0:  # empty case
            return ''
        result = []
        def _process(first, last):
            if first != last:  # range, but handle consecutive case
                result.append(f"{first}{'-' if last - first > 1 else ', '}{last}")
            else:
                result.append(str(first))  # first==last, single number
        first = last = numbers[0]  # initialize range tracking to first number
        for item in numbers[1:]:  # iterate remaining numbers
            if item <= last:  # input not increasing
                raise ValueError('not consecutive numbers')
            if item == last + 1:  # next number is consecutive
                last = item
            else:
                _process(first, last)  # next number not consecutive
                first = last = item    # reset for next range
        _process(first, last)  # no more numbers, finish last range
        return ', '.join(result)
    
    print(res)
    print(span(res))
    
    # Test cases
    print(span([]))
    print(span([1]))
    print(span([1,2]))
    print(span([1,2,3]))
    print(span([1,2,4]))
    print(span([1,3]))
    print(span([1,3,4]))
    print(span([1,3,4,5]))
    print(span([1,2,3,5]))
    print(span([1,2,4,5,6]))
    print(span([1,2,3,5,6]))
    print(span([1,2,3,5,6,7]))
    

    Output:

    [4, 5, 6, 7, 8, 9, 10, 22, 23, 26, 62, 63, 113, 137, 138, 139]
    4-10, 22, 23, 26, 62, 63, 113, 137-139
    
    1
    1, 2
    1-3
    1, 2, 4
    1, 3
    1, 3, 4
    1, 3-5
    1-3, 5
    1, 2, 4-6
    1-3, 5, 6
    1-3, 5-7