Критика этой поздней ночью, код Нуб Хаскелла - PullRequest
7 голосов
/ 25 апреля 2009

Я прорабатываю Реальный мир на Хаскеле , и в данный момент выполняю упражнения в конце главы 3.

Я придерживаюсь необычного подхода: , хотя я знаю, что некоторые языковые функции, которые они еще не охватили, могут помочь мне, я пытаюсь выполнять эти упражнения, используя только вещи они явно охватили. Почему? Просто для удовольствия. Такое чувство, что я заставляю свой мозг немного потренироваться с рекурсией.

Итак, я только что выполнил упражнение, изложенное следующим образом: "Создайте функцию, которая сортирует список списков по длине каждого подсписка. (Вы можете посмотреть на функцию sortBy из модуля Data.List .) "

Теперь они добавили подсказку о модуле Data.List. Но они не сказали ни слова о том, где можно найти справочный документ, о том, как импортировать вещи и т. Д. Поэтому я решил свернуть свой собственный вид , чтобы посмотреть, смогу ли я это сделать. Я использовал Bubble Sort, так как это самый простой алгоритм.

Результат ниже. Я бы хотел, чтобы вы, гуру Haskell, критиковали его, но ... 1019 * с учетом следующего предостережения: Если вы предлагаете улучшения, пожалуйста, опирайтесь на языковые особенности, описанные в главе 3 Real. World Haskell (или что вы думаете, что эти функции могут быть без необходимости искать его). Я знаю меня ждут тонны замечательных языковых функций, которые позволят мне улучшить этот код, но сейчас особая задача заключалась в том, чтобы сделать это с "примитивными" функциями, которые были рассмотрены до сих пор.

Я уверен, что есть случаи, когда я протягиваю руку вокруг своего плеча, чтобы почесать локоть, случаи, когда я использую явный поток управления, когда рекурсия и сопоставление с образцом может принести мне больше пользы, и т. Д. Я уверен, что код можно сделать намного короче и более читабельным. Могу поспорить, что есть хорошие идиомы, о которых я не знаю, которые можно использовать с примитивными языковыми функциями, которыми я ограничиваю себя. Вот такие советы я бы хотел получить.

Это, пожалуй, самый уродливый код, которым я горжусь на любом языке (по крайней мере, насколько я помню). Мой первый удар, на функциональном языке, за что-то помимо "Hello, world" типа материала. И теперь вы собираетесь выбить из этого дерьмо :). Будьте нежны, пожалуйста, но я с нетерпением жду некоторого мясного понимания. Спасибо.

areListsEqual :: (Eq a) => [a] -> [a] -> Bool

areListsEqual [] [] = True
areListsEqual [] _  = False
areListsEqual _ []  = False

areListsEqual xs ys = (head xs == head ys)  && (areListsEqual (tail xs) (tail ys))

charlieSort :: (Eq a) => [[a]] -> [[a]]

charlieSort [] = []
charlieSort (x:xs) | null xs = [x]
charlieSort xs | (length xs) >= 2 = if(not (areListsEqual xs wip))
                    then charlieSort wip 
                    else wip
                    where
                      first = head xs
                      second = head (tail xs)
                      theRest = drop 2 xs
                      swapPairIfNeeded a b = if(length a >= length b) 
                          then [second, first]
                          else [first, second]
                      modifiedPair = swapPairIfNeeded first second
                      wip = (take 1 modifiedPair) ++ charlieSort ( (drop 1 modifiedPair) ++ theRest)

Ответы [ 5 ]

6 голосов
/ 25 апреля 2009

Я бы прежде всего начал использовать сопоставление с образцом.

areListsEqual :: Eq a => [a] -> [a] -> Bool
areListsEqual [    ] [    ] = True
areListsEqual [    ] _      = False
areListsEqual _      [    ] = False
areListsEqual (x:xs) (y:ys) = x == y && areListsEqual xs ys

Обратите внимание, насколько это более читабельно, если исключить head и tail.

charlieSort :: Eq a => [[a]] -> [[a]]
charlieSort    [                    ] = []
charlieSort    [x                   ] = [x]
charlieSort xs@(first:second:theRest)
  | areListsEqual xs wip              = wip
  | otherwise                         = charlieSort wip
  where
  swapPairIfNeeded a b
    | length a >= length b = [second,first]
    | otherwise            = [first,second]
  modifiedPair = swapPairIfNeeded first second
  wip = take 1 modifiedPair ++ charlieSort (drop 1 modifiedPair ++ theRest)

Я изменил if - then - else на ограждение для немного улучшенной читаемости (YMMV). Вместо проверки того, что в списке есть как минимум два элемента с вызов length мы используем сопоставление с образцом, что также позволяет нам назвать first, second, theRest напрямую. Шаблон name @ pattern оба сопоставляет ввод с pattern , а называет весь ввод как name.

Теперь я хочу не использовать take и drop для извлечения двух элементов. modifiedPair, поэтому последние две строки заменяются на

  [shorter,longer] = swapPairIfNeeded first second
  wip = [shorter] ++ charlieSort ([longer] ++ theRest)

где вы могли бы написать последнюю строку как

  wip = shorter : charlieSort (longer : theRest)

если вы предпочитаете. Но зачем swapPairIfNeeded возвращать shorter и longer из списка first и second в списке ? Почему бы не использовать пара, как

  swapPairIfNeeded a b
    | length a >= length b = (second,first)
    | otherwise            = (first,second)
  (shorter,longer) = swapPairIfNeeded first second

? В большинстве случаев лучше использовать кортежи для фиксированного числа значения (возможно, различных типов) и использовать списки для переменного числа значения (обязательно одного типа). Но кажется странным, что swapPairIfNeeded сравнивает свои аргументы a и b, но затем возвращает first и second в любом случае. В этом случае вместо того, чтобы позволить ему вернуться a и b в паре, вместо этого я удалю swapPairIfNeeded:

  (shorter,longer)
    | length first >= length second = (second,first)
    | otherwise                     = (first,second)

«разворачивает» тело swapPairIfNeeded в определение (shorter,longer).

Так что теперь код charlieSort выглядит как

charlieSort :: Eq a => [[a]] -> [[a]]
charlieSort    [                    ] = []
charlieSort    [x                   ] = [x]
charlieSort xs@(first:second:theRest)
  | areListsEqual xs wip              = wip
  | otherwise                         = charlieSort wip
  where
  (shorter,longer)
    | length first >= length second = (second,first)
    | otherwise                     = (first,second)
  wip = shorter : charlieSort (longer : theRest)

Наконец, я должен отметить, что charlieSort на самом деле не реализует пузырьковая сортировка, потому что рекурсивный вызов charlieSort будет не только сделать один «всплывающий» проход по списку, но также полностью отсортировать список longer : theRest, так что все, что нужно сделать после этого рекурсивного вызова (перед возвратом на один уровень выше), возможно, прослужит shorter к его законное место.

4 голосов
/ 26 апреля 2009

Чарли: я ограничусь одной критикой: никогда не используйте head, tail или length, если вы можете использовать сопоставление с шаблоном :

areListsEqual [] [] = True
areListsEqual (x:xs) (y:ys) = x == y && areListsEqual xs ys
areListsEqual _ _   = False

Я не могу следовать вашему алгоритму сортировки (и было бы вежливо с вашей стороны переформатировать ваш вопрос, чтобы исключить горизонтальную полосу прокрутки), но я бы переписал первые три строки следующим образом:

charlieSort []  = []
charlieSort [x] = x
charlieSort (x1:x2:xs) = if ...

(PS Все варианты использования head и tail могут быть переписаны с использованием сопоставления с образцом, и новички должны это делать. Не все случаи использования length могут быть заменены сопоставлением с образцом, но общие коды нубов, такие как length xs == 0 или length xs >= 2 можно и нужно переписать.)

(PPS Даже опытные программисты на Haskell редко используют «head». Менее двух десятых одного процента исходных строк в компиляторе Glasgow Haskell упоминают «head», и, говоря о них, примерно половина упоминается в строковых литералах или комментариях. Это примерно одно использование 'head' на каждые 1500 строк кода.)

2 голосов
/ 25 апреля 2009

Вам не нужна функция areListsEqual. Вы можете сравнить списки с помощью функции (==). И я бы использовал быструю сортировку, а не пузырьковую сортировку. Вот решение, которое, я думаю, использует только то, что вы должны были узнать до сих пор.

charlieSort :: (Eq a) => [[a]] -> [[a]]
charlieSort []     = []
charlieSort (x:xs) = charlieSort (filter (cmpLen (>) x) xs) ++ [x] ++
                     charlieSort (filter (cmpLen (<=) x) xs)
   where filter _ [] = []
         filter p (x:xs) = (if (p x) then (x:) else id) (filter p xs)
         cmpLen f x y = f (length x) (length y)
1 голос
/ 25 апреля 2009

Вы утверждаете, что пузырьковая сортировка - это самый простой алгоритм сортировки, но здесь это не совсем так. Пузырьковая сортировка отлично подходит для массивов, где вы индексируете их линейно. Для связанных списков в Haskell сортировку вставкой на самом деле гораздо красивее.

Давайте начнем с функции insert:

winsert :: [a] -> [[a]] -> [[a]]
winsert x [] = [x]
winsert x (y:ys)
    | length x < length y = x : y : ys
    | otherwise = y : winsert x ys
  • Если список пуст, поместите в него x
  • Если список не пустой:
    • Если x < y, то x находится в начале списка
    • В противном случае заголовок списка - y, а хвост состоит из x, вставляемого куда-то в ys.

Далее у нас есть актуальная функция сортировки:

wsort :: [[a]] -> [[a]]
wsort [] = []
wsort [x] = [x]
wsort (x:xs) = winsert x (wsort xs)
  • Если список пуст, вернуть его
  • Если в списке есть только один элемент, его не нужно сортировать
  • Если список длиннее, сортируйте xs, затем вставьте x в теперь отсортированный xs

Интересно, что, изменив winsert на функцию в качестве аргумента (вместо length), можно использовать wsort для сортировки по всем видам критериев. Попробуйте составить список, который отсортирует список списков на основе суммы каждого подсписка.

1 голос
/ 25 апреля 2009

Я на восьмой главе, так что я не старожил, но я бы предпочел

areListsEqual x:xs y:ys = (x == y) && (areListsEqual xs ys)
areListsEqual [] [] = True
areListsEqual _ _ = False

Кажется, немного больше соответствует стилю Haskell.

Аналогично,

charlieSort [] = []
charlieSort (x:[]) = [x]
charlieSort (x1:x2:xs) = blah blah

swapPairIfNeed работает как есть, потому что вы вызываете его только с первым и вторым аргументами (в таком порядке), но вы, вероятно, имели в виду

swapPairIfNeed a b = if (length a >= length b)
    then [b, a]
    else [a, b]

На самом деле, я предпочитаю, чтобы третий случай с charlieSort выглядел как

charlieSort (x1:x2:xs) = if not (areListsEqual x1:x2:xs wip)
                         then charlieSort wip
                         else wip
    where swapPairIfNeeded a b = if (length a >= length b)
                                 then (b, a)
                                 else (a, b)
          wip = f (swapPairIfNeeded first second)
          f (a, b) = a : (charlieSort b:xs)

Я думаю, что все это было описано в главе 3.

Теперь давайте рассмотрим алгоритм. Даже удерживая себя в пузырьковой сортировке, нет необходимости проверять весь список после сортировки. Вместо этого мы можем поменять местами первые два элемента, а затем отсортировать хвост списка. Если голова короче головы отсортированного хвоста, все готово.

charlieSort (x1:x2:xs) = if (length a <= length (head sortedTail))
                         then a : sortedTail
                         else charlieSort (a : sortedTail)
    where sortedTail = charlieSort (b:xs)
          (a, b) = if (length x1 >= length x2)
                   then (x2, x1)
                   else (x1, x2)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...