first allow me to explain the what the purpose of this program is: a "barcode" as I'm calling it consists of three parts:
- plant_id=['ATX','HOU','CHS']
- Serial Number: a string of 9-digits
- mod 10 digit: A digit (as a chr()) between 0-9 that is assigned randomly to the end of a barcode.
So a full barcode would look like this:
'ATX9784634072': (ATX=plan_id, '978463407'=serial_number, and '2' is the mod 10 digit)
Note: for each serial number there can only be one barcode. So in this case there are no other barcodes with the serial number 978463407. The plant ID simply indicates the plant that manufactured the item. Thus for each serial number there are 3*10=30 possible barcodes and only one is valid.
Any other barcode with serial number 978463407 will be invalid regardless of it's pland_id value or its mod_10 digit value.
Thus far I have an iterator class called BarcodeIterator that only iterates over the serial numbers in a given range (start and stop arguments).
Then I have a generator function bar_code_generator, that takes an instance of BarcodeIterator() as a parameter to generate the full bar codes.
## Let me begin by explaining the structure of the barcodes in question
## The barcode is made up of three parts. The Plant ID,
## The serial number, then a mod_10 digit. (For purposes of this
## code, I'm going to assume this is a random number between 1 and 10
## Example: 'ATX9784634068',
Plants=['ATX','HOU','DFW']
s_num0='978463406'
s_num1='978463407'
s_num2='978463408'
class BarcodeIterator:
def __init__(self,barcode,start,stop,
plant_ID=['ATX','HOU','CHS']):
self.plant=barcode[:3]
self.serial_number=barcode[3:12]
self.mod_10=barcode[-1]
self.start = int(self.serial_number)-start-1
self.stop = int(self.serial_number)+stop
self.plant_ID=plant_ID
def __next__(self):
self.start+=1
if self.start>self.stop:
raise StopIteration
else:
start_string=str(self.start)
result=start_string.zfill(9)
return result
def __iter__(self):
return self
def bar_code_generator(iterObj,plant_ID=['ATX','HOU','CHS']):
for bc in iterObj:
for p_id in plant_ID:
def get_mod():
x0='0'
while x0<='9':
yield x0
x0=chr(ord(x0)+1)
for digit in get_mod():
barcode_tuple=(p_id,bc,digit)
barcode_string=''.join(barcode_tuple)
yield barcode_string
##class Barcode:
'''going to be my iterable class'''
## def __init__(self, barcode):
## self.barcode = barcode
## self.plant_id=barcode[:3]
## ....
##
## def __iter__(self):
## return BarcodeIterator(
if __name__=='__main__':
bc_iter=BarcodeIterator(s_num0,1,1)
bc_gen=bar_code_generator(bc_iter)
bc_list=[bc for bc in bc_gen]
print(bc_list)
and the result is this:
['ATX0004634050', 'ATX0004634051', 'ATX0004634052', 'ATX0004634053'
, 'ATX0004634054', 'ATX0004634055', 'ATX0004634056', 'ATX0004634057'
, 'ATX0004634058', 'ATX0004634059', 'HOU0004634050', 'HOU0004634051'
, 'HOU0004634052', 'HOU0004634053', 'HOU0004634054', 'HOU0004634055'
, 'HOU0004634056', 'HOU0004634057', 'HOU0004634058', 'HOU0004634059'
, 'CHS0004634050', 'CHS0004634051', 'CHS0004634052', 'CHS0004634053', . . . it continues
Finally, my question (or plea for help) is this:
I want to:
clean this code up a lot: I would really like to have a BarcodeIterator() object and a Barcode() object (the iterable). So that each time I call an instance of Barcode() it returns the next valid Barcode... which brings me to my second question
As I have mentioned twice. There is only one valid barcode per serial number (hence, 30 possible barcodes). I haven't put an isValid() function in the scrip because I don't know where it would go.
I want to see each time the Barcode() object is called, it yields the next valid barcode without having to create a new object (if possible).
I hope this was clear. Any help will be greatly appreciated.
Please post here, but if you have vast experience in python and want to work with me on this you are more than welcome to pm me.
4 Answers 4
I'm a little too tired to make any ambitious suggestions, but I thought I'd recommend a few minor improvements.
Your BarcodeIterator
class could be significantly neater by just using a generator function. Unless you have a complicated object state used in multiple different ways that also needs to be iterable (like if you're writing a data structure), I've found manually writing iterator classes to have a tendency to convolute code. Even then, the last structure I wrote had its __iter__
method as a generator function for simplicity. Instead, I'd use (after removing the un-used bits):
from typing import Iterator
def barcode_iterator(barcode: str, start: int, stop: int) -> Iterator[str]:
serial_number = barcode[3:12]
start = int(serial_number) - start
stop = int(serial_number) + stop
while start <= stop:
start_string = str(start)
result = start_string.zfill(9)
yield result
start += 1
This does away with needing to set up the state in the initializer, just to use it in the one method. This also prevents you from needing to manually throw a StopIteration
.
If you want to have a Barcode
class that's iterable and returns only valid results, its __iter__
method could look something like this:
class Barcode:
def __iter__(self):
return (code
for code in barcode_iterator(...)
if is_valid(code)) # Assuming the existence of an is_valid function
get_mod
is odd. It being nested inside of the function suggests that it's using something in the enclosing scope. That isn't the case though; it simply returns a list of stringified numbers. The numbers also never change, so it doesn't make much sense as a function. I think this is one of the rare cases where map
is actually the most appropriate tool:
def bar_code_generator(iterObj, plant_ID=['ATX', 'HOU', 'CHS']):
for bc in iterObj:
for p_id in plant_ID:
for digit in map(str, range(10)): # essentially (str(n) for n in range(10))
barcode_tuple = (p_id, bc, digit)
barcode_string = ''.join(barcode_tuple)
yield barcode_string
I also wouldn't use ['ATX','HOU','CHS']
as a default value in the functions. Swap it to using a tuple instead:
def bar_code_generator(iterObj, plant_ID=('ATX', 'HOU', 'CHS')):
It's a small change, and you likely won't get bitten here, but if you do ever accidentally mutate plant_ID
, you'll get very confusing behavior using lists.
-
\$\begingroup\$ I love the map suggestion. \$\endgroup\$Matthew Doughty– Matthew Doughty2021年01月08日 23:26:53 +00:00Commented Jan 8, 2021 at 23:26
-
\$\begingroup\$ Ok I like those two functions! I want to put them in the BarcodeIterator() class and use the in the __next__() function. Check if the barcodes are valid, if so return the in the __next__() function. Then I want to return the BarcodeIterator object in the __iter__() function of the Barcode() object. \$\endgroup\$Matthew Doughty– Matthew Doughty2021年01月09日 00:02:15 +00:00Commented Jan 9, 2021 at 0:02
-
\$\begingroup\$ @MatthewDoughty The first function I showed replaces the
BarcodeIterator
class (or at least that was my intent). Then, instead of filtering in__next__
, I'd just use a generator expression and do something likevalid_codes = (code for code in barcode_iterator(...) if is_valid(code))
. I'd produce the unfiltered results from one generator, then have a second generator consume the first and filter as needed. With that, the__iter__
method ofBarcode
could be justdef __iter__(self): return (code for code in barcode_iterator(...) if is_valid(code)
. \$\endgroup\$Carcigenicate– Carcigenicate2021年01月09日 00:09:54 +00:00Commented Jan 9, 2021 at 0:09
Why would a barcode be in charge of finding more barcodes? That seems really strange.
Beyond that, there seems to be a lot of unecessary juggling betwen iterators, generators, and the barcode class here.
def serial_strings(start, count):
return (
str(serial_num).zfill(9)
for serial_num in range(start, start+count)
)
def barcodes(serial_start=0, serial_count=1, plants=['ATX','HOU','CHS']):
return (
plant+serial+str(digit)
for serial in serial_strings(serial_start,serial_count)
for plant in plants
for digit in range(10)
)
if __name__=='__main__':
print(list(barcodes(463405)))
-
\$\begingroup\$ I'll get back to you in a second. with that. It's a microcosm of a real problem that I don't want to discuss online. But yes. Given a barcode I want to find the other items that were manufacture right before and right after. The serial numbers are assigned sequentially with respect to time. \$\endgroup\$Matthew Doughty– Matthew Doughty2021年01月08日 20:12:48 +00:00Commented Jan 8, 2021 at 20:12
-
\$\begingroup\$ So, the serial number part of the barcode is sequential with respect to time. So, barcode: ATX4634051019 (Plant: ATX; serial: 463405101; mod_10: 9) may have been made 2 seconds before HOU4634051027(Plant: HOU; serial: 463405102; mod_10: 7) and so on. So, if I want to find the items made around the same time as mine, I need to scan 30 possible bar codes for one bar code. \$\endgroup\$Matthew Doughty– Matthew Doughty2021年01月08日 23:06:21 +00:00Commented Jan 8, 2021 at 23:06
Something like this would generate all possible barcodes "near" the given barcode:
import itertools as it
def barcode_generator(barcode, start, stop, plants=['ATX','HOU','CHS']):
serial_number=int(barcode[3:12])
start = serial_number - start
stop = serial_number + stop
for serial, plant, suffix in it.product(range(start, stop), plants, range(10)):
yield f"{plant}{serial:09}{suffix}"
It can be used like this:
for bcode in barcode_generator('HOU1234567890', 1, 1):
print(bcode)
Or, if you need the generator object:
bcgen = barcode_generator('HOU1234567890', 1, 1)
print(next(bcgen))
So, the serial number part of the barcode is sequential with respect to time. So, barcode: ATX4634051019 (Plant: ATX; serial: 463405101; mod_10: 9) may have been made 2 seconds before HOU4634051027(Plant: HOU; serial: 463405102; mod_10: 7) and so on. The next serial number would be 463405103, 463405104, 463405105, 463405106, 463405107...
I don't know what their respective mod_10 digit's are nor their plant_id.
I want an iterator object that generates the potential barcodes. This is BarcodeIterator(): It's next function should checks if each generated barcode is valid, if it is, it returns it and skips to the next serial number. (it doesn't keep checking barcodes with that same serial number)
The next object Barcode():
should be the iterable. It should have the iter() method and return the next valid barcode when iterated over
plant_id
, plan_id, plant ID, and pland_id above. \$\endgroup\$