Determine a numeric range that may contain alpha and numeric characters - c#

I am not even sure how to phrase this question so I apologize in advance. I have a form used by our QA. it requires input of serial numbers - a lot of them (sometimes hundreds). I have two text boxes on the form for lower and upper numbers in the range (doesn't have to be this way but it is my best guess). I know how to to do this if it were just integers (see code below) but that is not the only format.Examples of the format could include a date code ("170508/1234") or could include alpha characters (ABC1234). there is a wide variety of formats but in every case, I want to find the range from the last set of numbers (typically last four digits - Like "170508/1234 170508/1235... and ABC1234 ABC1235 ... not to exclude 1234 1235 ....).
Thank you in advance
private void btnSN_Click(object sender, EventArgs e)
{
int from = Convert.ToInt32(txtSnStart.Text.Trim());
int to = Convert.ToInt32(txtSnEnd.Text.Trim());
for (int i = from; i <= to; i++)
{
txtSN.Text += i.ToString() +" ";
}

You can make use of RexEx for this, the RegEx expression below should match any number of digits at the end.:
(\d+)$
Here is the modified code:
// find the group of digits at end of entered text
var fromMatch = Regex.Match(txtSnStart.Text.Trim(), #"(\d+)$");
int from = Convert.ToInt32(fromMatch.Groups[1].Value);
// strip the matched digit group from entered text to get the prefix
string prefix = txtSnStart.Text.Trim().SubString(0, txtSnStart.Text.Trim().LastIndexOf(from.ToString()));
var toMatch = Regex.Match(txtSnEnd.Text.Trim(), #"(\d+)$");
int to = Convert.ToInt32(toMatch.Groups[1].Value);
for (int i = from; i <= to; i++)
{
// combine the prefix and range value
txtSN.Text += string.Format("{0}{1} ", prefix, i.ToString());
}

This problem becomes easier if you break it into smaller pieces.
This will become especially important once it becomes apparent that one small part of the problem might become more complex.
The inputs are a prefix, a number to begin the range, and a number to end the range. Given that, here's a class with a function to return those strings:
public class StringRangeCreator
{
public IEnumerable<string> CreateStringRange(
string prefix, int rangeStart, int rangeEnd)
{
if (rangeStart > rangeEnd) throw new ArgumentException(
$"{nameof(rangeStart)} cannot be greater than {nameof(rangeEnd)}");
return Enumerable.Range(rangeStart, (rangeEnd-rangeStart) + 1)
.Select(n => prefix + n);
}
}
Enumerable.Range creates a range of numbers. The Select takes that range and returns a set of strings consisting of the prefix concatenated with the number in the range.
And a unit test to make sure it works:
[TestClass]
public class DetermineRangeFromLastFourCharacters
{
[TestMethod]
public void ReturnsExpectedRangeOfStrings()
{
var result = new StringRangeCreator().CreateStringRange("abc", 1, 10).ToList();
Assert.AreEqual(10, result.Count);
Assert.AreEqual("abc1", result[0]);
Assert.AreEqual("abc10", result[9]);
}
}
But if these are serial numbers then you maybe you don't want ABC8, ABC9, ABC10. You might want ABC08, ABC09, ABC10. All the same length.
So here's the modified class. I'm guessing a little at what the expected behavior might be. It lets you specify an optional minimum number of digits so that you can pad accordingly:
public class StringRangeCreator
{
public IEnumerable<string> CreateStringRange(
string prefix, int rangeStart, int rangeEnd, int minimumDigits = 1)
{
if (rangeStart > rangeEnd) throw new ArgumentException(
$"{nameof(rangeStart)} cannot be greater than {nameof(rangeEnd)}");
return Enumerable.Range(rangeStart, (rangeEnd-rangeStart) + 1)
.Select(n => prefix + n.ToString("0").PadLeft(minimumDigits, '0'));
}
}
And another unit test:
[TestMethod]
public void PadsNumbersAccordingToParameter()
{
var result = new StringRangeCreator().CreateStringRange("abc", 999, 1001, 3).ToList();
Assert.AreEqual(3, result.Count);
Assert.AreEqual("abc999", result[0]);
Assert.AreEqual("abc1001", result[2]);
}
Some other scenarios you might want to account for are negative numbers or extremely large ranges.
Now that the problem of creating the result set is separated into a class with simple inputs and outputs that you can test, what remains is to take the input from the form and break it down into these inputs. That might include making sure that both strings have the same prefixes and making sure that the last parts of the strings are numbers.
But the whole thing will be a little simpler once it's not all in one big method. That also makes it easier to change when you realize something different about how you want it to work. Validating your input can be one step, and then getting the results can be another.
The unit tests help because you don't want to have to debug the whole thing to see if it works. Enumerable.Range didn't work the way I thought it did, so I had to fix a bug. It was easy to find and fix with the unit tests. It would have been harder if I had to run a whole app and then step through it in the debugger.

Related

For loop that doesn't iterate completely but displays no error message

I have a simple program that uses Random and a for loop to generate two six-digit numbers. The loop generates two numbers every time and most of the time both numbers are six digits. Sometimes, though, one of those numbers is less than 6 digits (4 to 5 digits). It generates no error message in all cases. What's going on? I've posted all the code although some of it is unused/tested until I can figure out the Random problem.
The six-digit number starts as an empty string. Random generates an integer, the integer gets converted to string then += onto six-digit number string, which is converted to an integer when the method finishes.
The rest of the program is unimplemented and will involve extracting the 2nd, 4th, and 6th place digits in each number and adding them all together. The troubling thing is the Random issue.
static void Main(string[] args)
{
Numbers theNumbers = new Numbers();
Console.WriteLine(string.Join(",", theNumbers.Ints[0]));
Console.WriteLine(string.Join(",", theNumbers.Ints[1]));
}
Numbers class
class Numbers
{
public int[] Ints { get; set; }
public Numbers()
{
Ints = new int[5];
CreateTheFirstTwoNumbers(new RandomNummberGenerator());
}
public int[] CreateTheFirstTwoNumbers(RandomNummberGenerator RNG)
{
for (int arrayIndex = 0; arrayIndex < 2; arrayIndex++)
{
Ints[arrayIndex] = RNG.CreateRandomNumber();
}
return Ints;
}
Random Number Generator class
class RandomNummberGenerator
{
public Random Generator { get; set; }
public RandomNummberGenerator()
{
Generator = new Random();
}
public int CreateRandomNumber()
{
string number = "";
for (int numberIndex = 0; numberIndex < 6; numberIndex++)
{
number += Generator.Next(0, 10).ToString();
}
return int.Parse(number);
}
}
There's a much easier (more correct, and certainly requiring less code) solution to generating a random number, and converting it to a 6-character string
var rng = new Random();
var number = rng.Next(1000000); // generates a number between 0 and 999999
// (maxValue is exclusive upper bound)
var numberAsString = number.ToString("000000");
If the number can not start with leading zeroes, generate a random number between 100000 and 999999:
var number = rng.Next(100000, 1000000);
You're generating a six-character string where each character is a digit from 0-9, inclusive, and then parsing that string as an integer. If the first character is '0', then you'll have a number with five digits (or fewer if there are more leading zeroes). If you want to guarantee a six-digit number, then your first digit should be 1-9 instead of 0-9.
If you haven't seen it already, I'd recommend reading Eric Lippert's blog post on how to debug small programs.
You won't get an error message unless you made a syntax mistake. Most likely you have a semantic error. If I had to guess, I would say your first digit is occasionally be generated as a zero.
The problem is when you convert the generated number to int .
Your CreateRandom() function sometimes create numbers started by 0(zero) so when you convert to a numeric type as int, float and etc.. the zero on the left is ignored.
So, if you generate 012345 when you convert to int you will get 12345, because the 0 on the left is supressed.
If you want a six digit number, be sure the first digit its not 0.
Your loop is actually working. You get numbers with less digits when the leading digits are 0.
You can force the display of six digits by using the following formatting
string s = number.ToString("000000");
E.g., with 123 the result is "000123".
Output with:
string s = String.Join(", ", theNumbers.Ints.Select(i => i.ToString("000000")).ToArray());
Console.WriteLine(s);

Unity formatting multiple numbers

So I'm a complete newb to unity and c# and I'm trying to make my first mobile incremental game. I know how to format a variable from (e.g.) 1000 >>> 1k however I have several variables that can go up to decillion+ so I imagine having to check every variable's value seperately up to decillion+ will be quite inefficient. Being a newb I'm not sure how to go about it, maybe a for loop or something?
EDIT: I'm checking if x is greater than a certain value. For example if it's greater than 1,000, display 1k. If it's greater than 1,000,000, display 1m...etc etc
This is my current code for checking if x is greater than 1000 however I don't think copy pasting this against other values would be very efficient;
if (totalCash > 1000)
{
totalCashk = totalCash / 1000;
totalCashTxt.text = "$" + totalCashk.ToString("F1") + "k";
}
So, I agree that copying code is not efficient. That's why people invented functions!
How about simply wrapping your formatting into function, eg. named prettyCurrency?
So you can simply write:
totalCashTxt.text = prettyCurrency(totalCashk);
Also, instead of writing ton of ifs you can handle this case with logarithm with base of 10 to determine number of digits. Example in pure C# below:
using System.IO;
using System;
class Program
{
// Very simple example, gonna throw exception for numbers bigger than 10^12
static readonly string[] suffixes = {"", "k", "M", "G"};
static string prettyCurrency(long cash, string prefix="$")
{
int k;
if(cash == 0)
k = 0; // log10 of 0 is not valid
else
k = (int)(Math.Log10(cash) / 3); // get number of digits and divide by 3
var dividor = Math.Pow(10,k*3); // actual number we print
var text = prefix + (cash/dividor).ToString("F1") + suffixes[k];
return text;
}
static void Main()
{
Console.WriteLine(prettyCurrency(0));
Console.WriteLine(prettyCurrency(333));
Console.WriteLine(prettyCurrency(3145));
Console.WriteLine(prettyCurrency(314512455));
Console.WriteLine(prettyCurrency(31451242545));
}
}
OUTPUT:
$0.0
$333.0
$3.1k
$314.5M
$31.5G
Also, you might think about introducing a new type, which implements this function as its ToString() overload.
EDIT:
I forgot about 0 in input, now it is fixed. And indeed, as #Draco18s said in his comment nor int nor long will handle really big numbers, so you can either use external library like BigInteger or switch to double which will lose his precision when numbers becomes bigger and bigger. (e.g. 1000000000000000.0 + 1 might be equal to 1000000000000000.0). If you choose the latter you should change my function to handle numbers in range (0.0,1.0), for which log10 is negative.

Count Zeroes before the Decimal Point

I am trying to count how many zeroes are a before a decimal.
private void textBox1_TextChanged(object sender, EventArgs e)
{
decimal x = 0;
if (Decimal.TryParse(textBox1.Text, out x))
{
var y = 1000000;
var answer = x * y;
displayLabel2.Text = (x.ToString().Replace(".", "").TrimStart(new Char[] { '0' }) + "00").Substring(0, 2);
}
else
{
displayLabel2.Text = "error";
}
}
When I plug in (lets say) 7.2 I get an output that displays 72, which is what I want. Now I need another display. That initial 7.2 is being multiplied by 1000000. So the quotent of that would be 7,200,000.00. Now I need to some how count the 5 zeroes before the decimal point and display 5 for that. Then if I were to do .72. My Quotent would be 720,000.00. And I would need to display 4, for the 4 zeroes. And so on. Then I need to output that number to displayLabel5.Text
Here's a one line Linq you could try to count zeroes before the decimal. You can Split() first by the decimal then perform a Where().Count() to get the number of zeros.
using System;
using System.Linq;
public class Program
{
public static void Main()
{
string myString = (720000.00).ToString();
Console.WriteLine(myString.Split('.')[0].Where(d => d == '0').Count());
}
}
Results:
4
Demo
Quick and dirty code so be careful, but AFAIK this is the fastest way to do it.
// Input assuming you've sanitised it
string myInputString = "720000.00";
// Remove the decimals
myInputString = myInputString.Substring(0, myInputString.IndexOf("."));
// The count
int count = 0;
// Loop through and count occurrences
foreach (char c in myInputString)
{
if (c == "0")
{
count++;
}
}
Count is now 4.
Guarantee you this is faster than Regex ;-)
Edit: Sorry for the multiple edits, it's been a long day. Need coffee.
use a regular expression to find all the zeros before the period, then get the string length of that match.
Regex regex = new Regex(#"(0+)\.?");
string value1 = "7,200,000.00";
value1 = value1.Replace(",",""); //get rid of the commas
Match match = regex.Match(value1);
if (match.Success)
{
Console.WriteLine(match.Value.Length);
}
As always test the code because I wrote it just now here in this little text box and not in actual visual studio where I could compile and test it myself. But this should at least illustrate the methodology.
Edit:
slight tweak to the regex to account for the possibility that the number will not display a decimal point at all.

Constantly Incrementing String

So, what I'm trying to do this something like this: (example)
a,b,c,d.. etc. aa,ab,ac.. etc. ba,bb,bc, etc.
So, this can essentially be explained as generally increasing and just printing all possible variations, starting at a. So far, I've been able to do it with one letter, starting out like this:
for (int i = 97; i <= 122; i++)
{
item = (char)i
}
But, I'm unable to eventually add the second letter, third letter, and so forth. Is anyone able to provide input? Thanks.
Since there hasn't been a solution so far that would literally "increment a string", here is one that does:
static string Increment(string s) {
if (s.All(c => c == 'z')) {
return new string('a', s.Length + 1);
}
var res = s.ToCharArray();
var pos = res.Length - 1;
do {
if (res[pos] != 'z') {
res[pos]++;
break;
}
res[pos--] = 'a';
} while (true);
return new string(res);
}
The idea is simple: pretend that letters are your digits, and do an increment the way they teach in an elementary school. Start from the rightmost "digit", and increment it. If you hit a nine (which is 'z' in our system), move on to the prior digit; otherwise, you are done incrementing.
The obvious special case is when the "number" is composed entirely of nines. This is when your "counter" needs to roll to the next size up, and add a "digit". This special condition is checked at the beginning of the method: if the string is composed of N letters 'z', a string of N+1 letter 'a's is returned.
Here is a link to a quick demonstration of this code on ideone.
Each iteration of Your for loop is completely
overwriting what is in "item" - the for loop is just assigning one character "i" at a time
If item is a String, Use something like this:
item = "";
for (int i = 97; i <= 122; i++)
{
item += (char)i;
}
something to the affect of
public string IncrementString(string value)
{
if (string.IsNullOrEmpty(value)) return "a";
var chars = value.ToArray();
var last = chars.Last();
if(char.ToByte() == 122)
return value + "a";
return value.SubString(0, value.Length) + (char)(char.ToByte()+1);
}
you'll probably need to convert the char to a byte. That can be encapsulated in an extension method like static int ToByte(this char);
StringBuilder is a better choice when building large amounts of strings. so you may want to consider using that instead of string concatenation.
Another way to look at this is that you want to count in base 26. The computer is very good at counting and since it always has to convert from base 2 (binary), which is the way it stores values, to base 10 (decimal--the number system you and I generally think in), converting to different number bases is also very easy.
There's a general base converter here https://stackoverflow.com/a/3265796/351385 which converts an array of bytes to an arbitrary base. Once you have a good understanding of number bases and can understand that code, it's a simple matter to create a base 26 counter that counts in binary, but converts to base 26 for display.

Is there a simple method of converting an ordinal numeric string to its matching numeric value?

Does anyone know of a method to convert words like "first", "tenth" and "one hundredth" to their numeric equivalent?
Samples:
"first" -> 1,
"second" -> 2,
"tenth" -> 10,
"hundredth" -> 100
Any algorithm will suffice but I'm writing this in C#.
EDIT
It ain't pretty and only works with one word at a time but it suits my purposes. Maybe someone can improve it but I'm out of time.
public static int GetNumberFromOrdinalString(string inputString)
{
string[] ordinalNumberWords = { "", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth" };
string[] ordinalNumberWordsTens = { "", "tenth", "twentieth", "thirtieth", "fortieth", "fiftieth", "sixtieth", "seventieth", "eightieth", "ninetieth" };
string[] ordinalNumberWordsExtended = {"hundredth", "thousandth", "millionth", "billionth" };
if (inputString.IsNullOrEmpty() || inputString.Length < 5 || inputString.Contains(" ")) return 0;
if (ordinalNumberWords.Contains(inputString) || ordinalNumberWordsTens.Contains(inputString))
{
var outputMultiplier = ordinalNumberWords.Contains(inputString) ? 1 : 10;
var arrayToCheck = ordinalNumberWords.Contains(inputString) ? ordinalNumberWords : ordinalNumberWordsTens;
// Use the loop counter to get our output integer.
for (int x = 0; x < arrayToCheck.Count(); x++)
{
if (arrayToCheck[x] == inputString)
{
return x * outputMultiplier;
}
}
}
// Check if the number is one of our extended numbers and return the appropriate value.
if (ordinalNumberWordsExtended.Contains(inputString))
{
return inputString == ordinalNumberWordsExtended[0] ? 100 : inputString == ordinalNumberWordsExtended[1] ? 1000 : inputString == ordinalNumberWordsExtended[2] ? 1000000 : 1000000000;
}
return 0;
}
I've never given this much thought beyond I know the word "and" is supposed to be the transition from whole numbers to decimals. Like
One Hundred Ninety-Nine Dollars and Ten Cents
not
One Hundred and Ninety-Nine Dollars.
Anyways any potential solution would have to parse the input string, raise any exceptions or otherwise return the value.
But first you'd have to know "the rules" This seems to be very arbitrary and based on tradition but this gentleman seems as good a place as any to start:
Ask Dr. Math
I think you might end up having to map strings to values up to the maximum range you expect and then parse the string in order and place values as such. Since there's very little regular naming convention across and within order of magnitude, I don't think there's an elegant or easy way to parse a word to get its numeric value. Luckily, depending on the format, you probably only have to map every order of magnitude. For example, if you only expect numbers 0-100 and they are inputted as "ninety-nine" then you only need to map 0-9, then 10-100 in steps of 10.

Categories