Why is this simple Python program not giving the correct output?

Question:

The challenge is given below:

You will be given an array of numbers. You have to sort the odd numbers in ascending order while leaving the even numbers at their original positions.

[7, 1]  =>  [1, 7]
[5, 8, 6, 3, 4]  =>  [3, 8, 6, 5, 4]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]  =>  [1, 8, 3, 6, 5, 4, 7, 2, 9, 0]

My code’s logic: The function takes a source array (src_arr). I create a new array of only the odd numbers and sort them in odd_arr. In the for-loop, the if-statement checks each whether each number in src_arr is odd and replaces it with a corresponding sorted odd number from the odd_arr.

def sort_array(src_arr):
    odd_arr = sorted([num for num in src_arr if num%2 != 0])
    i = 0
    for num in src_arr:
        if num%2 != 0:
            src_arr[src_arr.index(num)] = odd_arr[i]
            i += 1
    return src_arr

print(sort_array([5, 3, 2, 8, 1, 4]))

The input is [5, 3, 2, 8, 1, 4] and output of this should be [1, 3, 2, 8, 5, 4]. But I keep getting the output which is exactly same as the input. I did a pdb.trace() and everything works fine till it comes to the number 1 in the input list, and I cannot figure out why it is not giving the correct output.

Asked By: AB B

||

Answers:

The line "src_arr[src_arr.index(num)] = odd_arr[i]" may not always replace the correct element in src_arr with the corresponding odd number from odd_arr.

Try this:

def sort_array(src_arr):
    odd_arr = sorted([num for num in src_arr if num % 2 != 0])
    i = 0
    for j, num in enumerate(src_arr):
        if num % 2 != 0:
            src_arr[j] = odd_arr[i]
            i += 1
    return src_arr
Answered By: noobcoder

One of the other ways to look is you are changing the original array, so the number 1 has two indexes after you replace 5 with 1

Simply if you have a copy and change the copy while keeping the original array intact will solve your issue

def sort_array(src_arr):
    odd_arr = sorted([num for num in src_arr if num%2 != 0])
    new_arr = src_arr[:]
    i = 0
    for num in src_arr:
        if num%2 != 0:
            new_arr[src_arr.index(num)] = odd_arr[i]
            i += 1
    return new_arr

print(sort_array([5, 3, 2, 8, 1, 4]))
Answered By: Tushar

You chose to run the for-loop on src_arr and maintain a manual index i over odd_arr.

Personally I find it much more straightforward to run the for-loop on odd_arr and maintain a manual index i over src_arr.

This is because we want to look at every element of odd_arr in order, so a simple straightforward for-loop is appropriate; but src_arr is the array on which we’re skipping elements, so it’s easier to maintain our own index i and skip the elements we want to skip manually.

def sort_array(src_arr):
    odd_arr = sorted(num for num in src_arr if num % 2 != 0)
    i = 0
    for x in odd_arr:
        while src_arr[i] % 2 == 0:    # skip even numbers in src_arr
            i += 1
        src_arr[i] = x
        i += 1
    return src_arr

print(sort_array([5, 3, 2, 8, 1, 4]))
# [1, 3, 2, 8, 5, 4]
Answered By: Stef

You can even simplify the code a bit by making odd_arr an iterator on the sorted list of odd numbers, and simply calling next(odd_arr) when needed, without manually managing the index:

def sort_array(src_arr):
    odd_arr = iter(sorted([num for num in src_arr if num%2 != 0]))
    for idx, num in enumerate(src_arr):
        if num%2 != 0:
            src_arr[idx] = next(odd_arr)
    return src_arr

print(sort_array([5, 3, 2, 8, 1, 4]))

# [1, 3, 2, 8, 5, 4]
Answered By: Thierry Lathuille

There’re already some great answers posted. Here is just one more alternative – for fun. IT’s inspired by earlier comments too.


def sort_odd(A):
    # generator expression to extract odd numbers first
    odds = sorted(x for x in A if x & 1)[::-1] 

    # List Comp to achieve:
    # replace odd num. one by one, leave evens unchanged
    return [x if not x&1 else odds.pop() for x in A]

print(sort_odd(L))
Answered By: Daniel Hao