Как реализовать десятичное преобразование в двоичное

Я реализовал функцию преобразования двоичного кода в десятичную в Haskell и в настоящее время работаю над функцией, которая будет преобразовывать десятичное значение в двоичное. (Я знаю, что эти функции где-то доступны, хотя они не являются частью Prelude.hs)

Я придумал следующий код для процедурного языка C-типа, но у меня возникли проблемы с его адаптацией к функциональной парадигме.

while (n > 0)
{
    if (n % 2 == 1)
        str = str + "1";
    else
        str = str + "0";
    n = n / 2;
}

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

--Decimal to binary
toBin:: Int -> [Int]
toBin 0 = [0]
toBin n | (n % 2 == 1) =
        |(n % 2 == 0) = 

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

Ниже я придумал примитивную рекурсию для преобразования любой базы (меньше 10 вместо 2) в десятичную.

toDecimal :: [Int] -> Int
toDecimal [] = 0
toDecimal (x:xs) = (x * 2 ^(length xs)) + bin xs

person Tru    schedule 06.02.2012    source источник


Ответы (5)


Оператора % нет; вместо этого вы, вероятно, ищете `mod`:

toBin 0 = [0]
toBin n | n `mod` 2 == 1 = ...
        | n `mod` 2 == 0 = ...

Охранники позволяют выбирать между несколькими ветвями функции. В этом случае каждое ... будет результатом toBin n, если соответствующее ему условие истинно. Чтобы объединить два списка вместе, вы можете использовать оператор ++, а `div` соответствует целочисленному делению:

toBin 0 = [0]
toBin n | n `mod` 2 == 1 = toBin (n `div` 2) ++ [1]
        | n `mod` 2 == 0 = toBin (n `div` 2) ++ [0]

Однако это имеет несколько проблем. Во-первых, он всегда начинает результат с 0, что избыточно; кроме того, использование ++ [1] медленное, так как нужно пройти весь список, чтобы добавить элемент в конец; было бы лучше добавлять каждый элемент по мере продвижения, а затем обратить результат в конце.

Чтобы исправить обе эти вещи, мы разделим toBin на основную функцию и вспомогательную функцию:

toBin 0 = [0]
toBin n = reverse (helper n)

helper 0 = []
helper n | n `mod` 2 == 1 = 1 : helper (n `div` 2)
         | n `mod` 2 == 0 = 0 : helper (n `div` 2)

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

Мы можем упростить код helper, полностью пропустив защиту, поскольку мы просто снова записываем результат n `mod` 2 в правой части:

helper 0 = []
helper n = (n `mod` 2) : helper (n `div` 2)

Наконец, есть функция, которая выполняет div и mod за один раз, что может быть более эффективным:

helper 0 = []
helper n = let (q,r) = n `divMod` 2 in r : helper q

В качестве дополнительного примечания, на самом деле это не преобразует десятичное число в двоичное, оно преобразует целое число в двоичное; Реализации Haskell вряд ли будут хранить целые числа в десятичном формате, хотя они записываются и печатаются в этом формате. Чтобы написать полное преобразование десятичного числа в двоичное, потребуется функция, которая преобразует десятичную строку в целое число.

person ehird    schedule 06.02.2012
comment
потребуется функция, которая преобразует десятичную строку в целое число - например, эмм, read? - person Daniel Fischer; 07.02.2012
comment
@DanielFischer: Да, но, предположительно, ОП пытается реализовать это с нуля :) В конце концов, showIntAtBase тоже существует. - person ehird; 07.02.2012

toBin :: Int -> [Int]
toBin 0 = [0]
toBin 1 = [1]
toBin n
    | n `mod` 2 == 0 = toBin (n `div` 2) ++ [0]
    | otherwise = toBin (n `div` 2) ++ [1]

0 и 1 - тривиальные случаи. если n четное, вы должны добавить ноль в конец, иначе вы добавите единицу. Это хорошая практика, чтобы поймать все в вашем последнем выражении защиты (поэтому я использовал иначе, что то же самое, что и True)

person Mariy    schedule 06.02.2012

Оооо, я думал об этом в последнее время, и я нашел отличное решение, код на Haskell и прост, но очень эффективен, но работает только с положительными числами, так как я еще недостаточно продвинут, чтобы вычислить, скажем, дополнение 2 или другое вещи klike, которые связаны с двоичным кодом.

binarz :: Int -> [Int]
binarz 0 = []
binarz n = binarz (div n 2) ++ [(mod n 2)] 
person Tomelo    schedule 28.10.2019

Вы можете использовать функцию unfoldr из модуля Data.List и intToDigit из модуля Data.Char:

dec2bin :: Int -> [Char]
dec2bin = reverse . map intToDigit . unfoldr (\x -> if x==0 then Nothing else Just(rem x 2, div x 2))

Здесь происходит следующее: функция unfoldr обрабатывает входные данные с помощью предоставленной анонимной функции, пока не вернет Nothing, при этом объединяя первое значение из Just в списке. Этот список содержит Int, поэтому их нужно преобразовать в Char с помощью intToDigit, а затем перевернуть, так как они собираются в обратном порядке. Список Chars — это строка в Haskell, так что все готово.

person Wojciech Gac    schedule 03.02.2014

Вы можете использовать битовые сдвиги из Data.Bits

fromBits :: Bits a => a -> [Bool]
fromBits b
    | b == zeroBits = []
    | otherwise     = testBit b 0 : fromBits (shiftR b 1)

fromBits_ :: Bits a => a -> [Bool]
fromBits_ = reverse . fromBits
person Poscat    schedule 18.02.2020