3
\$\begingroup\$

first allow me to explain the what the purpose of this program is: a "barcode" as I'm calling it consists of three parts:

  1. plant_id=['ATX','HOU','CHS']
  2. Serial Number: a string of 9-digits
  3. 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.

\$\endgroup\$
2
  • \$\begingroup\$ What determines which barcode is valid? \$\endgroup\$ – Ted Brownlow Jan 8 at 19:33
  • 1
    \$\begingroup\$ There is plant_id, plan_id, plant ID, and pland_id above. \$\endgroup\$ – greybeard Jan 8 at 21:03
2
\$\begingroup\$

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.

\$\endgroup\$
3
  • \$\begingroup\$ I love the map suggestion. \$\endgroup\$ – Matthew Doughty Jan 8 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 Jan 9 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 like valid_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 of Barcode could be just def __iter__(self): return (code for code in barcode_iterator(...) if is_valid(code). \$\endgroup\$ – Carcigenicate Jan 9 at 0:09
0
\$\begingroup\$

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)))
\$\endgroup\$
2
  • \$\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 Jan 8 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 Jan 8 at 23:06
0
\$\begingroup\$

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))
\$\endgroup\$
-1
\$\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. 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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.