<jupyter_start><jupyter_text>Fun with Iterators (generators)  Playing around with iterators<jupyter_code>debugging = False
#debugging = True
debug2 = False

logging = True

def dbg(f, *args):
    if debugging:
        print(('  DBG:' + f).format(*args))

def dbg_cont(f, *args):
    if debugging:
        print(('  DBG:' + f).format(*args), end='')

def dbg2(f, *args):
    if debug2:
        print((' -DBG2:' + f).format(*args))
        
def log(f, *args):
    if logging:
        print((f).format(*args))
        
def logError(f, *args):
    if logging:
        print(('*** ERROR:' + f).format(*args))
        
def className(instance):
    return type(instance).__name__

def isstr(obj): 
    return isinstance(obj, basestring)
import types

def ordered_merge(ia, ib, ascending=True, comparator=None, validate=True):
    """ Returns an iterator which merges two sorted lists into one """
    a = None
    b = None
    aok = False
    bok = False
    iac = ibc = 0 # Count of objects iterated.
    # convert iterable objects into iterators as needed.
    if not isinstance(ia, types.GeneratorType): ia = iter(ia)
    if not isinstance(ib, types.GeneratorType): ib = iter(ib)
    if not comparator:
        comparator = (lambda x, y: x<=y) if ascending else (lambda x, y: x>y)
    while ia or ib:
        if not aok and ia: # load a
            try:
                nxtv = next(ia)
            except StopIteration:
                ia = None
                aok = False
            else:
                if validate and iac and not comparator(a, nxtv):
                    raise ValueError("merge iterator 'ia', item {0}, value:'{1}' is out of order.".
                                     format(iac, nxtv))
                a = nxtv
                aok = True
                iac += 1
                #log('load a={0}', a)
        if not bok and ib: # load b
            try:
                nxtv = next(ib)
            except StopIteration:
                ib = None
                bok = False
            else:
                if validate and ibc and not comparator(b, nxtv):
                    raise ValueError("merge iterator 'ib', item {0}, value:'{1}' is out of order.".
                                     format(ibc, nxtv))
                b = nxtv
                bok = True
                ibc += 1
                #log('load b={0}', b)               
        if aok and bok: # return the least of the loaded values.
            if comparator(a, b):
                aok = False
                yield a
            else:
                bok = False
                yield b
        else:
            # Return the remaining values from the remaining iterator
            if aok:
                aok = False
                yield a               
            if bok:
                bok = False
                yield b

import types

def ordered(it, ascending=True, comparator=None):
    """ Returns true if all items in a seqence are in order """
    prev = None
    curr = None
    running = False
    # convert iterable objects into iterators as needed.
    if not isinstance(it, types.GeneratorType): it = iter(it)
    if not comparator:
        comparator = (lambda x, y: x<=y) if ascending else (lambda x, y: x>y)
    for curr in it:
        if running:
            if not comparator(prev, curr):
                return False
        running = True
        prev = curr
    # All pairs have been examined and they are in order.
    return True
import types
#import functools

def ordered_multiway_merge(*iterset, ascending=True, comparator=None, validate=True):
    """ Returns an iterator which merges multiple sorted lists into one """
    iters = list(iterset)
    n    = len(iters)
    vals = [None]*n  # No values have been pulled from lists yet.
    voks = [False]*n # None of the values are loaded yet.
    visv = [0]*n # These values have not been validated.
    # convert iterable objects into iterators as needed.
    for i, it in enumerate(iters):
        if not isinstance(it, types.GeneratorType): it = iter(it)
        iters[i] = it
    if not comparator:
        comparator = (lambda x, y: x<=y) if ascending else (lambda x, y: x>y)
    #def validIterator():
    #    return functools.reduce(lambda x, y : x or y, iters)
    
    # As long as there's an unexhausted iterator or any value remaining
    # in the queue, keep generating.
    while True:
        besti = None
        bestv = None
        for i, vok in enumerate(voks):
            # Load a value from any healthy/unexhausted iterator.
            it = iters[i]           
            v  = vals[i]
            if not vok and it is not None:
                try:
                    nxtv = next(it)
                except StopIteration:
                    iters[i] = None # this iterator is now depleted.
                    voks[i] = vok = False
                else:
                    if validate and visv[i] and not comparator(v, nxtv):
                        raise ValueError("merge iterator #{0}, item {1}, value:'{2}' is out of order.".
                                         format(i, visv[i], nxtv))
                    visv[i] += 1
                    vals[i] = v = nxtv
                    voks[i] = vok = True
                    #log(' -- [{0}] val={1};   best[{2}]={3}', i, vals[i], besti, bestv)
            # See if this is the best entry from any list
            if vok:
                if besti is None or comparator(v, bestv):
                #if besti is None or v<bestv:
                    besti = i
                    bestv = v
                    #log(' ++ [{0}] val={1};   best[{2}]={3}', i, vals[i], besti, bestv)
        if bestv is not None:
            #log('YIELD {0}  besti=={0}, n=={2}', bestv, besti, n)
            voks[besti] = False
            yield bestv
        else:
            break
    # end while

a = list(range(10, 20))
b = list(range(5,32,2))[::-1]
c = (13, 22, 30, 36, 12345)

print(a)
print(b)
#a=[]
z = [x for x in ordered_merge(a,b,validate=False)]
print(z)
#y = [x for x in ordered_multiway_merge(a, b, [1, 2, 3], [0, 11, 12, 13, 14], [-1, 99])]
y = [x for x in ordered_multiway_merge(a, b, c, [], (1, 2, 3), [11.5], [0, 11, 12, 13, 14], [-1, 99])]
print(y)
ordered(y)
#ordered(ordered_merge(z,c))
#[x for x in merge(z,c)]
[None]*10
w={1, 3, 4, 5, 22, -1}
w
min(w)
import functools
functools.reduce(lambda x, y : x or y, [False, 0, False, 1, 0])
list((3, 4, 5))
 x = {
   "namespace": "example.avro",
   "type": "record",
   "name": "User",
   "fields": [
      {"name": "name", "type": "string"},
      {"name": "favorite_number",  "type": ["int", "null"]},
      {"name": "favorite_color", "type": ["string", "null"]}
   ] 
 }
    
x['fields']
x.setdefault('glorm',34)

class objectview(object):
    def __init__(self, d):
        self.__dict__ = d
objectview(x).glorm
class objdict(dict):
    def __getattr__(self, name):
        if name not in self: raise AttributeError("No such attribute: " + name)
        return self[name]

    def __setattr__(self, name, value):
        self[name] = value

    def __delattr__(self, name):
        if name not in self: raise AttributeError("No such attribute: " + name)
        del self[name]

y = objdict(x)
y.plugh = 'Go away'
y
x<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12143349414197105, 'fraction_numerical': 0.01392143349414197, 'mean_word_length': 1.7599847850893875, 'pattern_counts': {'":': 10, '<': 9, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '202603', 'license': 'Unlicense', 'n_tokens_mistral': 2364, 'n_tokens_neox': 2239, 'n_words': 770, 'path': 'Iterators.ipynb', 'repo_name': 'jfogarty/python-code-practice', 'size': '7255'}