JimiJ13
asked on
Populate Serial Number range
Dear Experts,
We are always receiving bulk merchandise items with Serial Numbers (SN) per SKU. What I want is to populate the SN on the details of every SKU receiving page.
Now, this is my question: Normally, SN is a long code with around 4 numerical digit at the end in sequential order. How can I strip the 4 numerical digits at the end and sequence them accordingly.
Example: From StartingSN XXXXXXXXXX3625 to EndingSN XXXXXXXXXX3666.
We are always receiving bulk merchandise items with Serial Numbers (SN) per SKU. What I want is to populate the SN on the details of every SKU receiving page.
Now, this is my question: Normally, SN is a long code with around 4 numerical digit at the end in sequential order. How can I strip the 4 numerical digits at the end and sequence them accordingly.
Example: From StartingSN XXXXXXXXXX3625 to EndingSN XXXXXXXXXX3666.
Why can't you just sort them? Or are the leading ten characters more or less random? Like:
JSK4W90SDA3625
to:
JS67ED34K83666
If so, you can list the items and sort them by the last four digits:
sortNumber = sku.Substring(10)
/gustav
JSK4W90SDA3625
to:
JS67ED34K83666
If so, you can list the items and sort them by the last four digits:
sortNumber = sku.Substring(10)
/gustav
ASKER
I think I need more explanation:
For example, I have a merchandise with SN from JSK4W90SDA3625 to JSK4W90SDA3635 and when populated it become as follows:
JSK4W90SDA3625
JSK4W90SDA3626
JSK4W90SDA3627
JSK4W90SDA3628
JSK4W90SDA3629
JSK4W90SDA3630
JSK4W90SDA3631
JSK4W90SDA3632
JSK4W90SDA3633
JSK4W90SDA3634
JSK4W90SDA3635
No more no less. However, I don't to do this manually but rather semi-automatically based on the range.
Thanks.
For example, I have a merchandise with SN from JSK4W90SDA3625 to JSK4W90SDA3635 and when populated it become as follows:
JSK4W90SDA3625
JSK4W90SDA3626
JSK4W90SDA3627
JSK4W90SDA3628
JSK4W90SDA3629
JSK4W90SDA3630
JSK4W90SDA3631
JSK4W90SDA3632
JSK4W90SDA3633
JSK4W90SDA3634
JSK4W90SDA3635
No more no less. However, I don't to do this manually but rather semi-automatically based on the range.
Thanks.
ASKER
The sequencing will always be based on the last 4 digits.
You can use Linq and Range for this:
string firstSku = "JSK4W90SDA3625";
string lastSku = "JSK4W90SDA3635";
const int skuLength = 10;
string baseSn = firstSku.Substring(0, skuLength);
int firstSn = int.Parse(firstSku.Substring(skuLength));
int lastSn = int.Parse(lastSku.Substring(skuLength));
List<string> sns = new List<string>();
sns = Enumerable.Range(firstSn, lastSn - firstSn + 1).Select(x => baseSn + x.ToString()).ToList();
/gustav
ASKER
Hi Gustav,
Your suggestion looks ok. However, I would suggest that we must focus on the last 4 digits, not on the 1st 10 since, the total digits is not always 14. It could be 16 or even 20.
Thanks.
Your suggestion looks ok. However, I would suggest that we must focus on the last 4 digits, not on the 1st 10 since, the total digits is not always 14. It could be 16 or even 20.
Thanks.
And you couldn't figure that out yourself?
string firstSku = "JSK4W90SDA3625";
string lastSku = "JSK4W90SDA3635";
const int snLength = 4;
int skuLength = firstSku.Length - snLength;
string baseSn = firstSku.Substring(0, skuLength);
int firstSn = int.Parse(firstSku.Substring(skuLength));
int lastSn = int.Parse(lastSku.Substring(skuLength));
List<string> sns = new List<string>();
sns = Enumerable.Range(firstSn, lastSn - firstSn + 1).Select(x => baseSn + x.ToString()).ToList();
/gustav
ASKER
Thanks Gustav!
Figuring this out myself, may take sometime. It's better to do something productive and leave what is for the experts like you.
I will get back to you when I tested well your solution.
Thanks.
Figuring this out myself, may take sometime. It's better to do something productive and leave what is for the experts like you.
I will get back to you when I tested well your solution.
Thanks.
You are welcome!
/gustav
/gustav
ASKER
Hi Gustav,
What I got is 11 count which is correct. However, this is just the 1st step of what I want.
Can you help me form the SNs into an Array like (JSK4W90SDA3625;JSK4W90SDA 3626;JSK4W 90SDA3627; ...)
My original idea is to populate them into a table but certainly this can help.
Thanks.
What I got is 11 count which is correct. However, this is just the 1st step of what I want.
Can you help me form the SNs into an Array like (JSK4W90SDA3625;JSK4W90SDA
My original idea is to populate them into a table but certainly this can help.
Thanks.
That could be done directly this way:
string[] allSns = Enumerable.Range(firstSn, lastSn - firstSn + 1).Select(x => baseSn + x.ToString()).ToArray();
Or you could reuse the list from above:string[] allSns = sns.ToArray();
Or to create your separated string:string allSns = string.Join(";",sns.ToArray());
/gustav
ASKER
Excellent! I like the last one - separated string creation.
Now, this is the last thing I need to do based on the list you have created. Will foreach like a shown below an appropriate efficient solution?
foreach (string NewSN in sns)
{
// call insert into the database routine
}
Thanks so much!
Now, this is the last thing I need to do based on the list you have created. Will foreach like a shown below an appropriate efficient solution?
foreach (string NewSN in sns)
{
// call insert into the database routine
}
Thanks so much!
You could also implement a SerialNumber class with a comparer; e.g. -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace EE_Q28848919
{
class Program
{
static readonly List<SerialNumber> unordered = new List<SerialNumber>()
{
new SerialNumber("JSK4W90SDA3629"),
new SerialNumber("JSK4W90SDA3635"),
new SerialNumber("JSK4W90SDA3628"),
new SerialNumber("JSK4W90SDA3634"),
new SerialNumber("JSK4W90SDA3627"),
new SerialNumber("JSK4W90SDA3633"),
new SerialNumber("JSK4W90SDA3626"),
new SerialNumber("JSK4W90SDA3632"),
new SerialNumber("JSK4W90SDA3625"),
new SerialNumber("JSK4W90SDA3631"),
new SerialNumber("JSK4W90SDA3630")
};
static void Main(string[] args)
{
Console.WriteLine("Unordered:");
foreach (SerialNumber item in unordered)
Console.WriteLine(item);
Console.WriteLine();
Console.WriteLine("Ordered:");
foreach (SerialNumber item in (from sn in unordered orderby sn select sn))
Console.WriteLine(item);
Console.WriteLine();
unordered.Sort();
Console.WriteLine("Comma-separated Array: {{{0}}}", String.Join(",", Array.ConvertAll(unordered.ToArray(), x => x.ToString())));
Console.ReadLine();
}
}
class SerialNumber : IComparable, ICloneable, IComparable<SerialNumber>, IComparer<SerialNumber>, IEquatable<SerialNumber>
{
public static readonly SerialNumber Empty;
public string Value { get; set; }
public SerialNumber()
{
Value = String.Empty;
}
public SerialNumber(string value)
{
Value = value;
}
public SerialNumber(SerialNumber source)
{
Value = source.Value;
}
public int CompareTo(object obj)
{
return CompareTo(obj as SerialNumber);
}
public object Clone()
{
return new SerialNumber(this);
}
public int CompareTo(SerialNumber other)
{
return Compare(this, other);
}
public override bool Equals(object obj)
{
if (this == null)
throw new NullReferenceException();
if (obj == null || (obj.GetType() != GetType()))
return false;
return Equals(obj as SerialNumber);
}
public bool Equals(SerialNumber other)
{
if (Object.ReferenceEquals(this, null))
throw new NullReferenceException();
if (Object.ReferenceEquals(other, null))
return false;
if (Object.ReferenceEquals(other, this))
return true;
return Object.Equals(Value, other.Value);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public override string ToString()
{
return Value;
}
public static bool operator ==(SerialNumber lhs, SerialNumber rhs)
{
return Object.Equals(lhs, rhs);
}
public static bool operator !=(SerialNumber lhs, SerialNumber rhs)
{
return lhs != rhs;
}
public int Compare(SerialNumber x, SerialNumber y)
{
string[] x1, y1;
if (x == y)
return 0;
x1 = Regex.Split(x.ToString(), @"(\d{4}(?!\d)+)");
y1 = Regex.Split(y.ToString(), @"(\d{4}(?!\d)+)");
for (int i = x1.Length > y1.Length ? y1.Length - 1 : x1.Length - 1; i >= 0; i--)
{
if (x1[i] != y1[i])
return Compare(x1[i], y1[i]);
}
if (y1.Length > x1.Length)
return 1;
else if (x1.Length > y1.Length)
return -1;
else
return 0;
}
private static int Compare(string left, string right)
{
int x, y;
if (!int.TryParse(left, out x) || !int.TryParse(right, out y))
return left.CompareTo(right);
return x.CompareTo(y);
}
}
}
Which produces the following output --saige-
Nevermind my comment, I was wondering why Gustav was making his recommendation. You are not trying to compare, rather, you want to build a sequence from an initial entry based upon a range. We could probably do the same with a regular expression that way we wont have to do so much string manipulation. Let me throw something together for that... *I feel like a heel* :)
-saige-
-saige-
So here is my submission for obtaining a range of SKU's based on the last four (I took the time to pad in the case of leading zeros in the last 4 since integers do not start with 0's):
-saige-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace EE_Q28848919
{
class Program
{
static void Main(string[] args)
{
var skus = GetSkus("SNBLPRAWQMDTK500022", 20);
foreach (string sn in skus)
Console.WriteLine(sn);
Console.ReadLine();
}
static IEnumerable<string> GetSkus(string source, int range)
{
List<string> results = new List<string>();
if (source != null)
{
string[] parts = Regex.Split(source, @"(\d{4}(?!\d)+)");
int part;
if (parts.Count() > 1 && int.TryParse(parts[1], out part))
results.AddRange(from i in Enumerable.Range(part, range) select parts[0] + new string('0', ZeroPadder(parts[1])) + i);
}
return results;
}
static int ZeroPadder(string source)
{
for (int i = 0; i < source.Length - 1; i++)
{
if (source[i] != '0')
return i;
}
return 0;
}
}
}
Which produces the following output -From this you could use Gustav's suggestion on building the array.-saige-
ASKER
-saige-,
Thanks for your submission. I realized that Gustav solution does not work with SN ending with 00##.
When I tried further your solution, it can not handle SN ending with 000# as well. More so with 0000 ending.
Do you have a fix on these conditions?
Thanks.
Thanks for your submission. I realized that Gustav solution does not work with SN ending with 00##.
When I tried further your solution, it can not handle SN ending with 000# as well. More so with 0000 ending.
Do you have a fix on these conditions?
Thanks.
You mean ending in all zeros... I admit I did not consider that case. Should be simple to check for though. Give me a few, I will through together a solution when I am back in front of my computer.
-saige-
-saige-
ASKER
-saige-,
Yes, ending in all zeros.
And here's another thing we need to consider as I have seen some SN with the format as follow:
var skus = GetSkus("50014EE20154", 10);
Results: 50014;50015;50016;50017;50 018;50019; 50020;5002 1;50022;50 023
The Regex may require modification to consider "Alphas" in the middle.
Thanks.
Yes, ending in all zeros.
And here's another thing we need to consider as I have seen some SN with the format as follow:
var skus = GetSkus("50014EE20154", 10);
Results: 50014;50015;50016;50017;50
The Regex may require modification to consider "Alphas" in the middle.
Thanks.
See if this now matches your criteria:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace EE_Q28848919
{
class Program
{
static void Main(string[] args)
{
foreach (string sn in GetSkus("SNOIHOUHBNDB0000", 10))
Console.WriteLine(sn);
Console.WriteLine();
foreach (string sn in GetSkus("0000SNOIHOUHBNDB", 10, 4, MatchLocation.Front))
Console.WriteLine(sn);
Console.ReadLine();
}
static IEnumerable<string> GetSkus(string source, int range, int length = 4, MatchLocation location = MatchLocation.Back)
{
List<string> results = new List<string>();
if (source != null)
{
string pattern = string.Format(@"(\d{{{0}}}(?!\d)+)", length);
List<string> parts = Regex.Split(source, pattern).Where(s => !string.IsNullOrEmpty(s)).ToList();
int part;
if (parts.Count() > 1)
{
switch (location)
{
case MatchLocation.Front:
{
if (int.TryParse(parts.First(), out part))
{
results.AddRange(from i in Enumerable.Range(part, range) select string.Format("{0}{1}", i.ToString("D" + ZeroPadder(parts.First())), string.Join("", parts.Where(x => !x.Equals(parts.First())).ToArray())));
}
}
break;
case MatchLocation.Back:
default:
{
if (int.TryParse(parts.Last(), out part))
{
results.AddRange(from i in Enumerable.Range(part, range) select string.Format("{0}{1}", string.Join("", parts.Where(x => !x.Equals(parts.Last())).ToArray()), i.ToString("D" + ZeroPadder(parts.Last()))));
}
}
break;
}
}
}
return results;
}
static int ZeroPadder(string source)
{
for (int i = 0; i < source.Length; i++)
{
if (source[i] != '0')
return i;
}
return source.Length;
}
}
enum MatchLocation : int
{
Front = 0,
Back = 1
}
}
Which produces the following output --saige-
ASKER
saige,
Great job! In dealing with all zeroes, it's perfect.
However, I am sorry for a confusion. These results were the output of your previous method and were all wrong:
var skus = GetSkus("50014EE20154", 10);
Results: 50014;50015;50016;50017;50 018;50019; 50020;5002 1;50022;50 023
The correct output would be:
(50014EE20154;50014EE20155 ;50014EE20 156;50014E E20157;500 14EE20158; 50014EE201 59;50014EE 20160;5001 4EE20161;5 0014EE2016 2;50014EE2 0163)
Many thanks!
Great job! In dealing with all zeroes, it's perfect.
However, I am sorry for a confusion. These results were the output of your previous method and were all wrong:
var skus = GetSkus("50014EE20154", 10);
Results: 50014;50015;50016;50017;50
The correct output would be:
(50014EE20154;50014EE20155
Many thanks!
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Hi Gustav,
I love the simplicity and compactness of your code and seems to be working great!
Thanks.
I love the simplicity and compactness of your code and seems to be working great!
Thanks.
You are welcome!
That said, depending on the context, it could be an idea to expand the S/N generation to a class - like shown by Saige - with the methods and properties and error handling you may need in your application. It takes a little, but in many cases the efforts will pay well off.
/gustav
That said, depending on the context, it could be an idea to expand the S/N generation to a class - like shown by Saige - with the methods and properties and error handling you may need in your application. It takes a little, but in many cases the efforts will pay well off.
/gustav
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
saige,
That one is very elegant that gave perfect results using various anticipated samples.
Thank you very much!
That one is very elegant that gave perfect results using various anticipated samples.
Thank you very much!
ASKER
Lovely and elegant solutions!
ASKER
What will happen if the last 4 characters are not all numeric? How to ensure or prevent this from happening?
Thanks.
Thanks.
With the solution that I presented it takes a length parameter, this way you can define the length of the number filter. Consider a serial number like: JSK4W90SDA3B25
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace EE_Q28848919
{
class Program
{
static void Main(string[] args)
{
foreach (string sn in GetSkus("JSK4W90SDA3B25", 10, 2))
Console.WriteLine(sn);
Console.ReadLine();
}
/// <summary>Produces a sequence of sku's based upon a numeric filter.</summary>
/// <param name="source">The original sku.</param>
/// <param name="range">The number of sku's to return.</param>
/// <param name="length">The total numbers to match in the filter; e.g. matching 2693 represents a length of 4.</param>
/// <param name="location">The location of the matches (At the front of the sku or at the back of the sku).</param>
/// <returns>IEnumerable<System.String>.</returns>
static IEnumerable<string> GetSkus(string source, int range, int length = 4, MatchLocation location = MatchLocation.Back)
{
List<string> results = new List<string>();
if (source != null)
{
string pattern;
if (location.Equals(MatchLocation.Front))
pattern = string.Format(@"(\b\d{{{0}}}\B)", length);
else
pattern = string.Format(@"(\d{{{0}}}(?!\d)+)", length);
List<string> parts = Regex.Split(source, pattern).Where(s => !string.IsNullOrEmpty(s)).ToList();
int part;
if (parts.Count() > 1)
{
switch (location)
{
case MatchLocation.Front:
{
if (int.TryParse(parts.First(), out part))
{
results.AddRange(from i in Enumerable.Range(part, range) select string.Format("{0}{1}", i.ToString("D" + ZeroPadder(parts.First())), string.Join("", parts.Where(x => !x.Equals(parts.First())).ToArray())));
}
}
break;
case MatchLocation.Back:
default:
{
if (int.TryParse(parts.Last(), out part))
{
results.AddRange(from i in Enumerable.Range(part, range) select string.Format("{0}{1}", string.Join("", parts.Where(x => !x.Equals(parts.Last())).ToArray()), i.ToString("D" + ZeroPadder(parts.Last()))));
}
}
break;
}
}
}
return results;
}
static int ZeroPadder(string source)
{
return source.StartsWith("0") ? source.Length : 0;
}
}
enum MatchLocation : int
{
Front = 0,
Back = 1
}
}
Which produces the following output --saige-
ASKER
Saige,
Thanks for the quick response.
I am pretty sure that it should be 4 numeric characters to take care of at least 9999 production items, but we cannot avoid garbage entry. Probably, we just need to prompt the user, if the last 4 characters are not numeric.
Thanks for the quick response.
I am pretty sure that it should be 4 numeric characters to take care of at least 9999 production items, but we cannot avoid garbage entry. Probably, we just need to prompt the user, if the last 4 characters are not numeric.
Either that or make a decision as to how to handle sequences that include letters (i.e. -do you want the letters to sequence as well?); e.g. -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace EE_Q28848919
{
class Program
{
static void Main(string[] args)
{
foreach (string sn in GetSkus("JSK4W90SDA3B25", 10, 4))
Console.WriteLine(sn);
Console.WriteLine();
foreach (string sn in GetSkus("JSK4W90SDA3B25", 10, 4, MatchLocation.Front))
Console.WriteLine(sn);
Console.ReadLine();
}
/// <summary>Produces a sequence of sku's based upon a numeric filter.</summary>
/// <param name="source">The original sku.</param>
/// <param name="range">The number of sku's to return.</param>
/// <param name="length">The total numbers to match in the filter; e.g. matching 2693 represents a length of 4.</param>
/// <param name="location">The location of the matches (At the front of the sku or at the back of the sku).</param>
/// <returns>IEnumerable<System.String>.</returns>
static IEnumerable<string> GetSkus(string source, int range, int length = 4, MatchLocation location = MatchLocation.Back)
{
List<string> results = new List<string>();
if (source != null)
{
string pattern;
if (location.Equals(MatchLocation.Front))
pattern = string.Format(@"((?i)\b[0-9a-z]{{{0}}}\B)", length);
else
pattern = string.Format(@"((?i)[0-9a-z]{{{0}}}(?![0-9a-z])+)", length);
List<string> parts = Regex.Split(source, pattern).Where(s => !string.IsNullOrEmpty(s)).ToList();
if (parts.Count() > 1)
{
switch (location)
{
case MatchLocation.Front:
results.AddRange(from i in Enumerable.Range(0, range)
let current = GetSequence(parts.First(), i)
select string.Format("{0}{1}", current, string.Join("", parts.Where(x => !x.Equals(parts.First())).ToArray())));
break;
case MatchLocation.Back:
default:
results.AddRange(from i in Enumerable.Range(0, range)
let current = GetSequence(parts.Last(), i)
select string.Format("{0}{1}", string.Join("", parts.Where(x => !x.Equals(parts.Last())).ToArray()), current));
break;
}
}
}
return results;
}
static string GetSequence(string source, int count)
{
char[] array = source.ToCharArray();
int code = 0;
int lower = 0;
int upper = 0;
for (int i = 0; i < count; i++)
{
for (int j = array.Length - 1; j > 0; j--)
{
code = (char)array[j];
lower = (code >= 48 && code <= 57) ? 48 : (code >= 65 && code <= 90) ? 65 : 97;
upper = (code >= 48 && code <= 57) ? 57 : (code >= 65 && code <= 90) ? 90 : 122;
if (code + 1 > upper)
{
array[j] = (char)lower;
}
else
{
array[j] = (char)(code + 1);
break;
}
}
}
return new string(array);
}
}
enum MatchLocation : int
{
Front = 0,
Back = 1
}
}
Produces the following output --saige-
ASKER
Saige,
This is surely a great solution when the requirement goes this far. For now, I just made a simple validation to ensure the "Starting SN must be > 4 characters and the last 4 be numeric" by prompting the user if not.
Thank you so much!
This is surely a great solution when the requirement goes this far. For now, I just made a simple validation to ensure the "Starting SN must be > 4 characters and the last 4 be numeric" by prompting the user if not.
Thank you so much!
ASKER
Saige,
Your final solution is indeed awesome. I decided to use it for more freedom and no worries.
Many thanks!
Your final solution is indeed awesome. I decided to use it for more freedom and no worries.
Many thanks!
Not a problem, glad it works out for you.
-saige-
-saige-
What I understand from this is that you want order the Sequence Number based on the last 4 digits of the SN so that it finally looks like below;
SNOIHOUHBNDB5021
SNQLOPMDUJDJJG5022
SNBLPRAWQMDTK5022
Is that what you want? Where have yo stored the SNs?