Next bigger number with the same digits – Codewars Kata – How can I improve it

Question:

So this kata says:

You have to create a function that takes a positive integer number and
returns the next bigger number formed by the same digits:

12 ==> 21
513 ==> 531
2017 ==> 2071

If no bigger number can be composed using those digits, return -1:

9 ==> -1
111 ==> -1
531 ==> -1

I wrote a code to it after so many failures despite the exercise being pretty simple. I was wondering if there are ways i could improve my code, and if there are , please point them out, I’m only trying to understand how to think better.

def next_bigger(num):
# First I checked it returns -1 if numbers already have descending digits and there's no bigger number
 if int("".join(sorted(str(num), reverse=True))) == num:  
     return -1
# Then I converted the number num into a list of strings
 num_list= list(str(num)) 
# Iterated through the num list. starting from the last number and going backwards
 for i in range(len(num_list) -1, 0, -1):
       # If one digit is bigger than it's predecessor:
        if num_list[i] > num_list[i-1]:     
            A=num_list[:i]    # Slices num_list into 2 lists, A
            B=num_list[i:]    # And B
            break                 
# I think there's a better way to do this next part, but nothing else came into my mind

#     So I made a clone of the second list and sorted it ascendantly
# for the smallest number to be in the first place([0])
 B_list_for_min_num=sorted(list(B))   

# And deleted every minimum number (from the copied B list) that
#was smaller than the last digit in list A through a While loop
 while B_list_for_min_num[0] <= A[-1]:    
     del B_list_for_min_num[0]  

# Then swapped the last digit from list A and the minimum digit from the copied B list, but with the digit from the original B list
 B[B.index(min(B_list_for_min_num))], A[-1] = A[-1], B[B.index(min(B_list_for_min_num))]
# Then sorted the changed B list ascendently to make sure it will result exactly the next bigger number
 B = sorted(B)
# Then joined the lists together
 finish = A + B
# And turned them into an integer     
 result = int(''.join(map(str, finish)))
# Aaaand returned the result 
 return result 
Asked By: Christi Tanul

||

Answers:

Here!
Thanks to Tom Ron for this edit: The integrity of the sort was lost due to setting as a set. This code works because the list conversion is done before the sort instead of after!

def main():
        someInt = 124
        num = str(someInt)
        listOfNums = set([int(''.join(nums)) for nums in itertools.permutations(num, len(num))])   
        listOfNums = sorted(list(listOfNums))
        try:
                print(listOfNums[listOfNums.index(someInt)+1])
        except Exception:
                print(-1)
main() 

someInt = 111
output = -1


someInt: 12451251125
Output: 12451251152

Itertools does most of the work for you. You create the permutations, which is a list of every combination of the digits. Then, make sure they’re ints, then sort! Since they’re sorted, the number after the original in the list is bound to be the next highest. If there’s no number after the original, return -1! We convert the list into a set and then back again because we want to get rid of duplicates.
We can also prematurely exit if an answer is too obvious. Right after the conversion to a string we can add:

if num == num[0] * len(num):
        print(-1)

(Although in CodeWars you want to return instead of print)

Answered By: KuboMD

Just adding an answer for completion, Though i would recommend just trying to get an intuition for why the logic works and then implementing it yourself.
The key thing to realize is that the making bigger numbers essentially is like trying to greedily find the earliest place to swap a bigger digit from the right hand side to somewhere on its left. This is why if the number is already sorted in descending order of digits, you will not have a larger number possible.

num = list('1222')
#start from the last digit
index_to_swap = len(num) - 1
#iterate from 2nd last digit backwards till the start.
# Note that range is right-exclusive, so you need to write -1 to actually reach 0 in this case.
for i in range(len(num) - 2, -1, -1):
    #if you find a smaller digit while going in reverse
    if num[i] < num[index_to_swap]:
        # then swap in place.
        num[index_to_swap], num[i] = num[i], num[index_to_swap]
        print(num)
        break
    else:
        # otherwise, this digit is same or larger from right to left, use this digit now for consideration
        index_to_swap = i
else:
    # this is a for: else shortcut syntax, if for loop ever runs to completion without "break", This part executes
    print('its already largest')
Answered By: Paritosh Singh

It checks the digits of the given number from the last and checks if it is greater than the one before it. So if the number were to be 1234, we would check from the end i.e 4 > 3? Since that is true, we swap the two number’s positions in the array to obtain [1,2,4,3] and convert it to an integer number at the end. It prolly works for every test case. Hope you understood it!

import math


def next_large_number(number):
    number_arr = []
    number_length = 0

    # converting the number into an array with integer elements
    # Note: this will be a reverse array of the desired number so you have to
    # reverse the array after the calculation is done
    while number != 0:
        number_arr.append(number % 10)
        number = math.trunc(number/10)
        number_length += 1

    number_arr = number_arr[::-1]  # reversing the array

    # from the back, checking if the last most number is greater than the one
    # before it. If it is, swap, else ignore.
    for i in range(number_length - 1, -1, -1):
        # second loop to check every element
        for j in range(i - 1, -1, -1):
            if number_arr[i] > number_arr[j]:
                number_arr[i], number_arr[j] = number_arr[j], number_arr[i]
                # changing the contents of the array into an integer
                # if you do the string method, it'll prolly take some time
                # calculation is preferred for better memory management
                number_arr = number_arr[:j + 1] + sorted(number_arr[j+1:])
                counter_variable = 1
                result = 0
                for k in range(number_length):
                    result += number_arr[k] * 
                        (10 ** (number_length - counter_variable))
                    counter_variable += 1

                return result


if __name__ == "__main__":
    given_num = int(input("Please enter a number: "))
    print(next_large_number(given_num))
Answered By: Mythic

If some one is looking for help in kotlin

I solved in Kotlin using this guide https://dev.to/kopiro/kata-resolution-next-bigger-number-with-the-same-digits-41mj .

fun NextBiggerNumber(n: Long): Long {
    if (n < 0) return -1
    var myPivot = 0
    var beforePivot = ""
    var afterPivot = ""
    val digitList = n.toString().map { it.toString().toInt() }.toMutableList()
    for (pos in digitList.lastIndex downTo 0) {
        if (pos > 0) {
            if (digitList[pos] > digitList[pos - 1]) {
                myPivot = digitList[pos - 1]
                beforePivot = n.toString().substring(0, pos - 1)
                afterPivot = n.toString().substring(pos)
                break
            } else if (digitList[pos] < digitList[pos - 1] && pos == digitList.lastIndex) {
                continue
            }
        }
    }

    val smallLarger = findSmallLarger(afterPivot, myPivot)

    val newAfterString = if (afterPivot.length > 1) {
        StringBuilder(afterPivot).append(myPivot.toString()).removeRange(
            smallLarger.second, smallLarger.second + 1
        ).toString().split("").sorted().joinToString("")
    } else {
        StringBuilder(afterPivot).append(myPivot.toString()).toString()
    }

    val solution = if (beforePivot.isEmpty()) {
        "${smallLarger.first}$newAfterString".toLong()
    } else if (smallLarger.first.isEmpty()) {
        "$beforePivot$newAfterString".toLong()
    } else {
        "$beforePivot${smallLarger.first}$newAfterString".toLong()
    }

    return if (solution > 0L) solution else -1
}


fun findSmallLarger(afterPivot: String, myPivot: Int): Pair<String, Int> {
    var mySmallLarger = ""
    var mySmallLargerPos = 0
    val digitList = afterPivot.map { it.toString().toInt() }.toMutableList()

    if (afterPivot.length > 1) {
        for (pos in digitList.lastIndex downTo 0) {
            if (digitList[pos] > myPivot) {
                mySmallLargerPos = pos
                mySmallLarger = digitList[pos].toString()
                break
            }
        }
    }
    return Pair(mySmallLarger, mySmallLargerPos)
}

I know there is a better way ( like the best answer)

fun nextBiggerNumber(n: Long): Long {
    val text = n.toString().toMutableList()
    for (i in text.size - 2 downTo 0) {
        if (text[i] < text[i + 1]) {
            val tail = text.subList(i + 1, text.size)
            val min = tail.withIndex().filter { it.value > text[i] }.minBy { it.value }!!
            text[i + 1 + min.index] = text[i]
            text[i] = min.value
            tail.sort()
            return text.joinToString("").toLong()
        }
    }
    return -1
}

user cases:

println(nextBiggerNumber(2017) == 2071L)
 
    println(nextBiggerNumber(12) == 21L)
 
    println(nextBiggerNumber(144) == 414L)
  
    println(nextBiggerNumber(513) == 531L)
   
    println(nextBiggerNumber(21581957621) == 21581961257L)
 
    println(nextBiggerNumber(135567381) == 135567813L)
   
    println(nextBiggerNumber(1234567890) == 1234567908L)

    println(nextBiggerNumber(9876543210) == -1L)
Answered By: Cristofer
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.