Как передать неизвестный Proc рекурсивно в Ruby - PullRequest
1 голос
/ 07 ноября 2019

Проблема

Я пытаюсь написать метод (Array # bubble_sort), который принимает необязательный аргумент proc.
Если задана процедура, метод должен отсортировать массив в соответствии с процедурой.
Когда не указан proc, метод должен сортировать массив в порядке возрастания.
Рекурсивно, это прекрасно работает без добавления процедуры, однако, когда мне дают неизвестный процесс, я не могу найти способ передать его обратно в качестве аргумента.

Код

class Array
  def bubble_sort(&prc)

    prc = Proc.new{|a,b| a <=> b} if !prc

    self.each_with_index do |ele,idx|
      if prc.call(self[idx],self[idx+1]) == 1
        self[idx],self[idx+1] = self[idx+1], self[idx]
        self.bubble_sort(&prc) 
      end
    end

    self
    end
end

Тестовый код

[4, 12, 2, 8, 1, 14, 9, 25, 24, 81].bubble_sort возвращает ожидаемые результаты [1, 2, 4, 8, 9, 12, 14, 24, 25, 81]

[4, 12, 2, 8, 1, 14, 9, 25, 24, 81].bubble_sort { |a, b| a.to_s <=> b.to_s } возвращает слишком большую ошибку на уровне стека вместо ожидаемого результата [1, 12, 14, 2, 24, 25, 4, 8, 81, 9]

1 Ответ

2 голосов
/ 07 ноября 2019

Проблема не в прохождении процедуры, а в том, что <=> работает по-разному с числами и строками.

Когда ваш цикл достигает конца массива, self.idx равен self.length - 1, а self.idx + 1 равен self.length. Вызов self[self.length] для массива всегда будет нулевым из-за нумерации.

Итак, вы в конечном итоге звоните proc.call(<last element of array>, nil)

Поведение оператора космического корабля зависит от того, выполняете ли вы number <=> nil или number.to_s <=> nil.to_s (что является разницей между двумяВы сравниваете):

81 <=> nil
# => nil

81.to_s <=> nil.to_s
# => 1

В вашем случае вы не хотите, чтобы сравнение с nil приводило к свопу, поэтому у вас есть два варианта:

  1. Вы можете изменить процесс, чтобы он возвращал nil, если второй элемент равен nil:

    arr.bubble_sort do |a,b|
      b.nil? ? nil : a.to_s <=> b.to_s
    end
    
  2. Вы можете просто пропустить сравнение, если b выходит за пределы:

    self.each_with_index do |ele,idx|
      next if idx + 1 == self.length
      # ... other stuff
    
...