/*
______________________________________________________________________________________________________________
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)