Generate the shortest alphanumeric save code - c#

For the purpose of a game, I need to generate a save code that the user can note somewhere and use to reload his game state later (no persistent data possible).
The save code needs to be short like 6DZF1D3, (a base 36 or base 62 string).
Scores of game levels can be simplified as a string like 1232312321321321321, a sequence where each char is a level score in "stars" (1, 2 or 3 stars).
There will be around 30 game levels.
I would like to generate the shortest code possible for the user, so my first idea was to generate all possibilities inside an Array. Then generate the base 62 code of the key where the user is. But with 3^30 possibilities, this is generating an array with 2e+14 key/values, which is not good for memory and CPU.
Second thought, was to use a base 4 to 62 converter, but most of codes I found are using int or long which are limited in size and lower than 30 chars.
Do you have any idea of how to generate the shortest save code composed of alphanumeric chars ?

The most common way to get binary data into a textual representation is Base64. Each character represents 6 bits of information. You have just under 48 bits of information, which nicely gets you to 8 Base64 digits.
So the strategy would be:
1. Convert your base 3 (star)array to base 2 using this algorithm.
2. Convert the bits to a byte array using Convert.ToByte();
3. Use Convert.ToBase64String() to create a Base64 string.
Edit: I realise you want to have it in a Base36, there are some code examples that can do it. This code needs a string as input, but converts it to a char[], so you can just provide the ByteArray instead.
Edit2:
The proof is in the eating, just created a back and forth converter for any base up to base36 (but can be extended). For your stars, you only have to provide a string with the star values as numbers (1 to 3).
private static string ConvertToOtherBase(string toConvert, int fromBase, int toBase)
{
const string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
long value = 0;
string result = "";
foreach (char digit in toConvert.ToCharArray())
value = (value * fromBase) + characters.IndexOf(digit);
while (value > 0)
{
result = characters[(int)(value % toBase)] + result;
value /= toBase;
}
return result;
}
You can call it like this (back and forth):
var stars = "112131121311213112131121311213";
string base36Result = ConvertToOtherBase(stars, 4, 36);
// 32NSB7MBR9T3
string base4Result = ConvertToOtherBase(base36Result, 36, 4);
// 112131121311213112131121311213

Of course, this question is opinion based but here is one simple way to save
Create object
public class Memento
{
public int Id {get; set;}
public int Level {get; set;}
public int Score {get; set;}
}
then just use Newtonsoft.Json library to serialize it. On top of it, you can encrypt serialized JSON so user can't see insides of saved data, and write it to disk. But of course, there are many ways of persisting the score. By the way, name of my class should point you to a programming pattern that specifically solves this issue
Update
Reading your comment - is this what you looking for?
int x = 5, y = 10;
byte[]xb = BitConverter.GetBytes(x);
var enumer = xb.Concat(BitConverter.GetBytes(y));
string outStr = Convert.ToBase64String(enumer.ToArray());
Console.WriteLine(outStr);
// your code: BQAAAAoAAAA=
And BTW, if you use int16, your code will be even shorter: BQAKAA==
byte[] back = Convert.FromBase64String(outStr);
short a = BitConverter.ToInt16(back, 0);
short b = BitConverter.ToInt16(back, 2);
Console.WriteLine(a + "_" + b);

So this is the code I wrote with the idea of #Yosh and that functions : https://www.pvladov.com/2012/07/arbitrary-to-decimal-numeral-system.html
string code = "";
string[] scoreArray = new string[100];
foreach (KeyValuePair<string, LevelScore> l in scores)
{
scoreArray[l.Value.levelNum - 1] = Convert.ToString(l.Value.stars, 2).PadLeft(2, '0');
}
for (int s = 0; s < scoreArray.Length; s++)
{
code = scoreArray[s] + code;
}
string b2 = code ;// like "111111111111111111111111111111111111111111111111111111111111";
print("b2 " + b2);
long b10 = ScoreUtils.ArbitraryToDecimalSystem(b2, 2);
print("b10 " + b10);
string b36 = ScoreUtils.DecimalToArbitrarySystem(b10, 36);
print("b36 " + b36);

If a user should be able to write it down i would prefer a Base58 encoding. So, for 1-3 possible stars per level we need 2 bits to encode each level.
00 => 0 star (would mean last unplayed level reached)
01 => 1 star
10 => 2 stars
11 => 3 stars
We need 60 bits for 30 levels, all levels with 3 stars would be decimal 1152921504606846975. This, base58 encoded, would be 3gDmDv6tjHG, not too long, is it?!
Update:
#DrNootNoot I'm glad you found a way to solve your problem! But I had fun to hack a small piece of code for my mentioned base58 version. I adapted the two functions by Pavel Vladov you used.
Maybe someday someone else has a similar issue:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] scoreArray = new string[30] { "1", "2", "3", "3", "1", "2", "2", "2", "3", "1", "1", "1", "2", "3", "2", "1", "2", "3", "1", "1", "1", "2", "2", "2", "1", "1", "2", "1", "2","3" };
ulong numScore = ScoreToDecimal(scoreArray);
string saveScore = UDecimalToBase58String(numScore);
Console.WriteLine("Score array: " + String.Join("-",scoreArray));
Console.WriteLine("Numeric score: " + Convert.ToString(numScore));
Console.WriteLine("Base58 score: " + saveScore);
ulong numScoreRestored = Base58StringToUDecimal(saveScore);
string[] scoreArrayRestored = DecimalToScore(numScoreRestored);
Console.WriteLine("From Base58 converted numeric score: " + Convert.ToString(numScoreRestored));
Console.WriteLine("From Base58 converted score array: " + String.Join("-", scoreArray));
Console.Read();
}
/// <summary>
/// Converts the stars-per-level array to a decimal value for the saved game.
/// </summary>
/// <param name="score">score array to convert. Max. 32 entries/levels.</param>
/// <returns></returns>
public static ulong ScoreToDecimal(string[] score)
{
int arrLength = score.Length;
if (arrLength > 32)
throw new ArgumentException("The score array must not be larger than 32 entries");
ulong result = 0;
for (int i = arrLength - 1; i >= 0; i--)
{
ulong singleScore = Convert.ToUInt64(score[i]);
if (singleScore > 3)
throw new ArgumentException(String.Format("Invalid score value. Max. allowed value is 3, but {0} was given at index {1}", singleScore, i), "score");
result += (singleScore << ((arrLength - 1 - i) * 2));
}
return result;
}
/// <summary>
/// Converts the decimal value of the saved game back to a stars-per-level array.
/// </summary>
/// <param name="decimalScore">Maximal 64-bit unsigned saved game number to convert.</param>
/// <returns></returns>
public static string[] DecimalToScore(ulong decimalScore)
{
List<string> result = new List<string>();
while(decimalScore > 0)
{
result.Add(Convert.ToString(decimalScore % 4));
decimalScore /= 4;
}
result.Reverse();
return result.ToArray();
}
/// <summary>
/// Adapted Unsigned-Base58-Version of Pavel Vladovs DecimalToArbitrarySystem function.
/// See: https://www.pvladov.com/2012/05/decimal-to-arbitrary-numeral-system.html
/// </summary>
/// <param name="decimalNumber"></param>
/// <returns></returns>
public static string UDecimalToBase58String(ulong decimalNumber)
{
const int BitsInLong = 64;
const int FixedRadix = 58;
const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
if (decimalNumber == 0)
return "0";
int index = BitsInLong - 1;
ulong currentNumber = decimalNumber;
char[] charArray = new char[BitsInLong];
while (currentNumber != 0)
{
int remainder = (int)(currentNumber % FixedRadix);
charArray[index--] = Digits[remainder];
currentNumber = currentNumber / FixedRadix;
}
string result = new String(charArray, index + 1, BitsInLong - index - 1);
return result;
}
/// <summary>
/// Adapted Unsigned-Base58-Version of Pavel Vladovs ArbitraryToDecimalSystem function.
/// See: https://www.pvladov.com/2012/07/arbitrary-to-decimal-numeral-system.html
/// </summary>
/// <param name="base58String"></param>
/// <returns></returns>
public static ulong Base58StringToUDecimal(string base58String)
{
const int FixedRadix = 58;
const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
if (String.IsNullOrEmpty(base58String))
return 0;
ulong result = 0;
ulong multiplier = 1;
for (int i = base58String.Length - 1; i >= 0; i--)
{
char c = base58String[i];
int digit = Digits.IndexOf(c);
if (digit == -1)
throw new ArgumentException(
"Invalid character in the arbitrary numeral system number",
"number");
result += (uint)digit * multiplier;
multiplier *= FixedRadix;
}
return result;
}
}
}
Regards

Related

How to build the heliocentric algorithm?

Given this problem:
Consider two of the planets in the orbital system: Earth and Mars.
Assume the Earth orbits the Sun in exactly 365 Earth days, and Mars
orbits the Sun in exactly 687 Earth days. Thus the Earth’s orbit
starts at day 0 and continues to day 364, and then starts over at day
0. Mars orbits similarly, but on a 687-day time scale.
We would like to find out how long it will take until both planets are
on day. 0 of their orbits simultaneously. Write a program that can
determine this.
Input Format:
The first line of input contains an integer N indicating the number of
test cases. N lines follow. Each test case contains two integers E and
M. These indicate which days Earth and Mars are at their respective
orbits.
Output Format:
For each case, display the case number followed by the smallest number
of days until the two planets will both be on day 0 of their orbits.
Follow the format of the sample output.
Sample Input 1
0 0
364 686
360 682
0 1
1 0
Sample Output 1
Case 1: 0
Case 2: 1
Case 3: 5
Case 4: 239075
Case 5: 11679
I tried solving the problem using modules but it doesn't seem correct
static string readInput;
static string firstStr = "";
static string secondStr = "";
static int firstInput;
static int secondInput;
static int testCases = 10;
static int caseNumber = 1;
static int outPut;
caseNumber <= testCases
static void Main(string[] args) {
//recall runProcess as long caseNumber is less or equal testCases
while (caseNumber <= testCases) {
runProcess();
Console.WriteLine("Case " + caseNumber + ": " + outPut);
caseNumber++;
}
}
Read input from console:
/// <summary>
/// This is the main process, is extracted to void so we can recall it.
/// </summary>
public static void runProcess() {
readInput = Console.ReadLine();
if (readInput != null) {
for (int i = 0; i < readInput.Length; i++) {
secondStr = secondStr + readInput[i];
if (readInput[i] == ' ') {
firstStr = secondStr;
secondStr = "";
continue;
}
}
}
firstInput = Convert.ToInt32(firstStr);
secondInput = Convert.ToInt32(secondStr);
outPut = atZero(firstInput, secondInput);
}
/// <summary>
/// This method takes the input data from the console to later determine the zero point
/// </summary>
/// <param name="earthDays"></param>
/// <param name="marsDays"></param>
/// <returns></returns>
public static int atZero(int earthDays, int marsDays) {
int earthOrbit = 365;
int marsOrbit = 687;
int modEarth = earthOrbit;
int modMars = marsOrbit;
int earthDistinction = earthOrbit - earthDays;
int marsDistinction = marsOrbit - marsDays;
if ((modInverse(earthDistinction, marsDistinction, modMars)) == 0) {
return (modInverse(marsDistinction, earthDistinction, modEarth)) * marsDistinction;
} else {
return (modInverse(earthDistinction, marsDistinction, modMars)) * earthDistinction;
}
}
mod invert
/// <summary>
/// The method below takes a denominator, numerator and a mod to later invert the mod.
/// </summary>
/// <param name="denominator"></param>
/// <param name="numerator"></param>
/// <param name="mod"></param>
/// <returns>modInverse</returns>
static int modInverse(int denominator, int numerator, int mod) {
int i = mod, outputAll = 0, d = numerator;
while (denominator > 0) {
int divided = i / denominator, x = denominator;
denominator = i % x;
i = x;
x = d;
d = outputAll - divided * x;
outputAll = x;
}
outputAll %= mod;
if (outputAll < 0) outputAll = (outputAll + mod) % mod;
return outputAll;
}
Is there any way to solve the problem without modules?
Thanks.
A straight forward way to calculate a solution could be this method:
private static int DaysTillBothAt0(int currentEarthDay, int currentMarsDay)
{
int result = 0, earth = currentEarthDay, mars = currentMarsDay;
while (earth != 0 || mars != 0)
{
result += 1;
earth = (earth + 1) % 365;
mars = (mars + 1) % 687;
}
return result;
}
This is of course not the fastest alogrithm or mathematically extraordinary elegant, but for the required data range performance doesn't matter here at all. (I don't know what your teacher expects, though).
It simply counts the orbits forward until they meet at 0.
You can use this for your test cases like this:
result = DaysTillBothAt0(0, 0); // 0
result = DaysTillBothAt0(364, 686); // 1
result = DaysTillBothAt0(360, 682); // 5
result = DaysTillBothAt0(0, 1); // 239075
result = DaysTillBothAt0(1, 0); // 11679
One more solution for this problem
private static int DaysTillBothAt0(int currentEarthday, int currentMarsday) {
int count = 365 - currentEarthday;
currentMarsday = (currentMarsday + count) % 687;
while (currentMarsday != 0) {
currentMarsday = (currentMarsday + 365) % 687;
count += 365;
}
return currentMarsday;
}

How to measure similarity of 2 strings aside from computing distance

I am creating a program that checks if the word is simplified word(txt, msg, etc.) and if it is simplified it finds the correct spelling like txt=text, msg=message. Iam using the NHunspell Suggest Method in c# which suggest all possible results.
The problem is if I inputted "txt" the result is text,tat, tot, etc. I dont know how to select the correct word. I used Levenshtein Distance (C# - Compare String Similarity) but the results still results to 1.
Input: txt
Result: text = 1, ext = 1 tit = 1
Can you help me how to get the meaning or the correct spelling of the simplified words?
Example: msg
I have tested your input with your sample data and only text has a distance of 25 whereas the other have a distance of 33. Here's my code:
string input = "TXT";
string[] words = new[]{"text","tat","tot"};
var levenshtein = new Levenshtein();
const int maxDistance = 30;
var distanceGroups = words
.Select(w => new
{
Word = w,
Distance = levenshtein.iLD(w.ToUpperInvariant(), input)
})
.Where(x => x.Distance <= maxDistance)
.GroupBy(x => x.Distance)
.OrderBy(g => g.Key)
.ToList();
foreach (var topCandidate in distanceGroups.First())
Console.WriteLine("Word:{0} Distance:{1}", topCandidate.Word, topCandidate.Distance);
and here is the levenshtein class:
public class Levenshtein
{
///*****************************
/// Compute Levenshtein distance
/// Memory efficient version
///*****************************
public int iLD(String sRow, String sCol)
{
int RowLen = sRow.Length; // length of sRow
int ColLen = sCol.Length; // length of sCol
int RowIdx; // iterates through sRow
int ColIdx; // iterates through sCol
char Row_i; // ith character of sRow
char Col_j; // jth character of sCol
int cost; // cost
/// Test string length
if (Math.Max(sRow.Length, sCol.Length) > Math.Pow(2, 31))
throw (new Exception("\nMaximum string length in Levenshtein.iLD is " + Math.Pow(2, 31) + ".\nYours is " + Math.Max(sRow.Length, sCol.Length) + "."));
// Step 1
if (RowLen == 0)
{
return ColLen;
}
if (ColLen == 0)
{
return RowLen;
}
/// Create the two vectors
int[] v0 = new int[RowLen + 1];
int[] v1 = new int[RowLen + 1];
int[] vTmp;
/// Step 2
/// Initialize the first vector
for (RowIdx = 1; RowIdx <= RowLen; RowIdx++)
{
v0[RowIdx] = RowIdx;
}
// Step 3
/// Fore each column
for (ColIdx = 1; ColIdx <= ColLen; ColIdx++)
{
/// Set the 0'th element to the column number
v1[0] = ColIdx;
Col_j = sCol[ColIdx - 1];
// Step 4
/// Fore each row
for (RowIdx = 1; RowIdx <= RowLen; RowIdx++)
{
Row_i = sRow[RowIdx - 1];
// Step 5
if (Row_i == Col_j)
{
cost = 0;
}
else
{
cost = 1;
}
// Step 6
/// Find minimum
int m_min = v0[RowIdx] + 1;
int b = v1[RowIdx - 1] + 1;
int c = v0[RowIdx - 1] + cost;
if (b < m_min)
{
m_min = b;
}
if (c < m_min)
{
m_min = c;
}
v1[RowIdx] = m_min;
}
/// Swap the vectors
vTmp = v0;
v0 = v1;
v1 = vTmp;
}
// Step 7
/// Value between 0 - 100
/// 0==perfect match 100==totaly different
///
/// The vectors where swaped one last time at the end of the last loop,
/// that is why the result is now in v0 rather than in v1
//System.Console.WriteLine("iDist=" + v0[RowLen]);
int max = System.Math.Max(RowLen, ColLen);
return ((100 * v0[RowLen]) / max);
}
///*****************************
/// Compute the min
///*****************************
private int Minimum(int a, int b, int c)
{
int mi = a;
if (b < mi)
{
mi = b;
}
if (c < mi)
{
mi = c;
}
return mi;
}
///*****************************
/// Compute Levenshtein distance
///*****************************
public int LD(String sNew, String sOld)
{
int[,] matrix; // matrix
int sNewLen = sNew.Length; // length of sNew
int sOldLen = sOld.Length; // length of sOld
int sNewIdx; // iterates through sNew
int sOldIdx; // iterates through sOld
char sNew_i; // ith character of sNew
char sOld_j; // jth character of sOld
int cost; // cost
/// Test string length
if (Math.Max(sNew.Length, sOld.Length) > Math.Pow(2, 31))
throw (new Exception("\nMaximum string length in Levenshtein.LD is " + Math.Pow(2, 31) + ".\nYours is " + Math.Max(sNew.Length, sOld.Length) + "."));
// Step 1
if (sNewLen == 0)
{
return sOldLen;
}
if (sOldLen == 0)
{
return sNewLen;
}
matrix = new int[sNewLen + 1, sOldLen + 1];
// Step 2
for (sNewIdx = 0; sNewIdx <= sNewLen; sNewIdx++)
{
matrix[sNewIdx, 0] = sNewIdx;
}
for (sOldIdx = 0; sOldIdx <= sOldLen; sOldIdx++)
{
matrix[0, sOldIdx] = sOldIdx;
}
// Step 3
for (sNewIdx = 1; sNewIdx <= sNewLen; sNewIdx++)
{
sNew_i = sNew[sNewIdx - 1];
// Step 4
for (sOldIdx = 1; sOldIdx <= sOldLen; sOldIdx++)
{
sOld_j = sOld[sOldIdx - 1];
// Step 5
if (sNew_i == sOld_j)
{
cost = 0;
}
else
{
cost = 1;
}
// Step 6
matrix[sNewIdx, sOldIdx] = Minimum(matrix[sNewIdx - 1, sOldIdx] + 1, matrix[sNewIdx, sOldIdx - 1] + 1, matrix[sNewIdx - 1, sOldIdx - 1] + cost);
}
}
// Step 7
/// Value between 0 - 100
/// 0==perfect match 100==totaly different
//System.Console.WriteLine("Dist=" + matrix[sNewLen, sOldLen]);
int max = System.Math.Max(sNewLen, sOldLen);
return (100 * matrix[sNewLen, sOldLen]) / max;
}
}
Not a complete solution, just a hopefully helpful suggestion...
It seems to me that people are unlikely to use simplifications that are as long as the correct word, so you could at least filter out all results whose length <= the input's length.
You really need to implement the SOUNDEX routine that exists in SQL. I've done that in the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Soundex
{
class Program
{
static char[] ignoreChars = new char[] { 'a', 'e', 'h', 'i', 'o', 'u', 'w', 'y' };
static Dictionary<char, int> charVals = new Dictionary<char, int>()
{
{'b',1},
{'f',1},
{'p',1},
{'v',1},
{'c',2},
{'g',2},
{'j',2},
{'k',2},
{'q',2},
{'s',2},
{'x',2},
{'z',2},
{'d',3},
{'t',3},
{'l',4},
{'m',5},
{'n',5},
{'r',6}
};
static void Main(string[] args)
{
Console.WriteLine(Soundex("txt"));
Console.WriteLine(Soundex("text"));
Console.WriteLine(Soundex("ext"));
Console.WriteLine(Soundex("tit"));
Console.WriteLine(Soundex("Cammmppppbbbeeelll"));
}
static string Soundex(string s)
{
s = s.ToLower();
StringBuilder sb = new StringBuilder();
sb.Append(s.First());
foreach (var c in s.Substring(1))
{
if (ignoreChars.Contains(c)) { continue; }
// if the previous character yields the same integer then skip it
if ((int)char.GetNumericValue(sb[sb.Length - 1]) == charVals[c]) { continue; }
sb.Append(charVals[c]);
}
return string.Join("", sb.ToString().Take(4)).PadRight(4, '0');
}
}
}
See, with this code, the only match out of the examples you gave would be text. Run the console application and you'll see the output (i.e. txt would match text).
One method I think programs like word uses to correct spellings, is to use NLP (Natural Language Processing) techniques to get the order of Nouns/Adjectives used in the context of the spelling mistakes.. then comparing that to known sentence structures they can estimate 70% chance the spelling mistake was a noun and use that information to filter the corrected spellings.
SharpNLP looks like a good library but I haven't had a chance to fiddle with it yet. To build a library of known sentence structures BTW, in uni we applied our algorithms to public domain books.
check out sams simMetrics library I found on SO (download here, docs here) for loads more options for algorithms to use besides Levenshtein distance.
Expanding on my comment, you could use regex to search for a result that is an 'expansion' of the input. Something like this:
private int stringSimilarity(string input, string result)
{
string regexPattern = ""
foreach (char c in input)
regexPattern += input + ".*"
Match match = Regex.Match(result, regexPattern,
RegexOptions.IgnoreCase);
if (match.Success)
return 1;
else
return 0;
}
Ignore the 1 and the 0 - I don't know how similarity valuing works.

English Dictionary word matching from a string

I'm trying to get my head around a problem of identifying the best match of English words from a dictionary file to a given string.
For example ("lines" being a List of dictionary words):
string testStr = "cakeday";
for (int x= 0; x<= testStr.Length; x++)
{
string test = testStr.Substring(x);
if (test.Length > 0)
{
string test2 = testStr.Remove(counter);
int count = (from w in lines where w.Equals(test) || w.Equals(test2) select w).Count();
Console.WriteLine("Test: {0} / {1} : {2}", test, test2, count);
}
}
Gives the output:
Test: cakeday / : 0
Test: akeday / c : 1
Test: keday / ca : 0
Test: eday / cak : 0
Test: day / cake : 2
Test: ay / caked : 1
Test: y / cakeda : 1
Obviously "day / cake" is the best fit for the string however if I were to introduce a 3rd word into the string e.g "cakedaynow" it doesnt work so well.
I know the example is primitive, its more a proof of concept and was wondering if anyone had any experience with this type of string analysis?
Thanks!
You'll want to research the class of algorithms appropriate to what you're trying to do. Start with Approximate string matching on Wikipedia.
Also, here's a Levenshtein Edit Distance implementation in C# to get you started:
using System;
namespace StringMatching
{
/// <summary>
/// A class to extend the string type with a method to get Levenshtein Edit Distance.
/// </summary>
public static class LevenshteinDistanceStringExtension
{
/// <summary>
/// Get the Levenshtein Edit Distance.
/// </summary>
/// <param name="strA">The current string.</param>
/// <param name="strB">The string to determine the distance from.</param>
/// <returns>The Levenshtein Edit Distance.</returns>
public static int GetLevenshteinDistance(this string strA, string strB)
{
if (string.IsNullOrEmpty(strA) && string.IsNullOrEmpty(strB))
return 0;
if (string.IsNullOrEmpty(strA))
return strB.Length;
if (string.IsNullOrEmpty(strB))
return strA.Length;
int[,] deltas; // matrix
int lengthA;
int lengthB;
int indexA;
int indexB;
char charA;
char charB;
int cost; // cost
// Step 1
lengthA = strA.Length;
lengthB = strB.Length;
deltas = new int[lengthA + 1, lengthB + 1];
// Step 2
for (indexA = 0; indexA <= lengthA; indexA++)
{
deltas[indexA, 0] = indexA;
}
for (indexB = 0; indexB <= lengthB; indexB++)
{
deltas[0, indexB] = indexB;
}
// Step 3
for (indexA = 1; indexA <= lengthA; indexA++)
{
charA = strA[indexA - 1];
// Step 4
for (indexB = 1; indexB <= lengthB; indexB++)
{
charB = strB[indexB - 1];
// Step 5
if (charA == charB)
{
cost = 0;
}
else
{
cost = 1;
}
// Step 6
deltas[indexA, indexB] = Math.Min(deltas[indexA - 1, indexB] + 1, Math.Min(deltas[indexA, indexB - 1] + 1, deltas[indexA - 1, indexB - 1] + cost));
}
}
// Step 7
return deltas[lengthA, lengthB];
}
}
}
Why not:
Check all the strings inside the search word extracting from current search position to all possible lengths of the string and extract all discovered words. E.g.:
var list = new List<string>{"the", "me", "cat", "at", "theme"};
const string testStr = "themecat";
var words = new List<string>();
var len = testStr.Length;
for (int x = 0; x < len; x++)
{
for(int i = (len - 1); i > x; i--)
{
string test = testStr.Substring(x, i - x + 1);
if (list.Contains(test) && !words.Contains(test))
{
words.Add(test);
}
}
}
words.ForEach(n=> Console.WriteLine("{0}, ",n));//spit out current values
Output:
theme, the, me, cat, at
Edit
Live Scenario 1:
For instance let's say you want to always choose the longest word in a jumbled sentence, you could read from front forward, reducing the amount of text read till you are through. Using a dictionary makes it much easier, by storing the indexes of the discovered words, we can quickly check to see if we have stored a word containing another word we are evaluating before.
Example:
var list = new List<string>{"the", "me", "cat", "at", "theme", "crying", "them"};
const string testStr = "themecatcryingthem";
var words = new Dictionary<int, string>();
var len = testStr.Length;
for (int x = 0; x < len; x++)
{
int n = len > 28 ? 28 : len;//assuming 28 is the maximum length of an english word
for(int i = (n - 1); i > x; i--)
{
string test = testStr.Substring(x, i - x + 1);
if (list.Contains(test))
{
if (!words.ContainsValue(test))
{
bool found = false;//to check if there's a shorter item starting from same index
var key = testStr.IndexOf(test, x, len - x);
foreach (var w in words)
{
if (w.Value.Contains(test) && w.Key != key && key == (w.Key + w.Value.Length - test.Length))
{
found = true;
}
}
if (!found && !words.ContainsKey(key)) words.Add(key, test);
}
}
}
}
words.Values.ToList().ForEach(n=> Console.WriteLine("{0}, ",n));//spit out current values
Output:
theme, cat, crying, them

.NET Short Unique Identifier

I need a unique identifier in .NET (cannot use GUID as it is too long for this case).
Do people think that the algorithm used here is a good candidate or do you have any other suggestions?
This one a good one - http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx
and also here
YouTube-like GUID
You could use Base64:
string base64Guid = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
That generates a string like E1HKfn68Pkms5zsZsvKONw==. Since a GUID is
always 128 bits, you can omit the == that you know will always be
present at the end and that will give you a 22 character string. This
isn't as short as YouTube though.
I use a similar approach as Dor Cohen's but removing some special characters:
var uid = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "");
This will output just alphanumeric characters. The UIDs are not guaranteed to have always the same length. Here is a sample run:
vmKo0zws8k28fR4V4Hgmw
TKbhS0G2V0KqtpHOU8e6Ug
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg
CQUg1lXIXkWG8KDFy7z6Ow
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg
var ticks = new DateTime(2016,1,1).Ticks;
var ans = DateTime.Now.Ticks - ticks;
var uniqueId = ans.ToString("x");
Keep a baseline date (which in this case is 1st Jan 2016) from when you will start generating these ids. This will make your ids smaller.
Generated Number: 3af3c14996e54
Simple usable package. I use it for temporal request id generator.
https://www.nuget.org/packages/shortid
https://github.com/bolorundurowb/shortid
Uses System.Random
string id = ShortId.Generate();
// id = KXTR_VzGVUoOY
(from the github page)
If you want to control the type of id generated by specifying whether you want numbers, special characters and the length, call the Generate method and pass three parameters, the first a boolean stating whether you want numbers, the second a boolean stating whether you want special characters, the last a number indicating your length preference.
string id = ShortId.Generate(true, false, 12);
// id = VvoCDPazES_w
As far as I know, just stripping off a part of a GUID isn't guaranteed to be unique - in fact, it's far from being unique.
The shortest thing that I know that guarantees global uniqueness is featured in this blog post by Jeff Atwood. In the linked post, he discusses multiple ways to shorten a GUID, and in the end gets it down to 20 bytes via Ascii85 encoding.
However, if you absolutely need a solution that's no longer than 15 bytes, I'm afraid you have no other choice than to use something which is not guaranteed to be globally unique.
For my local app I'm using this time based approach:
/// <summary>
/// Returns all ticks, milliseconds or seconds since 1970.
///
/// 1 tick = 100 nanoseconds
///
/// Samples:
///
/// Return unit value decimal length value hex length
/// --------------------------------------------------------------------------
/// ticks 14094017407993061 17 3212786FA068F0 14
/// milliseconds 1409397614940 13 148271D0BC5 11
/// seconds 1409397492 10 5401D2AE 8
///
/// </summary>
public static string TickIdGet(bool getSecondsNotTicks, bool getMillisecondsNotTicks, bool getHexValue)
{
string id = string.Empty;
DateTime historicalDate = new DateTime(1970, 1, 1, 0, 0, 0);
if (getSecondsNotTicks || getMillisecondsNotTicks)
{
TimeSpan spanTillNow = DateTime.UtcNow.Subtract(historicalDate);
if (getSecondsNotTicks)
id = String.Format("{0:0}", spanTillNow.TotalSeconds);
else
id = String.Format("{0:0}", spanTillNow.TotalMilliseconds);
}
else
{
long ticksTillNow = DateTime.UtcNow.Ticks - historicalDate.Ticks;
id = ticksTillNow.ToString();
}
if (getHexValue)
id = long.Parse(id).ToString("X");
return id;
}
IDENTITY values should be unique in a database, but you should be aware of the limitations... for example, it makes bulk data inserts basically impossible which will slow you down if you're working with a very large number of records.
You may also be able to use a date/time value. I've seen several databases where they use the date/time to be the PK, and while it's not super clean - it works. If you control the inserts, you can effectively guarantee that the values will be unique in code.
Just in case merely removing hyphens will do for anyone:
Guid.NewGuid().ToString("n")
This generates perfectly unique strings of 32 characters:
5db4cee3bfd8436395d37fca2d48d5b3
82fac271c76148a3a0667c00a5da990d
here my solution, is not safe for concurrency, no more of 1000 GUID's per seconds and thread safe.
public static class Extensors
{
private static object _lockGuidObject;
public static string GetGuid()
{
if (_lockGuidObject == null)
_lockGuidObject = new object();
lock (_lockGuidObject)
{
Thread.Sleep(1);
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var epochLong = Convert.ToInt64((DateTime.UtcNow - epoch).TotalMilliseconds);
return epochLong.DecimalToArbitrarySystem(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(this 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;
}
code not optimized, just sample!.
If your app dont have a few MILLIION people, using that generate short unique string at the SAME MILLISECOND, you can think about using below function.
private static readonly Object obj = new Object();
private static readonly Random random = new Random();
private string CreateShortUniqueString()
{
string strDate = DateTime.Now.ToString("yyyyMMddhhmmssfff");
string randomString ;
lock (obj)
{
randomString = RandomString(3);
}
return strDate + randomString; // 16 charater
}
private string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxy";
var random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
change yyyy to yy if you just need to use your app in next 99 year.
Update 20160511: Correct Random function
- Add Lock object
- Move random variable out of RandomString function
Ref
Based on some others, here is my solution which provides a different encoded guid which is URL (and Docker) safe and does not loose any information:
Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("=", "").Replace("+", "-").Replace("/", "_");
Example outputs are:
BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig
public static string ToTinyUuid(this Guid guid)
{
return Convert.ToBase64String(guid.ToByteArray())[0..^2] // remove trailing == padding
.Replace('+', '-') // escape (for filepath)
.Replace('/', '_'); // escape (for filepath)
}
Usage
Guid.NewGuid().ToTinyUuid()
It's not rocket science to convert back, so I'll leave you that much.
Here's my small method to generate a random and short unique id. Uses a cryptographic rng for secure random number generation. Add whatever characters you need to the chars string.
using System;
using System.Security.Cryptography;
// ...
private string GenerateRandomId(int length)
{
string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char[] outputChars = new char[length];
using RandomNumberGenerator rng = RandomNumberGenerator.Create();
int minIndex = 0;
int maxIndexExclusive = charset.Length;
int diff = maxIndexExclusive - minIndex;
long upperBound = uint.MaxValue / diff * diff;
byte[] randomBuffer = new byte[sizeof(int)];
for (int i = 0; i < outputChars.Length; i++)
{
// Generate a fair, random number between minIndex and maxIndex
uint randomUInt;
do
{
rng.GetBytes(randomBuffer);
randomUInt = BitConverter.ToUInt32(randomBuffer, 0);
}
while (randomUInt >= upperBound);
int charIndex = (int)(randomUInt % diff);
// Set output character based on random index
outputChars[i] = charset[charIndex];
}
return new string(outputChars);
}
This works by scaling a random integer down to the range of charset indices, and accounts for the edge case where the random number is the absolute upper bound by rerolling for a new integer.
This solution produces fair and evenly distributed output, tested with a 1,000,000 character long output showing no obvious biases:
string output = GenerateRandomId(1_000_000);
var tally = output.GroupBy(c => c).OrderBy(g => g.Key).Select(g => (g.Key, g.Count())).ToArray();
int average = (int)(tally.Aggregate(new BigInteger(0), (b, t) => {b += t.Item2; return b;}, b => b) / tally.Count());
int max = tally.Max(g => g.Item2);
int min = tally.Min(g => g.Item2);
Console.WriteLine($"Avg: {average}");
Console.WriteLine($"Max: {max}");
Console.WriteLine($"Min: {min}");
foreach((char key, int count) in tally) {
Console.WriteLine($"{key}: {count}");
}
Output:
Avg: 27777
Max: 28163
Min: 27341
0: 28081
1: 27773
...
Z: 27725
I know it's quite far from posted date... :)
I have a generator which produces only 9 Hexa characters, eg: C9D6F7FF3, C9D6FB52C
public class SlimHexIdGenerator : IIdGenerator
{
private readonly DateTime _baseDate = new DateTime(2016, 1, 1);
private readonly IDictionary<long, IList<long>> _cache = new Dictionary<long, IList<long>>();
public string NewId()
{
var now = DateTime.Now.ToString("HHmmssfff");
var daysDiff = (DateTime.Today - _baseDate).Days;
var current = long.Parse(string.Format("{0}{1}", daysDiff, now));
return IdGeneratorHelper.NewId(_cache, current);
}
}
static class IdGeneratorHelper
{
public static string NewId(IDictionary<long, IList<long>> cache, long current)
{
if (cache.Any() && cache.Keys.Max() < current)
{
cache.Clear();
}
if (!cache.Any())
{
cache.Add(current, new List<long>());
}
string secondPart;
if (cache[current].Any())
{
var maxValue = cache[current].Max();
cache[current].Add(maxValue + 1);
secondPart = maxValue.ToString(CultureInfo.InvariantCulture);
}
else
{
cache[current].Add(0);
secondPart = string.Empty;
}
var nextValueFormatted = string.Format("{0}{1}", current, secondPart);
return UInt64.Parse(nextValueFormatted).ToString("X");
}
}
22 chars, url safe, and retains Guid uniqueness.
// Our url safe, base 64 alphabet:
const string alphabet = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Sanitized Guid string. Preserve the last two hex chars
var guidStr = "929F7C4D4B2644E1A122A379C02D6345";
var lastTwo = guidStr.Substring(30, 2);
string shortGuid = "";
// Iterate over the ten groups of 3 hex chars: 929 F7C 4D4 B26 44E 1A1 22A 379 C02 D63
for (var i = 0; i < 10; i++)
{
var hex = guidStr.Substring(i*3, 3); // Get the next 3 hex chars
var x = Convert.ToInt32(hex, 16); // Convert to int
shortGuid += $"{alphabet[x/64]}{alphabet[x%64]}"; // Lookup the two-digit base64 value
}
shortGuid += lastTwo; // Don't forget the last two
Console.WriteLine(shortGuid);
Output:
yDXWhiGAfc4v6EbTK0Px45
Based on #dorcohen's answer and #pootzko's comment.
You can use this. It is safe over the wire.
var errorId = System.Web.HttpServerUtility.UrlTokenEncode(Guid.NewGuid().ToByteArray());
In C# a long value has 64 bits, which if encoded with Base64, there will be 12 characters, including 1 padding =. If we trim the padding =, there will be 11 characters.
One crazy idea here is we could use a combination of Unix Epoch and a counter for one epoch value to form a long value. The Unix Epoch in C# DateTimeOffset.ToUnixEpochMilliseconds is in long format, but the first 2 bytes of the 8 bytes are always 0, because otherwise the date time value will be greater than the maximum date time value. So that gives us 2 bytes to place an ushort counter in.
So, in total, as long as the number of ID generation does not exceed 65536 per millisecond, we can have an unique ID:
// This is the counter for current epoch. Counter should reset in next millisecond
ushort currentCounter = 123;
var epoch = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// Because epoch is 64bit long, so we should have 8 bytes
var epochBytes = BitConverter.GetBytes(epoch);
if (BitConverter.IsLittleEndian)
{
// Use big endian
epochBytes = epochBytes.Reverse().ToArray();
}
// The first two bytes are always 0, because if not, the DateTime.UtcNow is greater
// than DateTime.Max, which is not possible
var counterBytes = BitConverter.GetBytes(currentCounter);
if (BitConverter.IsLittleEndian)
{
// Use big endian
counterBytes = counterBytes.Reverse().ToArray();
}
// Copy counter bytes to the first 2 bytes of the epoch bytes
Array.Copy(counterBytes, 0, epochBytes, 0, 2);
// Encode the byte array and trim padding '='
// e.g. AAsBcTCCVlg
var shortUid = Convert.ToBase64String(epochBytes).TrimEnd('=');
If you dont need to type the string you could use the following:
static class GuidConverter
{
public static string GuidToString(Guid g)
{
var bytes = g.ToByteArray();
var sb = new StringBuilder();
for (var j = 0; j < bytes.Length; j++)
{
var c = BitConverter.ToChar(bytes, j);
sb.Append(c);
j++;
}
return sb.ToString();
}
public static Guid StringToGuid(string s)
=> new Guid(s.SelectMany(BitConverter.GetBytes).ToArray());
}
This will convert the Guid to a 8 character String like this:
{b77a49a5-182b-42fa-83a9-824ebd6ab58d} --> "䦥띺ᠫ䋺ꦃ亂檽趵"
{c5f8f7f5-8a7c-4511-b667-8ad36b446617} --> "엸詼䔑架펊䑫ᝦ"
to not lose characters (+ / -) and if you want to use your guid in an url, it must be transformed into base32
for 10 000 000 no duplicate key
public static List<string> guids = new List<string>();
static void Main(string[] args)
{
for (int i = 0; i < 10000000; i++)
{
var guid = Guid.NewGuid();
string encoded = BytesToBase32(guid.ToByteArray());
guids.Add(encoded);
Console.Write(".");
}
var result = guids.GroupBy(x => x)
.Where(group => group.Count() > 1)
.Select(group => group.Key);
foreach (var res in result)
Console.WriteLine($"Duplicate {res}");
Console.WriteLine($"*********** end **************");
Console.ReadLine();
}
public static string BytesToBase32(byte[] bytes)
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
string output = "";
for (int bitIndex = 0; bitIndex < bytes.Length * 8; bitIndex += 5)
{
int dualbyte = bytes[bitIndex / 8] << 8;
if (bitIndex / 8 + 1 < bytes.Length)
dualbyte |= bytes[bitIndex / 8 + 1];
dualbyte = 0x1f & (dualbyte >> (16 - bitIndex % 8 - 5));
output += alphabet[dualbyte];
}
return output;
}
private static readonly object _getUniqueIdLock = new object();
public static string GetUniqueId()
{
lock(_getUniqueIdLock)
{
System.Threading.Thread.Sleep(1);
return DateTime.UtcNow.Ticks.ToString("X");
}
}
you can use
code = await UserManager.GenerateChangePhoneNumberTokenAsync(input.UserId, input.MobileNumber);
its 6 nice characters only, 599527 ,143354
and when user virify it simply
var result = await UserManager.VerifyChangePhoneNumberTokenAsync(input.UserId, input.Token, input.MobileNumber);
hope this help you
Guid.NewGuid().ToString().Split('-').First()

Combinations.... Cartesian Product? [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 11 years ago.
I want to generate folders using combinations of given characters
i.e. 8 character text with combination of characters: abcdefghijklmnopqrstuvwxyz1234567890.
For the 8 characters combinations there are 2821109907456 possibilities. I want to group these by range of 10,000.
I need to put these folders in relevant range folders i.e.
'aaaaaaa1 - aaaaaaa9' is a range of 9 combinations and a folder 'aaaaaaa3' will be created in this range folder.
I want to use c# code, give my method a folder name i.e. 'aaaaaaa3' and be returned the relevant folder range i.e. 'aaaaaa1 - aaaaaaa9' where it should be saved.
Question: I need c# code to do this!
You are actually using 36-base notation (36 digits are used to represent numbers).
So the easiest way to handle these filenames is to convert them to decimal notation and then just divide by 10000.
Something like this:
string alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
string fileName = "asdjg66";
long result = 0;
foreach (var c in fileName)
{
sum = sum * 36 + alphabet.IndexOf(c);
}
And use sum to determine the target range. Just convert sum / 10000 back to 36-base notation. And you are done.
From the beginning, it looks like we're going to need to compute ranges of alphanumeric sequences, which means converting them to numbers and back. An all-purpose base converter seems like the first logical step:
/// <summary>
/// Provides conversion between long integers and custom number bases.
/// </summary>
public class BaseConverter
{
private string _characterSet;
/// <summary>
/// Creates a new BaseConverter.
/// </summary>
/// <param name="characterSet">The characters in the custom base, in
/// increasing order of value.</param>
public BaseConverter(string characterSet =
"0123456789abcdefghijklmnopqrstuvwxyz")
{
_characterSet = characterSet;
}
/// <summary>
/// Converts a number in the custom base system to a long.
/// </summary>
/// <param name="value">The custom base number to convert.</param>
/// <returns>The long form of the custom base number.</returns>
public long StringToLong(string value)
{
if (value == Convert.ToString(_characterSet[0])) return 0;
long val = 0;
string text = value[0] == '-' ? value.Substring(1,
value.Length - 1) : value;
for (int i = text.Length, power = 0; i != 0; i--, power++)
{
val += (long)Math.Round((_characterSet.IndexOf(text[i-1]) *
Math.Pow(_characterSet.Length, power)));
}
return value[0] == '-' ? -val : val;
}
/// <summary>
/// Converts a long to the custom base system.
/// </summary>
/// <param name="value">The long to convert.</param>
/// <returns>The custome base number version of the long.</returns>
public string LongToString(long value)
{
if (value == 0) return Convert.ToString(_characterSet[0]);
long number = value.Abs();
int remainder;
StringBuilder text = new StringBuilder((int)Math.Round(
Math.Log(long.MaxValue, (double)_characterSet.Length)) +
value < 0 ? 1 : 0);
while (number != 0)
{
remainder = (int)(number % _characterSet.Length);
text.Insert(0, _characterSet[remainder]);
number -= remainder;
number /= _characterSet.Length;
}
if (value < 0) text.Insert(0, "-");
return text.ToString();
}
Then, you'll need the code to compute your ranges:
///<summary>
///Computes numeric ranges using a BaseConverter.
///</summary>
public class NumericRangeFactory
{
private long _min, _length;
private BaseConverter _converter;
//creates a new NumericRangeFactory
//converter - the BaseConverter that defines the number system
//being used
//min - the smallest value in an acceptable range
//length - the number of values in a single range
public NumericRangeFactory(BaseConverter converter, long min,
long length)
{
_converter = converter; _min = min; _length = length;
}
public NumericRangeFactory(BaseConverter converter, string min,
long length) : this(converter.StringToLong(min), length) {}
//returns an array of long containing the min and max of the
//range that contains value
public long[] GetLongRange(long value)
{
long min = _length * (value / _length); //todo: fix non-zero _min case
return new long[] { min, min + length - 1 };
}
public long[] GetLongRange(string value)
{
return GetLongRange(_converter.StringToLong(value));
}
//returns an array of string containing the min and max of
//the range that contains value
public string[] GetStringRange(long value)
{
long[] range = GetLongRange(value);
return new string[] {_converter.LongToString(range[0]),
_converter.LongToString(range[1])};
}
public string[] GetStringRange(string value)
{
return GetStringRange(_converter.StringToLong(value));
}
}
Finally, tie the BaseConverter and NumericRangeFactory classes together to solve the problem with this sample static method:
public static string GetFolderRange(string folderName)
{
BaseConverter converter = new BaseConverter();
NumericRangeFactory rangeFactory = new NumericRangeFactory(converter,
"aaaaaaa0", 9);
string[] range = rangeFactory.GetStringRange(folderName);
return range[0] + "-" + range[1];
}
I haven't tested this, but I think the concept is solid.
string getRange(string fileName) {
string prefix = fileName.Substring(0, 7);
string start = "a";
string end = "0";
return prefix + start + " - " + prefix + end;
}
To make the range larger, most easily in powers of 36, make the prefix shorter and make the start and end repeat themselves a few times: (0, 6)..."aa"..."00". To make the range shorter, do something like this:
const string values = "abcdefghijklmnopqrstuvwxyz1234567890";
Then:
int index = values.IndexOf(fileName[7]);
if (index < 12) {
start = "a";
end = "l";
} else if (index < 24) ...

Categories