Конвертировать байтовый массив в любую базу - PullRequest
12 голосов
/ 16 июля 2010

У меня есть массив байтов (любой длины), и я хочу закодировать этот массив в строку, используя мой собственный базовый кодировщик. В .NET есть стандартный Base64 кодировщик, но что если я захочу закодировать массив в Base62, Base53 или Base13?

Можно ли вообще создать такой универсальный базовый кодер?

Я знаю, что мог бы сделать это простым способом, то есть для каждого байта зарезервировать фиксированное количество символов (в случае Base62, это будет 5 символов) и выполнить прямое кодирование байтов-> символов, но я будет тратить пространство, так как 5 Base62 символы могут содержать более 1 байта, но менее 2 байтов.

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

Ресурсы

Поскольку решение уже известно (используйте BigInteger), я просто хотел бы разместить здесь некоторые ресурсы, относящиеся к классу BigInteger, так как он недоступен в .NET 3.5:

Большие целые числа в C #
http://intx.codeplex.com/
https://svn.apache.org/repos/asf/incubator/heraldry/libraries/csharp/openid/trunk/Mono/Mono.Math/BigInteger.cs
http://www.codeproject.com/KB/cs/BigInteger_Library.aspx
http://www.codeproject.com/KB/cs/biginteger.aspx

Ответы [ 8 ]

11 голосов
/ 16 июля 2010

Немного опоздал на вечеринку, но ...

Поскольку ваша спецификация требует произвольного числа битов, у вас должен быть целочисленный тип, который может работать с произвольным числом битов. Если вы не можете ориентироваться на .NET 4.0, вам придется просить, одалживать или красть реализацию BigInteger (например, .NET 4.0).

public static class GenericBaseConverter
{
    public static string ConvertToString(byte[] valueAsArray, string digits, int pad)
    {
        if (digits == null)
            throw new ArgumentNullException("digits");
        if (digits.Length < 2)
            throw new ArgumentOutOfRangeException("digits", "Expected string with at least two digits");

        BigInteger value = new BigInteger(valueAsArray);
        bool isNeg = value < 0;
        value = isNeg ? -value : value;

        StringBuilder sb = new StringBuilder(pad + (isNeg ? 1 : 0));

        do
        {
            BigInteger rem;
            value = BigInteger.DivRem(value, digits.Length, out rem);
            sb.Append(digits[(int)rem]);
        } while (value > 0);

        // pad it
        if (sb.Length < pad)
            sb.Append(digits[0], pad - sb.Length);

        // if the number is negative, add the sign.
        if (isNeg)
            sb.Append('-');

        // reverse it
        for (int i = 0, j = sb.Length - 1; i < j; i++, j--)
        {
            char t = sb[i];
            sb[i] = sb[j];
            sb[j] = t;
        }

        return sb.ToString();

    }

    public static BigInteger ConvertFromString(string s, string digits)
    {
        BigInteger result;

        switch (Parse(s, digits, out result))
        {
            case ParseCode.FormatError:
                throw new FormatException("Input string was not in the correct format.");
            case ParseCode.NullString:
                throw new ArgumentNullException("s");
            case ParseCode.NullDigits:
                throw new ArgumentNullException("digits");
            case ParseCode.InsufficientDigits:
                throw new ArgumentOutOfRangeException("digits", "Expected string with at least two digits");
            case ParseCode.Overflow:
                throw new OverflowException();
        }

        return result;
    }

    public static bool TryConvertFromString(string s, string digits, out BigInteger result)
    {
        return Parse(s, digits, out result) == ParseCode.Success;
    }

    private enum ParseCode
    {
        Success,
        NullString,
        NullDigits,
        InsufficientDigits,
        Overflow,
        FormatError,
    }

    private static ParseCode Parse(string s, string digits, out BigInteger result)
    {
        result = 0;

        if (s == null)
            return ParseCode.NullString;
        if (digits == null)
            return ParseCode.NullDigits;
        if (digits.Length < 2)
            return ParseCode.InsufficientDigits;

        // skip leading white space
        int i = 0;
        while (i < s.Length && Char.IsWhiteSpace(s[i]))
            ++i;
        if (i >= s.Length)
            return ParseCode.FormatError;

        // get the sign if it's there.
        BigInteger sign = 1;
        if (s[i] == '+')
            ++i;
        else if (s[i] == '-')
        {
            ++i;
            sign = -1;
        }

        // Make sure there's at least one digit
        if (i >= s.Length)
            return ParseCode.FormatError;


        // Parse the digits.
        while (i < s.Length)
        {
            int n = digits.IndexOf(s[i]);
            if (n < 0)
                return ParseCode.FormatError;
            BigInteger oldResult = result;
            result = unchecked((result * digits.Length) + n);
            if (result < oldResult)
                return ParseCode.Overflow;

            ++i;
        }

        // skip trailing white space
        while (i < s.Length && Char.IsWhiteSpace(s[i]))
            ++i;

        // and make sure there's nothing else.
        if (i < s.Length)
            return ParseCode.FormatError;

        if (sign < 0)
            result = -result;

        return ParseCode.Success;
    }
}
3 голосов
/ 23 ноября 2011

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

В настоящее время я работаю над собственным сокращением URL: konv.es. Чтобы создать максимально короткий символьный хэш URL-адреса, я использую метод строки GetHashCode (), а затем преобразовываю полученное число в основание 62 ([0-9a-zA-Z]). Самое элегантное решение, которое я нашел до сих пор, чтобы сделать преобразование (которое также является удобным примером доходности):

public static IEnumerable<char> ToBase62(int number)
    {
        do
        {
            yield return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"[number % 62];
            number /= 62;

        } while (number > 0);
    }

Дополнительный кредит: перефакторинг как метод продления

3 голосов
/ 16 июля 2010

Если производительность не является проблемой, используйте класс BigInteger в фоновом режиме. У вас есть конструктор для BigInteger, который принимает байтовый массив, и затем вы можете вручную запустить циклы деления и модуля, чтобы получить представление в других нестандартных базах.

Также взгляните на это .

1 голос
/ 16 июля 2010

BASE64 работает хорошо, потому что 64 - это степень 2 (2 ^ 6), поэтому каждый символ содержит 6 бит данных, а 3 байта (3 * 8 = 24 бита) могут быть закодированы в 4 символа (4 * 6 = 24). Кодирование и декодирование могут быть просто битовыми сдвигающими битами.

Для баз, которые не совпадают со степенью 2 (например, ваша база 62 или база 53), тогда вы должны обработать сообщение, которое вы пытаетесь закодировать, как одно длинное число и выполнить с ним операции деления и по модулю. Возможно, вам было бы лучше использовать кодировку Base32 и растрачивать немного пропускной способности.

1 голос
/ 16 июля 2010

Вы можете получить вдохновение от реализации C # Base32 реализации Майкла Джинокаво.

0 голосов
/ 01 января 2013

Сообщение в CodeReview побудило меня создать класс RadixEncoding, который может обрабатывать кодирование / декодирование массива байтов в / из строки base-N.

Класс может бытьобнаружил в этом потоке вопросов и ответов вместе с документацией (и решениями) по нескольким крайним случаям при работе с BigInteger, поддержкой порядка байтов и общей производительности класса

0 голосов
/ 02 мая 2011

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

0 голосов
/ 16 июля 2010

Другой пример, на который стоит обратить внимание: Ascii85 , используемый в документах Adobe PostScript и PDF. В Ascii85, 5 символов используются для кодирования 4 байтов. Вы можете вычислить эффективность этого кодирования как (256 ^ 4) / (85 ^ 5) = 96,8%. Это та часть битовых комбинаций, которая будет фактически использоваться.

Таким образом, для любой новой базы, которую вы хотите использовать для кодирования ваших данных, вы должны искать мощность, которая получит ее чуть выше 256, если вы пытаетесь максимизировать эффективность кодирования. Это может быть непросто для каждой базы. Проверка базы 53 показывает, что лучшее, что вы, вероятно, получите, это использование 7 байтов для кодирования 5 байтов (эффективность 93,6%), если только вы не хотите использовать 88 байтов для кодирования 63 байтов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...