Najszybszy sposób na konwersję liczby podstawowej 10 na dowolną zasadę w .NET?

108

Mam i starą (ish) metodę C #, którą napisałem, która pobiera liczbę i konwertuje ją na dowolną bazę:

string ConvertToBase(int number, char[] baseChars);

Nie wszystko jest takie super szybkie i schludne. Czy istnieje dobry, znany sposób osiągnięcia tego celu w .NET?

Szukam czegoś, co pozwoli mi użyć dowolnej bazy z dowolnym ciągiem znaków do użycia.

Pozwala to tylko na podstawy 16, 10, 8 i 2:

Convert.ToString(1, x);

Chcę użyć tego, aby uzyskać bardzo wysoką podstawę, korzystając z liczb, wszystkich małych i wielkich liter. Jak w tym wątku , ale dla C # nie JavaScript.

Czy ktoś zna dobry i skuteczny sposób robienia tego w C #?

joshcomley
źródło

Odpowiedzi:

136

Convert.ToString może służyć do konwersji liczby na jej równoważną reprezentację w postaci ciągu w określonej bazie.

Przykład:

string binary = Convert.ToString(5, 2); // convert 5 to its binary representation
Console.WriteLine(binary);              // prints 101

Jednak, jak wskazano w komentarzach, Convert.ToStringobsługuje tylko następujący ograniczony - ale zazwyczaj wystarczający - zestaw podstaw: 2, 8, 10 lub 16.

Aktualizacja (aby spełnić wymóg konwersji na dowolną bazę):

Nie znam żadnej metody w BCL, która jest w stanie przekonwertować liczby na dowolną bazę, więc musiałbyś napisać własną małą funkcję użytkową. Prosty przykład wyglądałby tak (zauważ, że z pewnością można to zrobić szybciej, zastępując konkatenację ciągów):

class Program
{
    static void Main(string[] args)
    {
        // convert to binary
        string binary = IntToString(42, new char[] { '0', '1' });

        // convert to hexadecimal
        string hex = IntToString(42, 
            new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                         'A', 'B', 'C', 'D', 'E', 'F'});

        // convert to hexavigesimal (base 26, A-Z)
        string hexavigesimal = IntToString(42, 
            Enumerable.Range('A', 26).Select(x => (char)x).ToArray());

        // convert to sexagesimal
        string xx = IntToString(42, 
            new char[] { '0','1','2','3','4','5','6','7','8','9',
            'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
            'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x'});
    }

    public static string IntToString(int value, char[] baseChars)
    {
        string result = string.Empty;
        int targetBase = baseChars.Length;

        do
        {
            result = baseChars[value % targetBase] + result;
            value = value / targetBase;
        } 
        while (value > 0);

        return result;
    }

    /// <summary>
    /// An optimized method using an array as buffer instead of 
    /// string concatenation. This is faster for return values having 
    /// a length > 1.
    /// </summary>
    public static string IntToStringFast(int value, char[] baseChars)
    {
        // 32 is the worst cast buffer size for base 2 and int.MaxValue
        int i = 32;
        char[] buffer = new char[i];
        int targetBase= baseChars.Length;

        do
        {
            buffer[--i] = baseChars[value % targetBase];
            value = value / targetBase;
        }
        while (value > 0);

        char[] result = new char[32 - i];
        Array.Copy(buffer, i, result, 0, 32 - i);

        return new string(result);
    }
}

Aktualizacja 2 (poprawa wydajności)

Użycie buforu tablicy zamiast konkatenacji ciągów do skompilowania ciągu wynikowego zapewnia poprawę wydajności, szczególnie w przypadku dużej liczby (patrz metoda IntToStringFast). W najlepszym przypadku (tj. Najdłuższym możliwym wejściu) ta metoda jest mniej więcej trzy razy szybsza. Jednak dla liczb 1-cyfrowych (czyli 1-cyfrowych w bazie docelowej), IntToStringbędzie szybszy.

Dirk Vollmar
źródło
5
Należy zauważyć, że obsługuje to tylko podstawy 2,8,10,16 - a nie „jakiekolwiek” w pytaniu. bo nigdy nie wiadomo, kiedy będziesz potrzebować sześćdziesiątki ;-p
Marc Gravell
46
Szesnastkowy brzmi jak zabawa.
wodnik
Z targetBase równym 60 i wartością 12345, ten wiersz w metodzie IntToString: value = value / targetBase; dałoby wartość = 203,75. Czy to jest poprawne? Czy nie powinien zachować tego jako liczby całkowitej?
Adam Harte
6
Niesamowite. Ale gdzie jest funkcja odwrotna? : /
ashes999
2
Mam tutaj funkcję odwrotną pierwszego przejścia: stackoverflow.com/questions/3579970/ ...
ashes999
78

Niedawno pisałem o tym na blogu . Moja implementacja nie wykorzystuje podczas obliczeń żadnych operacji na łańcuchach, dzięki czemu jest bardzo szybka . Obsługiwana jest konwersja do dowolnego systemu liczbowego o podstawie od 2 do 36:

/// <summary>
/// Converts the given decimal number to the numeral system with the
/// specified radix (in the range [2, 36]).
/// </summary>
/// <param name="decimalNumber">The number to convert.</param>
/// <param name="radix">The radix of the destination numeral system (in the range [2, 36]).</param>
/// <returns></returns>
public static string DecimalToArbitrarySystem(long decimalNumber, int radix)
{
    const int BitsInLong = 64;
    const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    if (radix < 2 || radix > Digits.Length)
        throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString());

    if (decimalNumber == 0)
        return "0";

    int index = BitsInLong - 1;
    long currentNumber = Math.Abs(decimalNumber);
    char[] charArray = new char[BitsInLong];

    while (currentNumber != 0)
    {
        int remainder = (int)(currentNumber % radix);
        charArray[index--] = Digits[remainder];
        currentNumber = currentNumber / radix;
    }

    string result = new String(charArray, index + 1, BitsInLong - index - 1);
    if (decimalNumber < 0)
    {
        result = "-" + result;
    }

    return result;
}

Zaimplementowałem również szybką funkcję odwrotną na wypadek, gdyby ktoś też jej potrzebował: Arbitrary to Decimal Numeral System .

Pavel Vladov
źródło
5
Przetestowałem perfekcyjnie wszystkie rozwiązania na tej stronie i to jest najszybsze, około dwa razy szybsze niż krótkie rozwiązanie na końcu.
Justin R.
Co to jest result = "-" + result? Czy to jakaś wyściółka? Jak mogę zmodyfikować kod, aby używać tylko AZ lub 0-9 jako znaku wypełniającego?
oscilatingcretin
"-"W result = "-" + resultpodpórek znakiem ujemnym liczb ujemnych. To nie jest znak wypełniający.
Pavel Vladov
2
Dlaczego nie jest to akceptowana odpowiedź? To znakomicie!
Avrohom Yisroel
Dziękuję, zaoszczędziło mi to dużo czasu.
NinjaLlama
15

SZYBKIE METODY „ OD ” I „ DO

Spóźniłem się na imprezę, ale zsumowałem poprzednie odpowiedzi i poprawiłem je. Myślę, że te dwie metody są szybsze niż jakiekolwiek inne opublikowane do tej pory. Byłem w stanie przekonwertować 1 000 000 liczb zi do podstawy 36 w czasie krótszym niż 400 ms na jednym rdzeniu.

Poniższy przykład dotyczy podstawy 62 . Zmień BaseCharstablicę, aby przekonwertować z i na dowolną inną podstawę.

private static readonly char[] BaseChars = 
         "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray();
private static readonly Dictionary<char, int> CharValues = BaseChars
           .Select((c,i)=>new {Char=c, Index=i})
           .ToDictionary(c=>c.Char,c=>c.Index);

public static string LongToBase(long value)
{
   long targetBase = BaseChars.Length;
   // Determine exact number of characters to use.
   char[] buffer = new char[Math.Max( 
              (int) Math.Ceiling(Math.Log(value + 1, targetBase)), 1)];

   var i = buffer.Length;
   do
   {
       buffer[--i] = BaseChars[value % targetBase];
       value = value / targetBase;
   }
   while (value > 0);

   return new string(buffer, i, buffer.Length - i);
}

public static long BaseToLong(string number) 
{ 
    char[] chrs = number.ToCharArray(); 
    int m = chrs.Length - 1; 
    int n = BaseChars.Length, x;
    long result = 0; 
    for (int i = 0; i < chrs.Length; i++)
    {
        x = CharValues[ chrs[i] ];
        result += x * (long)Math.Pow(n, m--);
    }
    return result;  
} 

EDYCJA (12.07.2018)

Naprawiono rozwiązanie problemu narożnika znalezionego przez @AdrianBotor (patrz komentarze), konwertując 46655 na podstawę 36. Jest to spowodowane małym błędem zmiennoprzecinkowym, Math.Log(46656, 36)który wynosi dokładnie 3, ale .NET zwraca 3 + 4.44e-16, co powoduje dodatkowy znak w buforze wyjściowym .

Diego
źródło
@AdrianBotor Nie mogę powtórzyć twojego problemu:BaseToLong(LongToBase(46655)) == 46655
Diego
2
@Diego, Przepraszam za spóźnioną odpowiedź. Zainicjalizujmy tablicę za pomocą 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZi przekonwertujmy wartość 46655. Wynik powinien być, ZZZale w debugerze otrzymuję \0ZZZ. Tylko ta wartość zyskuje dodatkową \0. Na przykład wartość jest 46654prawidłowo konwertowana na ZZY.
Adrian Botor
@AdrianBotor Dobry chwyt. Zaadresowano, dostosowując instrukcję zwrotu LongToBasedoreturn new string(buffer, (int) i, buffer.Length - (int)i);
Diego,
7

Można też skorzystać z nieco zmodyfikowanej wersji akceptowanej i dostosować ciąg znaków bazowych do swoich potrzeb:

public static string Int32ToString(int value, int toBase)
{
    string result = string.Empty;
    do
    {
        result = "0123456789ABCDEF"[value % toBase] + result;
        value /= toBase;
    }
    while (value > 0);

    return result;
}
Kresimir
źródło
4

Bardzo późno na imprezę w tej sprawie, ale ostatnio napisałem następującą klasę pomocnika do projektu w pracy. Został zaprojektowany do konwersji krótkich ciągów na liczby iz powrotem (uproszczona, idealna funkcja skrótu ), jednak będzie również wykonywać konwersję liczb między dowolnymi zasadami. Base10ToStringRealizacja metoda odpowiada na pytanie, który został pierwotnie opublikowany.

shouldSupportRoundTrippingPotrzebna flaga przekazany do konstruktora klasy jest zapobieganie utracie wiodących cyfr z ciągiem liczb podczas konwersji do bazy-10 i z powrotem (ważne, biorąc pod uwagę moje wymagania!). W większości przypadków utrata wiodących zer z ciągu liczbowego prawdopodobnie nie będzie problemem.

Tak czy inaczej, oto kod:

using System;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflow
{
    /// <summary>
    /// Contains methods used to convert numbers between base-10 and another numbering system.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This conversion class makes use of a set of characters that represent the digits used by the target
    /// numbering system. For example, binary would use the digits 0 and 1, whereas hex would use the digits
    /// 0 through 9 plus A through F. The digits do not have to be numerals.
    /// </para>
    /// <para>
    /// The first digit in the sequence has special significance. If the number passed to the
    /// <see cref="StringToBase10"/> method has leading digits that match the first digit, then those leading
    /// digits will effectively be 'lost' during conversion. Much of the time this won't matter. For example,
    /// "0F" hex will be converted to 15 decimal, but when converted back to hex it will become simply "F",
    /// losing the leading "0". However, if the set of digits was A through Z, and the number "ABC" was
    /// converted to base-10 and back again, then the leading "A" would be lost. The <see cref="System.Boolean"/>
    /// flag passed to the constructor allows 'round-tripping' behaviour to be supported, which will prevent
    /// leading digits from being lost during conversion.
    /// </para>
    /// <para>
    /// Note that numeric overflow is probable when using longer strings and larger digit sets.
    /// </para>
    /// </remarks>
    public class Base10Converter
    {
        const char NullDigit = '\0';

        public Base10Converter(string digits, bool shouldSupportRoundTripping = false)
            : this(digits.ToCharArray(), shouldSupportRoundTripping)
        {
        }

        public Base10Converter(IEnumerable<char> digits, bool shouldSupportRoundTripping = false)
        {
            if (digits == null)
            {
                throw new ArgumentNullException("digits");
            }

            if (digits.Count() == 0)
            {
                throw new ArgumentException(
                    message: "The sequence is empty.",
                    paramName: "digits"
                    );
            }

            if (!digits.Distinct().SequenceEqual(digits))
            {
                throw new ArgumentException(
                    message: "There are duplicate characters in the sequence.",
                    paramName: "digits"
                    );
            }

            if (shouldSupportRoundTripping)
            {
                digits = (new[] { NullDigit }).Concat(digits);
            }

            _digitToIndexMap =
                digits
                .Select((digit, index) => new { digit, index })
                .ToDictionary(keySelector: x => x.digit, elementSelector: x => x.index);

            _radix = _digitToIndexMap.Count;

            _indexToDigitMap =
                _digitToIndexMap
                .ToDictionary(keySelector: x => x.Value, elementSelector: x => x.Key);
        }

        readonly Dictionary<char, int> _digitToIndexMap;
        readonly Dictionary<int, char> _indexToDigitMap;
        readonly int _radix;

        public long StringToBase10(string number)
        {
            Func<char, int, long> selector =
                (c, i) =>
                {
                    int power = number.Length - i - 1;

                    int digitIndex;
                    if (!_digitToIndexMap.TryGetValue(c, out digitIndex))
                    {
                        throw new ArgumentException(
                            message: String.Format("Number contains an invalid digit '{0}' at position {1}.", c, i),
                            paramName: "number"
                            );
                    }

                    return Convert.ToInt64(digitIndex * Math.Pow(_radix, power));
                };

            return number.Select(selector).Sum();
        }

        public string Base10ToString(long number)
        {
            if (number < 0)
            {
                throw new ArgumentOutOfRangeException(
                    message: "Value cannot be negative.",
                    paramName: "number"
                    );
            }

            string text = string.Empty;

            long remainder;
            do
            {
                number = Math.DivRem(number, _radix, out remainder);

                char digit;
                if (!_indexToDigitMap.TryGetValue((int) remainder, out digit) || digit == NullDigit)
                {
                    throw new ArgumentException(
                        message: "Value cannot be converted given the set of digits used by this converter.",
                        paramName: "number"
                        );
                }

                text = digit + text;
            }
            while (number > 0);

            return text;
        }
    }
}

Można to również podzielić na podklasy, aby uzyskać niestandardowe konwertery liczb:

namespace StackOverflow
{
    public sealed class BinaryNumberConverter : Base10Converter
    {
        public BinaryNumberConverter()
            : base(digits: "01", shouldSupportRoundTripping: false)
        {
        }
    }

    public sealed class HexNumberConverter : Base10Converter
    {
        public HexNumberConverter()
            : base(digits: "0123456789ABCDEF", shouldSupportRoundTripping: false)
        {
        }
    }
}

Kod zostałby użyty w ten sposób:

using System.Diagnostics;

namespace StackOverflow
{
    class Program
    {
        static void Main(string[] args)
        {
            {
                var converter = new Base10Converter(
                    digits: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz",
                    shouldSupportRoundTripping: true
                    );

                long number = converter.StringToBase10("Atoz");
                string text = converter.Base10ToString(number);
                Debug.Assert(text == "Atoz");
            }

            {
                var converter = new HexNumberConverter();

                string text = converter.Base10ToString(255);
                long number = converter.StringToBase10(text);
                Debug.Assert(number == 255);
            }
        }
    }
}
Steve Rands
źródło
2

Czy te zajęcia z tego posta na forum mogą Ci pomóc?

public class BaseConverter { 

public static string ToBase(string number, int start_base, int target_base) { 

  int base10 = this.ToBase10(number, start_base); 
  string rtn = this.FromBase10(base10, target_base); 
  return rtn; 

} 

public static int ToBase10(string number, int start_base) { 

  if (start_base < 2 || start_base > 36) return 0; 
  if (start_base == 10) return Convert.ToInt32(number); 

  char[] chrs = number.ToCharArray(); 
  int m = chrs.Length - 1; 
  int n = start_base; 
  int x; 
  int rtn = 0; 

  foreach(char c in chrs) { 

    if (char.IsNumber(c)) 
      x = int.Parse(c.ToString()); 
    else 
      x = Convert.ToInt32(c) - 55; 

    rtn += x * (Convert.ToInt32(Math.Pow(n, m))); 

    m--; 

  } 

  return rtn; 

} 

public static string FromBase10(int number, int target_base) { 

  if (target_base < 2 || target_base > 36) return ""; 
  if (target_base == 10) return number.ToString(); 

  int n = target_base; 
  int q = number; 
  int r; 
  string rtn = ""; 

  while (q >= n) { 

    r = q % n; 
    q = q / n; 

    if (r < 10) 
      rtn = r.ToString() + rtn; 
    else 
      rtn = Convert.ToChar(r + 55).ToString() + rtn; 

  } 

  if (q < 10) 
    rtn = q.ToString() + rtn; 
  else 
    rtn = Convert.ToChar(q + 55).ToString() + rtn; 

  return rtn; 

} 

}

Całkowicie nieprzetestowane ... daj mi znać, czy to działa! (Skopiuj i wklej go na wypadek, gdyby post na forum zniknął lub coś takiego ...)

Svish
źródło
Zamknij… Zagram później. Potrzeba trochę pracy, aby móc przyjąć dowolne znaki, ale jest to krok we właściwym kierunku. Porównam prędkość z moją własną metodą!
joshcomley
Pamiętaj, aby go udostępnić, jeśli tutaj poprawisz. Ktoś inny też mógłby chcieć ot =)
Svish
@joshcomley Jak minął weekend? ;)
Mikkel R. Lund
3
To był długi weekend: D
joshcomley
1

Ja też szukałem szybkiego sposobu na zamianę liczby dziesiętnej na inną podstawę z zakresu [2..36], więc opracowałem następujący kod. Jest prosty do naśladowania i używa obiektu Stringbuilder jako proxy dla bufora znaków, który możemy indeksować znak po znaku. Kod wydaje się być bardzo szybki w porównaniu z alternatywami i dużo szybszy niż inicjalizacja pojedynczych znaków w tablicy znaków.

Na własny użytek możesz preferować: 1 / Zwróć pusty ciąg zamiast zgłaszać wyjątek. 2 / usuń sprawdzanie podstawy, aby metoda działała jeszcze szybciej 3 / Zainicjuj obiekt Stringbuilder z 32 '0 i usuń wynik linii.Remove (0, i) ;. Spowoduje to zwrócenie ciągu z zerami wiodącymi i dalsze zwiększenie szybkości. 4 / Uczyń obiekt Stringbuilder statycznym polem w klasie, więc bez względu na to, ile razy metoda DecimalToBase jest wywoływana, obiekt Stringbuilder jest inicjowany tylko raz. Jeśli to zrobisz, zmiana 3 powyżej przestanie działać.

Mam nadzieję, że komuś to się przyda :)

AtomicParadox

        static string DecimalToBase(int number, int radix)
    {
        // Check that the radix is between 2 and 36 inclusive
        if ( radix < 2 || radix > 36 )
            throw new ArgumentException("ConvertToBase(int number, int radix) - Radix must be between 2 and 36.");

        // Create a buffer large enough to hold the largest int value represented in binary digits 
        StringBuilder result = new StringBuilder("                                ");  // 32 spaces

        // The base conversion calculates the digits in reverse order so use
        // an index to point to the last unused space in our buffer
        int i = 32; 

        // Convert the number to the new base
        do
        {
            int remainder = number % radix;
            number = number / radix;
            if(remainder <= 9)
                result[--i] = (char)(remainder + '0');  // Converts [0..9] to ASCII ['0'..'9']
            else
                result[--i] = (char)(remainder + '7');  // Converts [10..36] to ASCII ['A'..'Z']
        } while ( number > 0 );

        // Remove the unwanted padding from the front of our buffer and return the result
        // Note i points to the last unused character in our buffer
        result.Remove( 0, i );
        return (result.ToString());
    }
user1031307
źródło
0

Używałem tego do przechowywania Guid jako krótszego ciągu (ale był ograniczony do użycia 106 znaków). Jeśli ktoś jest zainteresowany, to mój kod do dekodowania ciągu z powrotem do wartości liczbowej (w tym przypadku użyłem 2 ulongów dla wartości Guid, zamiast kodowania Int128 (ponieważ jestem w 3.5, a nie 4.0). Dla jasności KOD to string const z 106 unikalnymi znakami ConvertLongsToBytes jest dość nieciekawy.

private static Guid B106ToGuid(string pStr)
    {
        try
        {
            ulong tMutl = 1, tL1 = 0, tL2 = 0, targetBase = (ulong)CODE.Length;
            for (int i = 0; i < pStr.Length / 2; i++)
            {
                tL1 += (ulong)CODE.IndexOf(pStr[i]) * tMutl;
                tL2 += (ulong)CODE.IndexOf(pStr[pStr.Length / 2 + i]) * tMutl;
                tMutl *= targetBase;
            }
            return new Guid(ConvertLongsToBytes(tL1, tL2));
        }
        catch (Exception ex)
        {
            throw new Exception("B106ToGuid failed to convert string to Guid", ex);
        }
    }
Gary
źródło
0

Miałem podobną potrzebę, z wyjątkiem tego, że potrzebowałem też matematyki na „liczbach”. Skorzystałem z niektórych sugestii tutaj i stworzyłem klasę, która będzie robić te wszystkie fajne rzeczy. Pozwala na użycie dowolnego znaku Unicode do reprezentowania liczby i działa również z liczbami dziesiętnymi.

Ta klasa jest dość łatwa w użyciu. Po prostu utwórz liczbę jako typ New BaseNumber, ustaw kilka właściwości i wyłącz. Procedury zajmują się automatycznym przełączaniem między podstawą 10 a podstawą x, a ustawiona wartość jest zachowywana w bazie, w której ją ustawiłeś, więc nie ma utraty dokładności (to znaczy do konwersji, ale nawet wtedy utrata precyzji powinna być bardzo minimalna, ponieważ rutynowe zastosowania DoubleiLong tam, gdzie to możliwe).

Nie mogę kontrolować szybkości tej procedury. Prawdopodobnie jest dość powolny, więc nie jestem pewien, czy będzie odpowiadał potrzebom osoby, która zadał pytanie, ale na pewno jest elastyczny, więc mam nadzieję, że ktoś inny może go użyć.

Dla każdego, kto może potrzebować tego kodu do obliczenia następnej kolumny w programie Excel, dołączę kod pętli, którego użyłem, który wykorzystuje tę klasę.

Public Class BaseNumber

    Private _CharacterArray As List(Of Char)

    Private _BaseXNumber As String
    Private _Base10Number As Double?

    Private NumberBaseLow As Integer
    Private NumberBaseHigh As Integer

    Private DecimalSeparator As Char = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
    Private GroupSeparator As Char = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator

    Public Sub UseCapsLetters()
        'http://unicodelookup.com
        TrySetBaseSet(65, 90)
    End Sub

    Public Function GetCharacterArray() As List(Of Char)
        Return _CharacterArray
    End Function

    Public Sub SetCharacterArray(CharacterArray As String)
        _CharacterArray = New List(Of Char)
        _CharacterArray.AddRange(CharacterArray.ToList)

        TrySetBaseSet(_CharacterArray)
    End Sub

    Public Sub SetCharacterArray(CharacterArray As List(Of Char))
        _CharacterArray = CharacterArray
        TrySetBaseSet(_CharacterArray)
    End Sub

    Public Sub SetNumber(Value As String)
        _BaseXNumber = Value
        _Base10Number = Nothing
    End Sub

    Public Sub SetNumber(Value As Double)
        _Base10Number = Value
        _BaseXNumber = Nothing
    End Sub

    Public Function GetBaseXNumber() As String
        If _BaseXNumber IsNot Nothing Then
            Return _BaseXNumber
        Else
            Return ToBaseString()
        End If
    End Function

    Public Function GetBase10Number() As Double
        If _Base10Number IsNot Nothing Then
            Return _Base10Number
        Else
            Return ToBase10()
        End If
    End Function

    Private Sub TrySetBaseSet(Values As List(Of Char))
        For Each value As Char In _BaseXNumber
            If Not Values.Contains(value) Then
                Throw New ArgumentOutOfRangeException("The string has a value, " & value & ", not contained in the selected 'base' set.")
                _CharacterArray.Clear()
                DetermineNumberBase()
            End If
        Next

        _CharacterArray = Values

    End Sub

    Private Sub TrySetBaseSet(LowValue As Integer, HighValue As Integer)

        Dim HighLow As KeyValuePair(Of Integer, Integer) = GetHighLow()

        If HighLow.Key < LowValue OrElse HighLow.Value > HighValue Then
            Throw New ArgumentOutOfRangeException("The string has a value not contained in the selected 'base' set.")
            _CharacterArray.Clear()
            DetermineNumberBase()
        End If

        NumberBaseLow = LowValue
        NumberBaseHigh = HighValue

    End Sub

    Private Function GetHighLow(Optional Values As List(Of Char) = Nothing) As KeyValuePair(Of Integer, Integer)
        If Values Is Nothing Then
            Values = _BaseXNumber.ToList
        End If

        Dim lowestValue As Integer = Convert.ToInt32(Values(0))
        Dim highestValue As Integer = Convert.ToInt32(Values(0))

        Dim currentValue As Integer

        For Each value As Char In Values

            If value <> DecimalSeparator AndAlso value <> GroupSeparator Then
                currentValue = Convert.ToInt32(value)
                If currentValue > highestValue Then
                    highestValue = currentValue
                End If
                If currentValue < lowestValue Then
                    currentValue = lowestValue
                End If
            End If
        Next

        Return New KeyValuePair(Of Integer, Integer)(lowestValue, highestValue)

    End Function

    Public Sub New(BaseXNumber As String)
        _BaseXNumber = BaseXNumber
        DetermineNumberBase()
    End Sub

    Public Sub New(BaseXNumber As String, NumberBase As Integer)
        Me.New(BaseXNumber, Convert.ToInt32("0"c), NumberBase)
    End Sub

    Public Sub New(BaseXNumber As String, NumberBaseLow As Integer, NumberBaseHigh As Integer)
        _BaseXNumber = BaseXNumber
        Me.NumberBaseLow = NumberBaseLow
        Me.NumberBaseHigh = NumberBaseHigh
    End Sub

    Public Sub New(Base10Number As Double)
        _Base10Number = Base10Number
    End Sub

    Private Sub DetermineNumberBase()
        Dim highestValue As Integer

        Dim currentValue As Integer

        For Each value As Char In _BaseXNumber

            currentValue = Convert.ToInt32(value)
            If currentValue > highestValue Then
                highestValue = currentValue
            End If
        Next

        NumberBaseHigh = highestValue
        NumberBaseLow = Convert.ToInt32("0"c) 'assume 0 is the lowest

    End Sub

    Private Function ToBaseString() As String
        Dim Base10Number As Double = _Base10Number

        Dim intPart As Long = Math.Truncate(Base10Number)
        Dim fracPart As Long = (Base10Number - intPart).ToString.Replace(DecimalSeparator, "")

        Dim intPartString As String = ConvertIntToString(intPart)
        Dim fracPartString As String = If(fracPart <> 0, DecimalSeparator & ConvertIntToString(fracPart), "")

        Return intPartString & fracPartString

    End Function

    Private Function ToBase10() As Double
        Dim intPartString As String = _BaseXNumber.Split(DecimalSeparator)(0).Replace(GroupSeparator, "")
        Dim fracPartString As String = If(_BaseXNumber.Contains(DecimalSeparator), _BaseXNumber.Split(DecimalSeparator)(1), "")

        Dim intPart As Long = ConvertStringToInt(intPartString)
        Dim fracPartNumerator As Long = ConvertStringToInt(fracPartString)
        Dim fracPartDenominator As Long = ConvertStringToInt(GetEncodedChar(1) & String.Join("", Enumerable.Repeat(GetEncodedChar(0), fracPartString.ToString.Length)))

        Return Convert.ToDouble(intPart + fracPartNumerator / fracPartDenominator)

    End Function

    Private Function ConvertIntToString(ValueToConvert As Long) As String
        Dim result As String = String.Empty
        Dim targetBase As Long = GetEncodingCharsLength()

        Do
            result = GetEncodedChar(ValueToConvert Mod targetBase) & result
            ValueToConvert = ValueToConvert \ targetBase
        Loop While ValueToConvert > 0

        Return result
    End Function

    Private Function ConvertStringToInt(ValueToConvert As String) As Long
        Dim result As Long
        Dim targetBase As Integer = GetEncodingCharsLength()
        Dim startBase As Integer = GetEncodingCharsStartBase()

        Dim value As Char
        For x As Integer = 0 To ValueToConvert.Length - 1
            value = ValueToConvert(x)
            result += GetDecodedChar(value) * Convert.ToInt32(Math.Pow(GetEncodingCharsLength, ValueToConvert.Length - (x + 1)))
        Next

        Return result

    End Function

    Private Function GetEncodedChar(index As Integer) As Char
        If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
            Return _CharacterArray(index)
        Else
            Return Convert.ToChar(index + NumberBaseLow)
        End If
    End Function

    Private Function GetDecodedChar(character As Char) As Integer
        If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
            Return _CharacterArray.IndexOf(character)
        Else
            Return Convert.ToInt32(character) - NumberBaseLow
        End If
    End Function

    Private Function GetEncodingCharsLength() As Integer
        If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
            Return _CharacterArray.Count
        Else
            Return NumberBaseHigh - NumberBaseLow + 1
        End If
    End Function

    Private Function GetEncodingCharsStartBase() As Integer
        If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
            Return GetHighLow.Key
        Else
            Return NumberBaseLow
        End If
    End Function
End Class

A teraz, aby kod przechodził przez kolumny Excela:

    Public Function GetColumnList(DataSheetID As String) As List(Of String)
        Dim workingColumn As New BaseNumber("A")
        workingColumn.SetCharacterArray("@ABCDEFGHIJKLMNOPQRSTUVWXYZ")

        Dim listOfPopulatedColumns As New List(Of String)
        Dim countOfEmptyColumns As Integer

        Dim colHasData As Boolean
        Dim cellHasData As Boolean

        Do
            colHasData = True
            cellHasData = False
            For r As Integer = 1 To GetMaxRow(DataSheetID)
                cellHasData = cellHasData Or XLGetCellValue(DataSheetID, workingColumn.GetBaseXNumber & r) <> ""
            Next
            colHasData = colHasData And cellHasData

            'keep trying until we get 4 empty columns in a row
            If colHasData Then
                listOfPopulatedColumns.Add(workingColumn.GetBaseXNumber)
                countOfEmptyColumns = 0
            Else
                countOfEmptyColumns += 1
            End If

            'we are already starting with column A, so increment after we check column A
            Do
                workingColumn.SetNumber(workingColumn.GetBase10Number + 1)
            Loop Until Not workingColumn.GetBaseXNumber.Contains("@")

        Loop Until countOfEmptyColumns > 3

        Return listOfPopulatedColumns

    End Function

Zauważysz, że ważną częścią części Excela jest to, że 0 jest identyfikowane przez @ w ponownie utworzonym numerze. Więc po prostu odfiltrowuję wszystkie liczby, które mają w sobie znak @ i otrzymuję odpowiednią sekwencję (A, B, C, ..., Z, AA, AB, AC, ...).

cjbarth
źródło
0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConvertToAnyBase
{
   class Program
    {
        static void Main(string[] args)
        {
            var baseNumber = int.Parse(Console.ReadLine());
            var number = int.Parse(Console.ReadLine());
            string conversion = "";


            while(number!=0)
            {

                conversion += Convert.ToString(number % baseNumber);
                number = number / baseNumber;
            }
            var conversion2 = conversion.ToArray().Reverse();
            Console.WriteLine(string.Join("", conversion2));


       }
    }
}
Martin Dimitrov
źródło
To dla liczb podstawowych od 1 do 10.
Martin Dimitrov
0

Jeśli ktoś szuka opcji VB, wynikało to z odpowiedzi Pawła:

Public Shared Function ToBase(base10 As Long, Optional baseChars As String = "0123456789ABCDEFGHIJKLMNOPQRTSUVWXYZ") As String

    If baseChars.Length < 2 Then Throw New ArgumentException("baseChars must be at least 2 chars long")

    If base10 = 0 Then Return baseChars(0)

    Dim isNegative = base10 < 0
    Dim radix = baseChars.Length
    Dim index As Integer = 64 'because it's how long a string will be if the basechars are 2 long (binary)
    Dim chars(index) As Char '65 chars, 64 from above plus one for sign if it's negative

    base10 = Math.Abs(base10)


    While base10 > 0
        chars(index) = baseChars(base10 Mod radix)
        base10 \= radix

        index -= 1
    End While

    If isNegative Then
        chars(index) = "-"c
        index -= 1
    End If

    Return New String(chars, index + 1, UBound(chars) - index)

End Function
Caius Jard
źródło
0

Jest to dość prosty sposób, ale może nie być najszybszy. Jest dość potężny, ponieważ można go komponować.

public static IEnumerable<int> ToBase(this int x, int b)
{
    IEnumerable<int> ToBaseReverse()
    {
        if (x == 0)
        {
            yield return 0;
            yield break;
        }
        int z = x;
        while (z > 0)
        {
            yield return z % b;
            z = z / b;
        }
    }

    return ToBaseReverse().Reverse();
}

Połącz to z tą prostą metodą rozszerzenia, a uzyskanie dowolnej bazy jest teraz możliwe:

public static string ToBase(this int number, string digits) =>
    String.Concat(number.ToBase(digits.Length).Select(x => digits[x]));

Można go używać w następujący sposób:

var result = 23.ToBase("01");
var result2 = 23.ToBase("012X");

Console.WriteLine(result);
Console.WriteLine(result2);

Wynik to:

10111
11X
Enigmativity
źródło