Самый быстрый способ преобразовать число 10 в любую базу в.NET?
У меня есть и старый (ish) метод C#, который я написал, который берет число и преобразует его в любую базу:
string ConvertToBase(int number, char[] baseChars);
Это не все, что супер быстро и аккуратно. Есть ли хороший, известный способ достижения этого в.NET?
Я ищу то, что позволяет мне использовать любую базу с произвольной строкой символов для использования.
Это позволяет только базы 16, 10, 8 и 2:
Convert.ToString(1, x);
Я хочу использовать это для достижения чрезвычайно высокой базы, используя числа, все строчные и заглавные буквы. Как и в этой теме, но для C# не JavaScript.
Кто-нибудь знает хороший и эффективный способ сделать это в C#?
10 ответов
Convert.ToString
может использоваться для преобразования числа в его эквивалентное строковое представление в указанной базе.
Пример:
string binary = Convert.ToString(5, 2); // convert 5 to its binary representation
Console.WriteLine(binary); // prints 101
Однако, как отмечается в комментариях, Convert.ToString
поддерживает только следующий ограниченный, но обычно достаточный набор баз: 2, 8, 10 или 16.
Обновление (чтобы соответствовать требованию конвертировать в любую базу):
Я не знаю ни одного метода в BCL, который способен конвертировать числа в любую базу, поэтому вам придется написать свою собственную небольшую служебную функцию. Простой пример будет выглядеть следующим образом (обратите внимание, что это можно сделать быстрее, заменив конкатенацию строк):
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);
}
}
Обновление 2 (улучшение производительности)
Использование буфера массива вместо конкатенации строк для построения результирующей строки повышает производительность, особенно при большом количестве (см. Метод IntToStringFast
). В лучшем случае (т.е. при максимально возможном вводе) этот метод примерно в три раза быстрее. Тем не менее, для 1-значных чисел (т.е. 1-значное число в целевой базе), IntToString
будет быстрее
Я недавно писал об этом в блоге. Моя реализация не использует строковые операции во время вычислений, что делает его очень быстрым. Поддерживается преобразование в любую систему счисления с основанием от 2 до 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;
}
Я также реализовал быструю обратную функцию на случай, если кому-то это тоже понадобится: от произвольной до десятичной системы счисления.
БЫСТРЫЕ МЕТОДЫ "ОТ" И "ДО"
Я опоздал на вечеринку, но я составил предыдущие ответы и улучшил их. Я думаю, что эти два метода быстрее, чем любые другие опубликованные до сих пор. Мне удалось преобразовать 1 000 000 чисел с базы 36 в менее чем 400 мс на одноядерном компьютере.
Пример ниже для базы 62. Изменить BaseChars
массив для преобразования из и в любую другую базу.
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;
}
РЕДАКТИРОВАТЬ (2018-07-12)
Исправлено для решения углового случая, найденного @AdrianBotor (см. Комментарии), преобразующего 46655 в базу 36. Это вызвано небольшой ошибкой вычисления с плавающей запятой Math.Log(46656, 36)
что ровно 3, но.NET возвращает 3 + 4.44e-16
, что вызывает дополнительный символ в выходном буфере.
Можно также использовать слегка измененную версию принятой и настроить строку базовых символов в соответствии с ее потребностями:
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;
}
Очень поздно на вечеринке по этому поводу, но я недавно написал следующий класс помощника для проекта на работе. Он был разработан для преобразования коротких строк в числа и обратно (упрощенная идеальная хеш- функция), однако он также будет выполнять преобразование чисел между произвольными основаниями. Base10ToString
Реализация метода отвечает на вопрос, который был первоначально размещен.
shouldSupportRoundTripping
Флаг, передаваемый в конструктор класса, необходим для предотвращения потери начальных цифр из числовой строки во время преобразования в base-10 и обратно (важно, учитывая мои требования!). В большинстве случаев потеря начальных 0 из числовой строки, вероятно, не будет проблемой.
Во всяком случае, вот код:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Stackru
{
/// <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;
}
}
}
Это также может быть разделено на подклассы для получения пользовательских преобразователей чисел:
namespace Stackru
{
public sealed class BinaryNumberConverter : Base10Converter
{
public BinaryNumberConverter()
: base(digits: "01", shouldSupportRoundTripping: false)
{
}
}
public sealed class HexNumberConverter : Base10Converter
{
public HexNumberConverter()
: base(digits: "0123456789ABCDEF", shouldSupportRoundTripping: false)
{
}
}
}
И код будет использоваться так:
using System.Diagnostics;
namespace Stackru
{
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);
}
}
}
}
Может ли этот класс из этого поста форума помочь вам?
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;
}
}
Полностью не проверено... дайте мне знать, если это работает! (Скопируйте его, если сообщение на форуме исчезнет или что-то в этом роде...)
Я тоже искал быстрый способ преобразования десятичного числа в другое основание в диапазоне [2..36], поэтому я разработал следующий код. Он прост в использовании и использует объект Stringbuilder в качестве прокси для символьного буфера, который мы можем индексировать символ за символом. Код кажется очень быстрым по сравнению с альтернативами и намного быстрее, чем инициализация отдельных символов в массиве символов.
Для собственного использования вы можете предпочесть: 1/ Возвращать пустую строку, а не выдавать исключение. 2/ уберите проверку радиуса, чтобы метод работал еще быстрее 3/ Инициализируйте объект Stringbuilder с 32 '0 и удалите строку result.Remove( 0, i);. Это приведет к тому, что строка будет возвращена с ведущими нулями и еще больше увеличит скорость. 4/ Сделайте объект Stringbuilder статическим полем внутри класса, поэтому независимо от того, сколько раз метод DecimalToBase вызывается, объект Stringbuilder инициализируется только один раз. Если вы сделаете это изменение 3 выше, больше не будет работать.
Я надеюсь, что кто-то найдет это полезным:)
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());
}
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));
}
}
}
Я использовал это для хранения Guid как более короткой строки (но было ограничено использованием 106 символов). Если кому-то интересно, вот мой код для декодирования строки обратно к числовому значению (в данном случае я использовал 2 ulong для значения Guid, а не для кодирования Int128 (так как я в 3.5, а не 4.0). Для ясности, CODE строка const с 106 уникальными символами. ConvertLongsToBytes довольно неинтересен.
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);
}
}
У меня была аналогичная потребность, за исключением того, что мне нужно было делать математику и на "числах" Я взял некоторые из предложений здесь и создал класс, который будет делать все эти забавные вещи. Он позволяет использовать любой символ Юникода для представления числа и работает с десятичными знаками.
Этот класс довольно прост в использовании. Просто создайте номер как тип New BaseNumber
, установите несколько свойств, и вы выключите. Процедуры обеспечивают автоматическое переключение между основанием 10 и основанием x, и заданное вами значение сохраняется в базе, в которой вы его установили, поэтому точность не теряется (пока не произойдет преобразование, но даже тогда потеря точности должна быть очень минимальной, поскольку рутинное использование Double
а также Long
где только возможно).
Я не могу командовать скоростью этой рутины. Вероятно, он довольно медленный, поэтому я не уверен, что он подойдет для того, кто задал вопрос, но он, безусловно, гибкий, поэтому, надеюсь, кто-то другой сможет его использовать.
Для любого другого, кому может понадобиться этот код для вычисления следующего столбца в Excel, я включу используемый мной цикл-код, который использует этот класс.
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
А теперь код для цикла по столбцам Excel:
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
Вы заметите, что важной частью Excel является то, что 0 обозначается знаком @ в пересмотренном числе. Поэтому я просто отфильтровываю все числа, в которых есть @, и получаю правильную последовательность (A, B, C, ..., Z, AA, AB, AC, ...).
Это довольно простой способ сделать это, но он может быть не самым быстрым. Он довольно мощный, потому что его можно компоновать.
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();
}
Объедините это с этим простым методом расширения, и теперь возможно получение любой базы:
public static string ToBase(this int number, string digits) =>
String.Concat(number.ToBase(digits.Length).Select(x => digits[x]));
Его можно использовать так:
var result = 23.ToBase("01");
var result2 = 23.ToBase("012X");
Console.WriteLine(result);
Console.WriteLine(result2);
Результат:
10111 11X
Если кто-то ищет вариант VB, это было основано на ответе Павла:
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