function _compareMaps(a,b, options) {

    debug(a,b);

    let alength = a.size === undefined ? a.length : a.size;
    let blength = b.size === undefined ? b.length : b.size;

    if (alength === 0 && blength === 0) return ops.NOP;
    // ops.Map([...b]) and not just ops.Rpl(b) because if b is a map it's not good JSON.
    if (alength === 0 || blength === 0) return new ops.Map([...b]);

    let patch = [];

    if (!options.sorted) {
        a = Array.from(a).sort((a,b) => utils.compare(a,b,options));
        b = Array.from(b).sort((a,b) => utils.compare(a,b,options));
    }

    let ai = 1, bi = 1;
    let ao = a[0], bo = b[0]
    let element_options = options.getArrayElementOptions();

    do {
        let comparison = utils.compare(ao,bo,options);
        debug("comparing items", ao, bo, comparison);
        if (comparison < 0) {
            debug('skip');
            ao = a[ai++]; 
        } else if (comparison > 0) {
            debug('insert');
            patch.push([options.key(bo), new ops.Ins(options.value(bo))]);
            bo = b[bi++];
        } else {
            if (options.value(ao) !== options.value(bo)) {
                let element_patch = compare(options.value(ao), options.value(bo), element_options)
                if (element_patch != ops.NOP) patch.push([options.key(bo), element_patch]);
            }
            else debug('skip2');
            ao = a[ai++]; 
            bo = b[bi++];
        }
    } while (ai <= a.length && bi <= b.length);

    while (ai <= a.length) {
        patch.push([options.key(ao), ops.DEL]);
        ao=a[ai++]; 
    }

    while (bi <= b.length) {
        patch.push([options.key(bo), new ops.Ins(options.value(bo))]); 
        bo=b[bi++]; 
    }

    return new ops.Map(patch);
}
function _compareMaps(a,b, options) {

    debug(a,b);

    let alength = a.size === undefined ? a.length : a.size;
    let blength = b.size === undefined ? b.length : b.size;

    if (alength === 0 && blength === 0) return ops.NOP;
    // ops.Map([...b]) and not just ops.Rpl(b) because if b is a map it's not good JSON.
    if (alength === 0 || blength === 0) return new ops.Map([...b]);

    let patch = [];

    if (!options.sorted) {
        a = Array.from(a).sort((a,b) => utils.compare(a,b,options));
        b = Array.from(b).sort((a,b) => utils.compare(a,b,options));
    }

    let ai = 1, bi = 1;
    let ao = a[0], bo = b[0]
    let element_options = options.getArrayElementOptions();

    do {
        let comparison = utils.compare(ao,bo,options);
        debug("comparing items", ao, bo, comparison);
        if (comparison < 0) {
            debug('skip');
            ao = a[ai++]; 
        } else if (comparison > 0) {
            debug('insert');
            patch.push([options.key(bo), new ops.Ins(options.value(bo))]);
            bo = b[bi++];
        } else {
            if (options.value(ao) !== options.value(bo)) {
                let element_patch = compare(options.value(ao), options.value(bo), element_options)
                if (element_patch != ops.NOP) patch.push([options.key(bo), element_patch]);
            }
            else debug('skip2');
            ao = a[ai++]; 
            bo = b[bi++];
        }
    } while (ai <= a.length && bi <= b.length);

    while (ai <= a.length) {
        patch.push([options.key(ao), ops.DEL]);
        ao=a[ai++]; 
    }

    while (bi <= b.length) {
        patch.push([options.key(bo), new ops.Ins(options.value(bo))]); 
        bo=b[bi++]; 
    }

    return new ops.Map(patch);
}
function _compareMaps ( a , b , options ) { debug ( a , b ) ; let alength = a . size === undefined ? a . length : a . size ; let blength = b . size === undefined ? b . length : b . size ; if ( alength === 0 && blength === 0 ) return ops . NOP ; // ops.Map([...b]) and not just ops.Rpl(b) because if b is a map it's not good JSON. if ( alength === 0 || blength === 0 ) return new ops . Map ( [ ... b ] ) ; let patch = [ ] ; if ( ! options . sorted ) { a = Array . from ( a ) . sort ( ( a , b ) => utils . compare ( a , b , options ) ) ; b = Array . from ( b ) . sort ( ( a , b ) => utils . compare ( a , b , options ) ) ; } let ai = 1 , bi = 1 ; let ao = a [ 0 ] , bo = b [ 0 ] let element_options = options . getArrayElementOptions ( ) ; do { let comparison = utils . compare ( ao , bo , options ) ; debug ( "comparing items" , ao , bo , comparison ) ; if ( comparison < 0 ) { debug ( 'skip' ) ; ao = a [ ai ++ ] ; } else if ( comparison > 0 ) { debug ( 'insert' ) ; patch . push ( [ options . key ( bo ) , new ops . Ins ( options . value ( bo ) ) ] ) ; bo = b [ bi ++ ] ; } else { if ( options . value ( ao ) !== options . value ( bo ) ) { let element_patch = compare ( options . value ( ao ) , options . value ( bo ) , element_options ) if ( element_patch != ops . NOP ) patch . push ( [ options . key ( bo ) , element_patch ] ) ; } else debug ( 'skip2' ) ; ao = a [ ai ++ ] ; bo = b [ bi ++ ] ; } } while ( ai <= a . length && bi <= b . length ) ; while ( ai <= a . length ) { patch . push ( [ options . key ( ao ) , ops . DEL ] ) ; ao = a [ ai ++ ] ; } while ( bi <= b . length ) { patch . push ( [ options . key ( bo ) , new ops . Ins ( options . value ( bo ) ) ] ) ; bo = b [ bi ++ ] ; } return new ops . Map ( patch ) ; }
Patch for objects

Functions that should allow us to compare two objects, compute a difference, send the difference over the wire, and apply a patch at the far end
to sync a remote object with the changes.

For 'patch' to work properly, the patched object (and child objects) should have a static 'fromJSON' method somewhere in its inheritance tree.
You may also implement a static 'getAttrProps(name)' object, which may return a variety of options that are applied to
child properties with the given name (see {@link DEFAULT_OPTIONS}).

Won't work with cyclic data structures.
 Compare two Map-like objects.
Patch for objects