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.seeds = slist;
}
var clist;
clist = this.at(prevkey);
if(clist.isNil, { this.put(prevkey, CountList.new.put(nextkey) ) },
{ clist.put(nextkey) }
);
^this;
}

var counts;
counts = countlist.counts;
countlist.items.do({ arg item, i;
})
}

++ { arg aMarkovSet;
aMarkovSet.keysValuesDo({ arg key, clist;
})
}

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},
{
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,
);
previtem = item;
});

}

parseIO { arg iostream, delimiter, length, exclusionsArray, blocksize;
var stream, item;

if(delimiter.notNil, {
stream = Routine.new({
var item;
length.do({
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, {

this.parseIO(file, delimiter, count-1, exclusionsArray, blocksize=1)

}, { "something went wrong.".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]

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, {
}, {
n = counts.at(index);
counts.put(index, n + 1);
});

}

var index, n;
index = items.indexOfLike(item);
if( index.isNil, {
}, {
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
}
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)