/*
______________________________________________________________________________________________________________
First Order Markov Set: a set of a finite number of elements in which
each element is associated
with a number of possible transition probabilities to another element
of the set.
MarkovSet is a Dictionary that contains keys pointing to CountLists
(that again contain keys and
their probabilities). By parsing in a stream the Set 'learns' what
element can possibly follow
another.
for reference see: www.taygeta.com
____________________________________________________________________________________________
2000 Julian Rohrhuber
*/
 
MarkovSet : Dictionary {
 var <>seeds;
 
*make { arg stream, length, seeds, exits;
        var dict;
        dict = super.new;
        dict.parse(stream, length);
 
        dict.makeDefaultSeeds;
 
        ^dict
}
makeDefaultSeeds {  var slist;
    slist = List.new;
    this.keys.do({ arg item; slist.add(item) });
    if(slist.size == 0, { slist.add(nil)});
    this.seeds = slist;
    }
read { arg prevkey, nextkey;
  var clist;
  clist = this.at(prevkey);
  if(clist.isNil, { this.put(prevkey, CountList.new.put(nextkey)
) },
     { clist.put(nextkey) }
  );
  ^this;
}
add { arg key, countlist;
  var counts;
  counts = countlist.counts;
  countlist.items.do({ arg item, i;
   counts.do({ this.read(key, item) })
  })
}
++ { arg aMarkovSet;
  aMarkovSet.keysValuesDo({ arg key, clist;
   this.add(key, clist)
  })
}
addExits { arg terminatorsArray; terminatorsArray.do({ arg item; this.add(item,
nil) }) }
normalize { ^this.do({ arg node; node.weigh }) }
manipulate { arg func;
   this.do({ arg node, i;
    node.manipulate(func, i)
   })
}
 
parse { arg stream, length=inf, exclusionsArray;
   var item, previtem, counter, exclusiveDict;
 
    if( exclusionsArray.notNil,{
      exclusiveDict = Dictionary.new; //use
a Dictionary for efficiency. (?)
       exclusionsArray.do({ arg item;
exclusiveDict.put(item.asString, \found) });
       stream = stream.reject({ arg item;
exclusiveDict.at(item).notNil });
 
     });
 
 
         counter = 0; previtem
= stream.next; item = stream.next;
        while({ previtem.notNil
&& (counter <= length) && item.notNil},
          {
           this.read(previtem,
item);
           counter
= counter + 1;
           previtem
= item;
           item =
stream.next;
          }
         );
}
 
 
spy { arg stream, length=inf, exclusionsArray;
   var item, previtem, counter, exclusiveDict;
 
    if( exclusionsArray.notNil,{
      exclusiveDict = Dictionary.new; //use
a Dictionary for efficiency. (?)
       exclusionsArray.do({ arg item;
exclusiveDict.put(item.asString, \found) });
       stream = stream.reject({ arg item;
exclusiveDict.at(item).notNil });
 
     });
 
 
        counter = 0;
 
        ^stream.collect({ arg item;
 
           counter
= counter + 1;
         if( previtem.notNil
&& (counter <= length) && item.notNil,
          {this.read(previtem,
item)}
         );
         previtem = item;
        });
 
}
 
 
parseIO { arg iostream, delimiter, length, exclusionsArray, blocksize;
  var stream, item;
 
   if(delimiter.notNil, {
   stream = Routine.new({
      var item;
      length.do({
      item = iostream.readUpTo(delimiter);
      item.yield;
 
     })  })
     }, {
   stream = Routine.new({
      var item;
      length.do({
      item = iostream.nextN(blocksize);
      item.yield;
 
     })  })
   });
 
 
  ^this.parse(stream, length, exclusionsArray)
}
 
parseFile { arg file, delimiter, exclusionsArray, blocksize=1;
  var stream, len, count;
 
  // try to find out how many blocks are in the file:
  len = file.length;
  count = 0;
  if(delimiter.isNil,
   { count = len div: blocksize },
   { while(
    { file.pos < (len-20) },
    { file.readUpTo(delimiter); count = count + 1; })
  });
  file.pos_(0);
 
  //parse file as iostream
  if(count != 0, {
   count.post;" blocks read into Markovset. Please wait ... 
".post;
 
   this.parseIO(file, delimiter, count-1, exclusionsArray,
blocksize=1)
 
  }, { "something went wrong.".postln; });
  "ready.".postln;
 
  ^this
}
 
asPat { arg length=inf, starts;
   var item,  clist;
   if(this.size == 0, {"no elements contained in this MarkovSet".postln; 
this.halt });
   this.do({ arg node; if( node.weights.at(0).isNil, { node.weigh
}) });
   if(starts.notNil, { seeds = starts.reject({ arg item; this.at(item).isNil
}) }, { this.makeDefaultSeeds } );
 
           
^Prout({
             
item = seeds.choose;
                
item.yield;
                
length.do({
                 
clist = this.at(item);
                 
if( clist.notNil, {
                  
item = clist.wchoose;
 
                
}, { item = nil });
                
item.yield;
                
})
            
})
}
// passes an array of data each time it is called: [item, weight, number
of items]
// (see also CountList::infoChoose)
 
asInfoPat { arg length, starts;
   var items,  clist;
   if(this.size == 0, {"no elements contained in this MarkovSet".postln; 
this.halt });
   this.normalize;
   if(starts.notNil, { seeds = starts }, { this.makeDefaultSeeds
} );
 
           
^Prout({
             
items = [seeds.choose, 1, 1];
 
                
length.do({
                 
if(items.notNil, { clist = this.at(items.at(0)) });
                 
if( clist.notNil, {
                  
items = clist.infoChoose;
 
                
}, { items = nil });
                
items.yield;
                
})
            
})
}
 
//uses a pattern of indices to walk through a MarkovDict. uses wrapped
indexing.
asPswitch { arg indexPat, length, starts;
   var item,  clist, inStream, index;
   if(this.size == 0, {"no elements contained in this MarkovSet".postln; 
this.halt });
   this.do({ arg node; if( node.weights.at(0).isNil, { node.weigh
}) });
   if(starts.notNil, { seeds = starts }, { this.makeDefaultSeeds
} );
   length = length ? inf;
 
           
^Prout({ arg inval;
             
item = seeds.choose;
                
inStream = indexPat.asStream;
                
length.do({
                 
clist = this.at(item); //.postln;
                 
index = inStream.next; //.postln;
                 
if( clist.notNil && index.notNil, {
                  
item = clist.wrapAt(index.asInteger);
 
 
                
}, { item = nil });
                
item.embedInStream(inval);
                
})
            
})
}
 
 
}
 
 
 
/*
______________________________________________________________________________________________________________
CountList works similar to Bag: it contains every kind of element only
once
(simple equality == ) counting the instances and calculating an array
of associated weights.
it is used as Element in MarkovSet.
____________________________________________________________________________________________
Julian Rohrhuber 2000
*/
CountList  { //WeighBag? SpecialBag? SuperBag? ExtraBag? ServiceBag?
MarkovBag?
   var <>items, <>counts, <>weights;
*new { ^super.new.items_(Array.new).counts_(Array.new).weights_(Array.new)
}
 
*with { arg theitems, thecounts, theweights;
  if(theitems.size == thecounts.size, {
   ^super.new.items_(theitems).counts_(thecounts).weights_(theweights)
  }, { "the first two arrays must have the same number of elements".postln
})
}
at { arg index;  ^items.at(index) }
wrapAt { arg index;  ^items.wrapAt(index) }
 
put { arg item;
  var index, n;
 
  index = items.indexOfLike(item);
  if( index.isNil, {
    items = items.add(item);
    counts = counts.add(1);
    }, {
    n = counts.at(index);
    counts.put(index, n + 1);
    });
 
}
 
add { arg item, ntimes;
  var index, n;
  index = items.indexOfLike(item);
  if( index.isNil, {
    items = items.add(item);
    counts = counts.add(ntimes);
    }, {
    n = counts.at(index);
    counts.put(index, n + ntimes);
    })
 
}
remove { arg item, ntimes;
  var index, n, count;
  index = items.indexOf(item);
  count = 0;
  if(index.notNil, {
  while({
    n = counts.at(index);
 
   (n > 0) && (count < ntimes)
   }, {
   if(n == 1, { items.removeAt(index) });
   counts.put(index, n - 1);
   count = count + 1;
 
   });
  });
 
}
removeAll { arg item;
  var index, n;
  index = items.indexOf(item);
  if(index.notNil, {
  while({
    n = counts.at(index);
    if(n == 1, { items.removeAt(index) });
   n > 0
   }, {
   counts.put(index, n - 1);
 
   });
  });
 
}
 
 
 
weigh { this.weights_(counts.normalizeSum) }
normalize { ^this.weigh }
equalize { var n; n = this.items.size; this.weights_(Array.fill(n,
{ 1/n })) }
manipulate { arg func, inval;
   this.items.do({ arg item, i;
    weights.put(i, func.value(item, counts.at(i), inval))
   });
   weights = weights.normalizeSum;
   ^this
}
collect { arg func; items = items.collect({ arg item, i; func.value(item,
i) }); ^this }
wchoose { ^items.wchoose(weights) }
// passes an array of data each time it is called. [item, weight, number
of items]
infoChoose { var index; index = weights.windex; ^[items.at(index),
weights.at(index), counts.at(index)] }
size { ^items.size }
depth { ^counts.sum }
purge { counts = Array.fill(this.size, { 1 }); this.weigh; ^this }
 
++ { arg countlist;
  var newitems;
  newitems = countlist.items;
  newitems.do({ arg item, i;
   this.add(item, countlist.counts.at(i))
   });
  ^this
}
printOn { arg stream;
  stream << "[" ;
  this.items.printOn(stream);
  stream << ", ";
  this.counts.printOn(stream);
  stream << ", ";
  this.weights.printOn(stream);
  stream << "]";
 
 }
storeOn { arg stream;
  stream << "CountList.with(" ;
  this.items.storeOn(stream);
  stream << ", ";
  this.counts.storeOn(stream);
  stream << ", ";
  this.weights.storeOn(stream);
  stream << ")";
 
 
 }
}
/*
indexOfLike { arg item;
  this.do({ arg elem, i;
   if ( item == elem, { ^i })
  });
  ^nil
 }
 */
//needs to be added to  SequenceableCollection (similar to indexOf)