3

I want to generate alphanumeric number in such way that its first two character represents the alpha and rest 7 digits represent numeric and it should be sequential. for example it should start like this

enter image description here

and once its reaches seven time 9 enter image description hereit should start like

enter image description here

and this process will continue in batches so I need to store the last generated numbers count as well so whenever the window service start again in can take last number count from DB and then generate new numbers.

In my current code I am able to generate it till AA9999999 but after that its change to A10000000 instead of AB0000001 .

Here is my code as follow which I have tried so far .

private static List<char> Values;

        private static readonly char[] AlphaNumericValues = {
        'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z','2','3', '4', '5', '6', '7', '8', '9'
        };
        private static readonly char[] NumericValues = {
         '0','1','2','3', '4', '5', '6', '7', '8', '9'
        };

        private static readonly char[] AlphaOnlyValues = {
          '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'
        };
        private static int ValueCount = 0;


    



switch (selectedItem.suffixType)
            {
                case "alphanumeric":
                    Values = AlphaNumericValues.ToList();
                    ValueCount = AlphaNumericValues.Count();
                    break;
                case "numeric":
                    Values = NumericValues.ToList();
                    ValueCount = NumericValues.Count();
                    break;
                case "alphaonly":
                    Values = AlphaOnlyValues.ToList();
                    ValueCount = AlphaOnlyValues.Count();
                    break;
            }

            if(selectedItem.suffixType== "alphaonly")
            {
                GeneratedNumbers = GenerateValues2(KnoLength, selectedItem).ToList();
            }

and here are two methods as follow

private IEnumerable<Tuple<string, long>> GenerateValues2(int count, PrefixItems selectedItem)
        {
            for (var c = 0; c < count; c++)
            {
                yield return FormatNumber2(KeyCountCurrent++, selectedItem.PrefixSize);

            }

        }
        private static Tuple<string, long> FormatNumber2(long value, int FormatSize)
        {

            var digits = Enumerable.Repeat(Values[0], FormatSize).ToList();

            digits[2] = NumericValues[Convert.ToInt32(0)];
            digits[3] = NumericValues[Convert.ToInt32(0)];
            digits[4] = NumericValues[Convert.ToInt32(0)];
            digits[5] = NumericValues[Convert.ToInt32(0)];
            digits[6] = NumericValues[Convert.ToInt32(0)];
            digits[7] = NumericValues[Convert.ToInt32(0)];
            digits[8] = NumericValues[Convert.ToInt32(0)];


            var slotCount = digits.Count;
            var current = value;
            var count = 0;
            while (current > 0)
            {
                long rem;
                current = Math.DivRem(current, NumericValues.Count(), out rem);
                digits[slotCount - ++count] = NumericValues[Convert.ToInt32(rem)];
            }

            return new Tuple<string, long>(item1: string.Join("", digits), item2: value);
        }
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
rahularyansharma
  • 11,156
  • 18
  • 79
  • 135

5 Answers5

2

Here is one way:

const long MaxAlphaNumericValue = 26 * 26 * 10000000L - 1;
static string ToAlphaNumeric(long value) {
    // validate your input
    if (value < 0 || value > MaxAlphaNumericValue)
        throw new ArgumentException("value");
    // divide by 10000000. Remainder will be numeric part, quotient will be letters part
    var (lettersPart, numbersPart) = Math.DivRem(value, 10000000L);        
    // divide letters part by 26. Remainder will be second letter, quotient will be first letter
    // since we validated input - we can not check for overflow here
    var (first, second) = Math.DivRem(lettersPart, 26);
    var firstLetter = ((char) ('A' + first)).ToString();
    var secondLetter = ((char) ('A' + second)).ToString();
    // just concat what we got        
    return firstLetter + secondLetter + numbersPart.ToString("D7");
}

4.7.2 version compatible code

using System;
                    
public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");
        Console.WriteLine(ToAlphaNumeric(100000001));
    }
    
    const long MaxAlphaNumericValue = 26 * 26 * 10000000L - 1;
    static string ToAlphaNumeric(long v) 
    {
        long numbersPart;
        var lettersPart = Math.DivRem(v, 10000000L, out numbersPart);        
        long second;
        var first = Math.DivRem(lettersPart, 26, out second);
        var firstLetter = ((char) ('A' + first)).ToString();
        var secondLetter = ((char) ('A' + second)).ToString();
        return firstLetter + secondLetter + numbersPart.ToString("D7");
    }
}
rahularyansharma
  • 11,156
  • 18
  • 79
  • 135
Evk
  • 98,527
  • 8
  • 141
  • 191
  • https://dotnetfiddle.net/B7HbUj – rahularyansharma Dec 30 '22 at 09:24
  • Since you didn't mention which .NET version you need - I used the most recent. And that fiddle uses old .NET 4.7, so code does not compile. – Evk Dec 30 '22 at 15:49
  • 2
    Great answer. @rahularyansharma if you really need to use .NET Framework 4.7.x use the Math.DivRem method with an out param: https://learn.microsoft.com/en-us/dotnet/api/system.math.divrem?view=netframework-4.7.2 – Mafii Jan 03 '23 at 11:09
  • @rahularyansharma here's the code adjusted for 4.7.2: https://dotnetfiddle.net/PDurEf – Mafii Jan 03 '23 at 11:14
  • @Evk feel free to include it in your answer! – Mafii Jan 03 '23 at 11:15
2

Based on your question, here is my understanding about requirements:

  • Be able to generate identifiers in alphabetic order, sequentially
  • Identifier's first two characters are letters from English alphabet
  • Rest of the characters of the identifier are numbers
  • The algorithm should be able to continue where it was left off

With the following three simple methods we can satisfy all requirements:

GetNextCharacter

static readonly char[] Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
static IEnumerable<char> GetNextCharacter(char startFrom = 'A')
{
    foreach (var letter in Alphabet)
        if (letter >= startFrom)
            yield return letter;
}

GetNextSuffix

static IEnumerable<string> GetNextSuffix(int startFrom = 0)
{
    for (int suffix = startFrom + 1; suffix < 10_000_000; suffix++)
        yield return suffix.ToString("D7");
}

GenerateIdentifiers

static IEnumerable<string> GenerateIdentifiers(string lastKnown = null)
{
    char lastFirstLetter = lastKnown?[0] ?? 'A';
    char lastSecondLetter = lastKnown?[1] ?? 'A';
    string lastSuffixRaw = lastKnown?[2..].ToString() ?? "0";
    var lastSuffix = int.TryParse(lastSuffixRaw, out var start) ? start : 0;

    foreach (var firstLetter in GetNextCharacter(lastFirstLetter))
    {
        foreach (var secondLetter in GetNextCharacter(lastSecondLetter))
        {
            foreach (var suffix in GetNextSuffix(lastSuffix))
            {
                yield return $"{firstLetter}{secondLetter}{suffix}";
            }
            lastSuffix = 0;
        }
        lastSecondLetter = 'A'; 
    }      
}

Here I've assumed that the lastKnow is a valid identifier. In production code you should not make this assumption and rigorously check the format.


UPDATE #1
The original version included some bugs. They were pointed out by Astrid E.. I've updated the above code based on the suggestions.

I've created a simple dotnet fiddle to verify that it is working fine now. In the sample the last know id is AZ9999998 and I call the GenerateIdentifiers 10 times.

  • Before the fixes the last item was AZ0000019
  • After the fixes the last item is BA0000009

Thanks again Astrid!

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
2

You can create a custom class that encapsulates your rules for incrementing and then use it as needed.

public class Counter {
    char FirstLetter;
    char SecondLetter;
    int Number;

    public Counter() {
        FirstLetter = 'A';
        SecondLetter = 'A';
        Number = 0;
    }

    public Counter(string startingValue) {
        FirstLetter = startingValue[0];
        SecondLetter = startingValue[1];
        Number = Int32.Parse(startingValue.Substring(2));
    }
    public string Value {
        get {
            ++Number;
            if (Number > 9999999) {
                Number = 1;
                ++SecondLetter;
                if (SecondLetter > 'Z') {
                    ++FirstLetter;
                    SecondLetter = 'A';
                }
            }
            return $"{FirstLetter}{SecondLetter}{Number:0000000}";
        }
    }
}
NetMage
  • 26,163
  • 3
  • 34
  • 55
1

Not best code, but I have not time:

void Main()
{
    for (int i = 0; i < 3100; i++)
    {
        var result = Generate(i);
        //result.Dump();
    }
}

public static string Generate(int number)
{
    int numDigits = 2;
    int mod = (int)Math.Pow(10,numDigits);
    
    var strNumber = (number % mod).ToString().PadLeft(numDigits, '0');
    
    number /= mod;
    int a = (int)'A';
    int aZ = (int)'Z'-(int)'A' + 1;
    
    char letter1 = (char) ((number % aZ) + a);
    number /= aZ;
    char letter2 = (char) ((number % aZ) + a);
    
    return "" + letter2 + letter1 + strNumber;
}

It starts from 00, but I think can be easily tweaked.

apocalypse
  • 5,764
  • 9
  • 47
  • 95
1

Instead of depending on generating the sequence via code. You probably need a table to store the sequence, and just retrieve the values from that table.

You could start with something like :

CREATE TABLE SequenceTable (Id INT, Prefix NVARCHAR(50), Starts INT, Ends INT, LastSequence INT, IsExcluded BIT)

Then, just define the sequences you need either by code or sql, example :

;WITH CTE AS (
    SELECT 
        1 Id
    ,   CAST('AA' AS NVARCHAR(50)) Prefix
    ,   1 Starts
    ,   9999999 Ends
    ,   1 LastSequence 
    ,   CAST(0 AS BIT) IsExcluded
    ,   CAST(0 AS BIT) IsActive
    UNION ALL 
    SELECT 
        Id + 1
    ,   CAST(LEFT(Prefix, 1) + CHAR(ASCII(RIGHT(Prefix, 1)) + 1) AS NVARCHAR(50))
    ,   Starts
    ,   Ends
    ,   LastSequence
    ,   IsExcluded
    ,   IsActive
    FROM CTE 
    WHERE Id < 26   
)
INSERT INTO  SequenceTable (Id, Prefix, Starts, Ends, LastSequence, IsExcluded)
SELECT Id, Prefix, Starts, Ends, LastSequence, IsExcluded
FROM CTE e

Then, from the code, you can do something like:

public class SequenceValue
{       
    public string Prefix { get; set; }
    
    public int Value { get; set; }
    
    public override string ToString(){
        return $"{Prefix}{Value:X7}";
    }
}

public class SequenceManager 
{
    public SequenceValue GenerateSequence() { ... }
}

Every time you generate a sequence, you increment the LastSequence and check the incremented value wither it's still within (starts /ends) range, if true, return the incremented value, if false, get the next sequence entry (e.g. AB).

You can add some additional flags to the table to help identify the current sequence that is used, another flag for sequences that already maxed out. and so on. (e.g IsExcluded).

iSR5
  • 3,274
  • 2
  • 14
  • 13