Link to home
Start Free TrialLog in
Avatar of JimiJ13
JimiJ13Flag for Philippines

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.
Avatar of Ramprakash Jeyabalan
Ramprakash Jeyabalan

Is it just 4 digit always?

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?
Avatar of Gustav Brock
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
Avatar of JimiJ13

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.
Avatar of JimiJ13

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();

Open in new window

/gustav
Avatar of JimiJ13

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.
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();

Open in new window

/gustav
Avatar of JimiJ13

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.
You are welcome!

/gustav
Avatar of JimiJ13

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;JSK4W90SDA3626;JSK4W90SDA3627;...)

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();

Open in new window

Or you could reuse the list from above:
string[] allSns = sns.ToArray();

Open in new window

Or to create your separated string:
string allSns = string.Join(";",sns.ToArray());

Open in new window

/gustav
Avatar of JimiJ13

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!
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);
		}
	}
}

Open in new window

Which produces the following output -User generated image-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-
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):
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;
		}
	}
}

Open in new window

Which produces the following output -User generated imageFrom this you could use Gustav's suggestion on building the array.

-saige-
Avatar of JimiJ13

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.
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-
Avatar of JimiJ13

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;50018;50019;50020;50021;50022;50023

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
	}
}

Open in new window

Which produces the following output -User generated image-saige-
Avatar of JimiJ13

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;50018;50019;50020;50021;50022;50023

The correct output would be:
(50014EE20154;50014EE20155;50014EE20156;50014EE20157;50014EE20158;50014EE20159;50014EE20160;50014EE20161;50014EE20162;50014EE20163)


Many thanks!
SOLUTION
Avatar of Gustav Brock
Gustav Brock
Flag of Denmark image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of JimiJ13

ASKER

Hi Gustav,

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
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of JimiJ13

ASKER

saige,

That one is very elegant that gave perfect results using various anticipated samples.

 
Thank you very much!
Avatar of JimiJ13

ASKER

Lovely and elegant solutions!
Avatar of JimiJ13

ASKER

What will happen if the last 4 characters are not all numeric? How to ensure or prevent this from happening?

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&lt;System.String&gt;.</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
	}
}

Open in new window

Which produces the following output -User generated image-saige-
Avatar of JimiJ13

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.
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&lt;System.String&gt;.</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
	}
}

Open in new window

Produces the following output -User generated image-saige-
Avatar of JimiJ13

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!
Avatar of JimiJ13

ASKER

Saige,

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-