2017-01-31 14 views
-1

Функция signum - это реализация обычного mathematical definition of sign, который условно возвращает значение в {-1,0,1}. Это теоретическое определение и, как таковое, оно не учитывает вычислительные затраты на операции или тип данных данных, поэтому умножение на (-1) представляет собой теоретический способ изменения стоимости с нулевой стоимостью. Из-за этого это не самая полезная обработка знака для программирования.Используемые случаи «signum» для упорядоченных числовых типов в Haskell

Корпус signum a == 0 не очень полезен, так как вы всегда можете напрямую протестировать a == 0, без дополнительной оплаты signum a. Что касается двух других ценностей, я думаю, что они используются всего три общих способов:

  • либо вы проверить, является ли значение положительным или отрицательным, чтобы запустить другой код условно, так как в:

    f x y | signum x == -1 = h x y 
         | otherwise  = g x y 
    
  • или умножить что-то на 1 или -1, прежде чем работать с ним, как:

    f x y = g x (y * b) where 
         b = signum x 
    
  • или вы объявления d 1 или -1 к чему-то, прежде чем работать с ним, как:

    f x y = g x (y + b) where 
         b = signum x 
    

Во всех случаях было бы лучше Bool значение знака. Таким образом, нам просто нужны функции для декомпозиции Num в абсолютное значение и логический знак и обратная функция, которая меняет знак значения в зависимости от логического условия (которое представляет знак). Эта функция эквивалентна умножению числа 1 или -1 на число, поэтому мы определяем его как оператор, аналогичный (*). :

sgn a  = a >= 0 
(*.) a True = a 
(*.) a _ = -a 
abs a  = a *. sgn a 
signum1 a = 1 *. sgn a 

Я добавил дихотомический вариант signum, который может возвращать только '{-1,1}'. Обратите внимание, что перед ним с signum 0 = 0 мы получим обычную функцию signum, но этот третий случай - это то, что, как мне кажется, обычно не полезно.

Мы можем закодировать так же на добавление оператора, потому что это очень частый случай, чтобы добавить 1 или -1 в зависимости от знака чего-то (вы можете видеть, что эти операторы просто лечить True как 1 и False в -1):

(+.) a b = a + 1 *. b 
(-.) a b = a - 1 *. b 

Мы можем даже приложить объявления в классе Signed, для удобства использования, включая правильные подписи и фиксацию.

Таким образом, приведенные выше общие примеры упростит не только код, но и время и пространство выполнения, потому что мы избегаем умножения (вместо этого, используя (*.)), мы избегаем дополнительного сравнения, если у нас есть Bool, мы можем получить знак из одного типа данных и использовать его для другого типа без преобразования типа, и мы используем короткий тип Bool вместо потенциально длинного типа класса Num. Но мы получаем большую гибкость, одновременно позволяя оптимизировать код и типы данных.

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


Edit: Я изменил первый абзац более "нейтральной" моды, следуя Reid Barton комментарий.


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

+3

Полагает, что ноль - это просто другой случай. Вы не можете сказать, что знак * * нуля равен «+». Это одновременно «+» и «-», начиная с «-0 == + 0». (Нет, я не dv). –

+4

Я думаю, что этот вопрос может быть поставлен более «нейтральным» способом. –

+3

В отчете Haskell 2010 требуется только 'abs x * signum x == x'; он ничего не говорит о размере codomain 'signum'. – chepner

ответ

4

Я использовал функцию как для навигации курсора к цели смещения в квадратной сетке серией перемещается в (ортогонально и диагонально) соседние ячейки.

move :: (Int, Int) -> [(Int, Int)] 
move (0, 0) = [] 
move (x, y) = (dx, dy) : move (x - dx, y - dy) 
    where dx = signum x 
     dy = signum y 
+0

Хороший пример. Но вы вычисляете 'signum' на каждой рекурсии, что является избыточным, так как знак тот же, когда он не равен нулю. Для больших входов вам лучше попытаться прекомпретировать знак выхода из рекурсии, а также 'abs x' и' abs y', и рассматривать случаи, когда одна из координат равна нулю. И тогда очевидное преимущество 'signum' для этого случая вызывает сомнения (я думаю, что нет). – enrique

+0

@enrique Если вы просматриваете код и видите, что это приводит к большому узкому месту для ввода, в котором вы его используете, вы можете избежать этой перерасчета [используя общую икону Haskell функции «go») (http://lpaste.net/351956). Я не уверен, почему здесь нужно вычислять абсолютные значения. –

+0

@ Давид, ваш код неполный, так как для отдельных случаев одна координата становится равной нулю, если другая не равна нулю. Но вы правы, нам не нужно 'abs', если мы используем' signum'. Этот пример хорош. – enrique

10

Вы считаете, что «положительные» и «отрицательные» являются единственными двумя возможными признаками. Но, например, Complex Double, то signum операция возвращает комплексное число с тем же «направлением», но величинами 1:

Data.Complex> signum (3 :+ 4) 
0.6 :+ 0.8 
+3

Правда, но это вызывает вопрос, почему это называется «signum». Вероятно, эту операцию следует называть «нормализовать» или что-то подобное, и она не должна отображаться от 0 до 0 (поскольку у нее нет модуля модуляции). – leftaroundabout

+0

@leftaroundabout Я бы предпочел более короткую «норму». В стороне, функция 'sgn' - это [вещь в математике] (https://en.wikipedia.org/wiki/Sign_function), и ее обычное обобщение в сложных числах присваивает' sgn (0) = 0'. – Alec

+0

@Alec 'norm' будет еще более вводить в заблуждение - [norm] (https://en.wikipedia.org/wiki/Norm_ (математика)) в основном является обобщением' abs' на метрические векторные пространства'. – leftaroundabout

4

Для решения вопроса о временной сложности:

Филиалы не являются свободными и, если вы должны (в понятии) умножаются значений в результате Signum одного и того же значения в нескольких разных местах, это было бы вероятно, будет более эффективным иметь let s = signum x in ... или иметь это связывание в where -clause. Вы больше не должны проходить через филиал каждый раз. Также имейте в виду, что in some cases, code can slow down due to branching behavior that goes against what the branch predictor expects.

Например, у вас есть такой код:

f x y z = (s * y)/(1 + (s * z)) 
    where 
    s = signum x 

Анализ эффективности зачастую не так ясна, как вы могли бы ожидать, и может быть сильно зависит от очень конкретных аспектов той или иной программы, как можно видеть в вопросе, который я связал выше, поэтому часто цитировался совет «профиля перед оптимизацией». В вопросе, который я связал, версия кода, который выполняет больше инструкций, фактически выполняет быстрее, чем версия, которая выполняет меньше инструкций (я могу проверить эти результаты на своей машине, даже если я включу дополнительные инструкции по сортировке в профилирование)!

+0

Что относительно типа 'x' отличается от типов' y' и 'z' в вашем примере ?. В моем предлагаемом подходе это не проблема, так как все они должны быть экземплярами 'Num' (и типа' x' экземпляра 'Ord'). Я имею в виду, что эффективность - это не единственная, а не основная проблема, это согласованность. – enrique

+0

@enrique Я не уверен, что понимаю, что вы имеете в виду. В конечном счете, вещи должны быть одного типа, если вы собираетесь использовать на них '*', '/' и '+', как в примере, подобном этому. Вам понадобятся функции преобразования в любом подходе, если они разные. Наличие или отсутствие 'signum' не меняет этого. И вы также можете быть последовательны, используя 'signum' всюду ... –

+2

Я бы сказал, что' signum' также концептуально проще, потому что для этого требуется только одна, довольно простая функция.Только одно имя функции для запоминания, ясно, какие результаты соответствуют положительным и отрицательным (было бы легче забыть, что означает «Истинный») и нуль, и вы можете использовать его непосредственно с другими математическими операциями, такими как «+», не требуя какого-либо рода 'if', guard или' case'. И, кстати, нуль не является ни отрицательным, ни позитивным, так что значение «Bool» должно быть таким? И как вы должны это помнить? –