Очевидно, это всего лишь пример, который я использую для понимания Haskell. Я не планирую использовать пузырьковую сортировку для сортировки, и при этом я не буду стремиться оптимизировать одну итерацию по списку в квадратичном алгоритме.

Я надеялся остановить пузырьковую сортировку, когда список уже отсортирован. Я написал этот код, где bubble возвращает в качестве второго элемента в кортеже логическое состояние, которое сообщает вызывающей стороне, были ли произведены какие-либо перестановки:

bubble (x:y:ys)
    | x > y = (y : fst (bubble (x:ys)), True)
    | otherwise = (x : fst (bubble (y:ys)), snd (bubble (y:ys)))
bubble x = (x, False)
bubblesort xs
    | snd (bubble xs) = bubblesort (fst (bubble xs))
    | otherwise = fst (bubble xs)

На языках, с которыми я знаком, это будет ужасно неэффективно, потому что bubble (y:ys) и bubble xs будут пересчитаны дважды без явного запоминания. Но учитывая, что функции Haskell не имеют побочных эффектов, могу ли я предположить, что компилятор оптимизирует дублирующиеся вызовы, делая мой код эффективным?

Если нет, то какой хороший способ написать эффективную сортировку пузырьков?

Я видел подход, который проверяет, отсортирован ли список :

bubbleSort (x:y:xs) = if sorted thisSort then thisSort else bubbleSort thisSort
    where thisSort = (min x y) : bubbleSort ((max x y):xs)
bubbleSort x = x
sorted (x:y:xs) = if x <= y then sorted (y:xs) else False
sorted x = x

Но я пытаюсь избежать одной дополнительной итерации по списку (как только bubbleSort не выполнил перестановок, еще один обход списка, чтобы убедиться, что список отсортирован, не нужен).

2
max 24 Мар 2017 в 05:13

2 ответа

Лучший ответ

Используйте where предложение:

bubble (x:y:ys)
  | x > y = (y : fst (bubble (x:ys)), True)
  | otherwise = (x : fst yys, snd yys)
  where
    yys = bubble (y:ys)
bubble x = (x, False)
bubblesort xs
  | snd bxs = bubblesort (fst bxs)
  | otherwise = fst bxs
  where
    bxs = bubble xs

GHC , вероятно, выяснит, как оптимизировать его для этого в любом случае, но это делает его гораздо более вероятным, поскольку он дает явную привязку для создания памятки, а не для поиска дублированного кода.

3
Lazersmoke 24 Мар 2017 в 02:36

На языках, с которыми я знаком, это будет ужасно неэффективно, потому что bubble (y:ys) и bubble xs будут пересчитаны дважды без явного запоминания. Но учитывая, что функции Haskell не имеют побочных эффектов, могу ли я предположить, что компилятор оптимизирует дублирующиеся вызовы, делая мой код эффективным?

Нет, ты не можешь предположить это. Эта оптимизация, называемая устранение общих подвыражений , может быть довольно сложным компромиссом, поскольку вы обмениваете время на пространство. Например, если дублированное выражение потребляет много памяти, может быть предпочтительнее вычислить его дважды, чтобы вы могли использовать его сразу, а не вычислять его один раз и оставлять в памяти. По этой причине GHC довольно консервативен, когда дело доходит до применения общего исключения подвыражений. См. этот вопрос о примере, довольно похожем на ваш, и этот для углубленного обсуждения.

(Конечно, в вашем случае нежелание вычислять bubble (y:ys) дважды вполне разумно. Вы можете обойти весь вопрос, добавив вспомогательное определение, например, Ответ Lazersmoke иллюстрирует.)

6
Community 23 Май 2017 в 12:25