Supermarkov


following classes allow supercollider to read data and parse it into a Markov Set.
there will be a worldwide markov module trading place opened here soon. a typical text result
some examples how to use the MarkovSet
further details about Markov Chains and Finite State Machines in SuperCollider: (-)

/*
______________________________________________________________________________________________________________

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)