if/elsif/else return Ruby 和 Python 之间的行为差​​异

if/elsif/else return behavior difference between Ruby and Python

我正在尝试将快速选择从 Ruby 转换为 Python。 Ruby代码如下:

class SortableArray
    attr_reader :array
    def initialize(array)
        @array = array
    end
    def quickselect!(kth_lowest_value, left_index, right_index)
        # If we reach a base case - that is, that the subarray has one cell,
        # we know we've found the value we're looking for:
        if right_index - left_index <= 0
            return @array[left_index]
        end
        # Partition the array and grab the index of the pivot:
        pivot_index = partition!(left_index, right_index)
        # If what we're looking for is to the left of the pivot:
        if kth_lowest_value < pivot_index
            # Recursively perform quickselect on the subarray to
            # the left of the pivot:
            quickselect!(kth_lowest_value, left_index, pivot_index - 1)
            # If what we're looking for is to the right of the pivot:
        elsif kth_lowest_value > pivot_index
            # Recursively perform quickselect on the subarray to
            # the right of the pivot:
            quickselect!(kth_lowest_value, pivot_index + 1, right_index)
        else # if kth_lowest_value == pivot_index
            # if after the partition, the pivot position is in the same spot
            # as the kth lowest value, we've found the value we're looking for
            return @array[pivot_index]
        end
    end
    
    def partition!(left_pointer, right_pointer)
        # We always choose the right-most element as the pivot.
        # We keep the index of the pivot for later use:
        pivot_index = right_pointer
        # We grab the pivot value itself:
        pivot = @array[pivot_index]
        # We start the right pointer immediately to the left of the pivot
        right_pointer -= 1
        while true
            # Move the left pointer to the right as long as it
            # points to value that is less than the pivot:
            while @array[left_pointer] < pivot do
                left_pointer += 1
            end
            # Move the right pointer to the left as long as it
            # points to a value that is greater than the pivot:
            while @array[right_pointer] > pivot do
                right_pointer -= 1
            end
            # We've now reached the point where we've stopped
            # moving both the left and right pointers.
            # We check whether the left pointer has reached
            # or gone beyond the right pointer. If it has,
            # we break out of the loop so we can swap the pivot later
            # on in our code:
            if left_pointer >= right_pointer
                break
            # If the left pointer is still to the left of the right
            # pointer, we swap the values of the left and right pointers:
            else
                @array[left_pointer], @array[right_pointer] = @array[right_pointer], @array[left_pointer]
            # We move the left pointer over to the right, gearing up
            # for the next round of left and right pointer movements:
                left_pointer += 1
            end
        end
        # As the final step of the partition, we swap the value
        # of the left pointer with the pivot:
        @array[left_pointer], @array[pivot_index] = @array[pivot_index], @array[left_pointer]
        # We return the left_pointer for the sake of the quicksort method
        # which will appear later in this chapter:
        return left_pointer
    end
end



array = [0, 50, 20, 10, 60, 30]
sortable_array = SortableArray.new(array)
p sortable_array.quickselect!(1, 0, array.length - 1)

当我将其转换为 python 时,只有当我在 if 和 elif 条件(第 28 和 30 行)之后放置一个 return 时它才有效,如下所示:

def partition(left_p, right_p, arr=[]):
    pivot_index = right_p
    pivot = arr[pivot_index]
    right_p -= 1

    while True:
        while arr[left_p] < pivot:
            left_p += 1
        while arr[right_p] > pivot:
            right_p -= 1

        if left_p >= right_p:
            break
        else:
            arr[left_p], arr[right_p] = arr[right_p], arr[left_p]
            left_p += 1

    arr[left_p], arr[pivot_index] = arr[pivot_index], arr[left_p]
    return left_p

def quickselect(kth_lowest_value, left_index, right_index, arr=[]):
    print(arr)
    if right_index - left_index <= 0:
        return arr[left_index]
    pivot_index = partition(left_index, right_index, arr)

    if kth_lowest_value < pivot_index:
        return quickselect(kth_lowest_value, left_index, pivot_index-1, arr)
    elif kth_lowest_value > pivot_index:
        return quickselect(kth_lowest_value, pivot_index+1, right_index, arr)
    else:
        print(f"item = {arr[pivot_index]}")
        return arr[pivot_index]


array = [200, 97, 100, 101, 211, 107, 63, 123, 11, 34]
index = quickselect(6, 0, len(array)-1, array)
print(index)

为什么在没有 return 的 Ruby 中也能正常工作?是因为在class里面吗?

Ruby 代码不是很地道。 if-elsif-else 分支中的最后一个 return 是转移注意力,与条件链中的其他分支不一致。在 if-elsif-else 链的其他分支上也有一个隐含的 return

概括上述思想,在所有Ruby函数和方法中,如果控制到达函数末尾而没有遇到return,则return值为评估的最后一个表达式。在条件链中,这就是采用的分支。

一个最小的例子是:

def foo(n)
  if n == 0
    "zero"
  elsif n == 1
    "one"
  else
    "something else"
  end
end

puts foo(0) # => zero
puts foo(1) # => one
puts foo(2) # => something else

return 添加到任何或所有上述分支不会改变行为,通常会被省略。

另一方面,

Python 的隐式 return 始终是 None。返回非 None 值涉及使用显式 return(我猜是 "explicit is better than implicit")。

def foo(n):
    if n == 0:
        return "zero"
    elif n == 1:
        return "one"
    else:
        return "something else"

print(foo(0)) # => zero
print(foo(1)) # => one
print(foo(2)) # => something else

关于 Ruby 代码的另一个注意事项:通常 ! 在 function/method 名称之后用于 in-place 算法,即修改输入的算法,或者否则更危险的非 bang 方法版本。由于 quickselect! 不修改任何 class 状态,所以它有一个爆炸令人困惑。