diff --git a/BotSharp.Algorithm.UnitTest/BayesianTest.cs b/BotSharp.Algorithm.UnitTest/BayesianTest.cs
new file mode 100644
index 00000000..1665a43b
--- /dev/null
+++ b/BotSharp.Algorithm.UnitTest/BayesianTest.cs
@@ -0,0 +1,201 @@
+using BotSharp.Algorithm.Bayesian;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BotSharp.Algorithm.UnitTest
+{
+ [TestClass]
+ public class BayesianTest
+ {
+ ///
+ /// The training set
+ ///
+ private ITrainingSet _trainingSet;
+
+ ///
+ /// The classifier
+ ///
+ private IClassifier _classifier;
+
+ ///
+ /// The spam class
+ ///
+ private static IClass _spamClass;
+
+ ///
+ /// The ham class
+ ///
+ private static IClass _hamClass;
+
+ ///
+ /// Sets up.
+ ///
+ public void SetUp()
+ {
+ _trainingSet = BuildTrainingSet();
+ _classifier = BuildClassifier(_trainingSet);
+ }
+
+ ///
+ /// Builds the classifier.
+ ///
+ /// Classifier<StringClass, StringToken>.
+ private IClassifier BuildClassifier(ITrainingSetAccessor trainingSet)
+ {
+ var classifier = new NaiveClassifier(trainingSet)
+ {
+ // disable smoothing for exact probabilities
+ SmoothingAlpha = 0.0D
+ };
+
+ return classifier;
+ }
+
+ ///
+ /// Builds the training set.
+ ///
+ /// ITrainingSet<StringClass, StringToken>.
+ private static ITrainingSet BuildTrainingSet()
+ {
+ var trainingSet = new TrainingSet();
+
+ // build data sets
+ var spamSet = BuildSpamDataSet();
+ var hamSet = BuildHamDataSet();
+
+ // monkey test
+ //spamSet.SetSize.Should()
+ //.Be(hamSet.SetSize, "because this test relies on identical set sizes for exact probability testing");
+
+ // register classes
+ _spamClass = spamSet.Class;
+ _hamClass = hamSet.Class;
+
+ // add the sets and return
+ trainingSet.Add(spamSet, hamSet);
+ return trainingSet;
+ }
+
+ ///
+ /// Builds the spam data set.
+ ///
+ /// IDataSet<StringClass, StringToken>.
+ private static IDataSet BuildSpamDataSet()
+ {
+ return BuildDataSet("spam", 0.5D, "rolex", "watches", "viagra", "prince", "money", "send", "xyzzy");
+ }
+
+ ///
+ /// Builds the spam data set.
+ ///
+ /// IDataSet<StringClass, StringToken>.
+ private static IDataSet BuildHamDataSet()
+ {
+ return BuildDataSet("ham", 0.5D, "love", "flowers", "unicorn", "friendship", "money", "send", "send");
+ }
+
+ ///
+ /// Builds the data set.
+ ///
+ /// Name of the class.
+ /// The class probability.
+ /// The token.
+ /// The additional tokens.
+ /// IDataSet<StringClass, StringToken>.
+ private static IDataSet BuildDataSet(string className, double classProbability, string token, params string[] additionalTokens)
+ {
+ var @class = new StringClass(className, classProbability);
+ var dataSet = new DataSet(@class);
+
+ dataSet.AddToken(new StringToken(token));
+ dataSet.AddToken(additionalTokens.Select(t => new StringToken(t)));
+
+ return dataSet;
+ }
+
+ [TestMethod]
+ public void CalculateProbabilityReturnsOneHundredPercentForAKnownSpamWord()
+ {
+ var token = new StringToken("rolex");
+
+ var probability = _classifier.CalculateProbability(_spamClass, token);
+ //probability.Should().BeApproximately(1.0D, 0.0001D, "because the word is known the be a spam word");
+ }
+
+ [TestMethod]
+ public void CalculateProbabilityReturnsOneHundredPercentForAKnownHamWord()
+ {
+ var token = new StringToken("unicorn");
+
+ var probability = _classifier.CalculateProbability(_hamClass, token);
+ //probability.Should().BeApproximately(1.0D, 0.0001D, "because the word is known the be a ham word");
+ }
+
+ [TestMethod]
+ public void CalculateProbabilitiesWithHamWordReturnsProbabilitiesForAllClasses()
+ {
+ var token = new StringToken("unicorn");
+
+ var probabilities = _classifier.CalculateProbabilities(token).ToList();
+ /*probabilities.Single(p => p.Class.Equals(_spamClass))
+ .Probability.Should()
+ .BeApproximately(0D, 0.000001D, "because the token is known to be a ham word");
+
+ probabilities.Single(p => p.Class.Equals(_hamClass))
+ .Probability.Should()
+ .BeApproximately(1D, 0.000001D, "because the token is known to be a ham word");*/
+ }
+
+ [TestMethod]
+ public void CalculateProbabilitiesWithMixedWordReturnsProbabilitiesForAllClasses()
+ {
+ var token = new StringToken("money");
+
+ var probabilities = _classifier.CalculateProbabilities(token).ToList();
+ /*probabilities.Single(p => p.Class.Equals(_spamClass))
+ .Probability.Should()
+ .BeApproximately(0.5D, 0.000001D, "because the token is known to be a ham and spam word");
+
+ probabilities.Single(p => p.Class.Equals(_hamClass))
+ .Probability.Should()
+ .BeApproximately(0.5D, 0.000001D, "because the token is known to be a ham and spam word");*/
+ }
+
+ [TestMethod]
+ public void CalculateProbabilitiesWithMixedWordThatIsMoreLikelyHamThanSpamReturnsProbabilitiesForAllClasses()
+ {
+ var token = new StringToken("send");
+
+ var probabilities = _classifier.CalculateProbabilities(token).ToList();
+ /*probabilities.Single(p => p.Class.Equals(_spamClass))
+ .Probability.Should()
+ .BeApproximately(1 / 3D, 0.000001D, "because the token is more likely to be a ham than spam word");
+
+ probabilities.Single(p => p.Class.Equals(_hamClass))
+ .Probability.Should()
+ .BeApproximately(2 / 3D, 0.000001D, "because the token is more likely to be a ham than spam word");*/
+ }
+
+ [TestMethod]
+ public void CalculateProbabilitiesWithRareTokensAndSmoothingAlphaIsUnambiguous()
+ {
+ var token1 = new StringToken("rolex");
+ var token2 = new StringToken("unicorn");
+ var token3 = new StringToken("send");
+
+ const double smoothingAlpha = 1.0D;
+ var probabilities = _classifier.CalculateProbabilities(new IToken[] { token1, token2, token3 }, smoothingAlpha).ToList();
+
+ /*probabilities.Single(p => p.Class.Equals(_spamClass))
+ .Probability.Should()
+ .BeLessThan(0.5D, "because we used more ham than spam tokens");
+
+ probabilities.Single(p => p.Class.Equals(_hamClass))
+ .Probability.Should()
+ .BeGreaterThan(0.5D, "because we used more ham than spam tokens");*/
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/ClassBase.cs b/BotSharp.Algorithm/Bayesian/ClassBase.cs
new file mode 100644
index 00000000..47e95e6a
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/ClassBase.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Diagnostics;
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Class ClassBase.
+ ///
+ [DebuggerDisplay("Class {Name}, base P = {Probability}")]
+ public abstract class ClassBase : IClass
+ {
+ ///
+ /// Gets the name.
+ ///
+ /// The name.
+ public string Name { get; private set; }
+
+ ///
+ /// Gets the class' base probability.
+ ///
+ /// The probability.
+ public double Probability { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name.
+ /// The probability.
+ /// name
+ ///
+ /// probability;Class base probability must be greater than or equal to zero.
+ /// or
+ /// probability;Class base probability must be less than or equal to one.
+ ///
+ protected ClassBase(string name, double probability)
+ {
+ if (ReferenceEquals(name, null)) throw new ArgumentNullException("name");
+ if (probability < 0) throw new ArgumentOutOfRangeException("probability", "Class base probability must be greater than or equal to zero.");
+ if (probability > 1) throw new ArgumentOutOfRangeException("probability", "Class base probability must be less than or equal to one.");
+
+ Name = name;
+ Probability = probability;
+ }
+
+ ///
+ /// Indicates whether the current object is equal to another object of the same type.
+ ///
+ /// An object to compare with this object.
+ /// true if the current object is equal to the parameter; otherwise, false.
+ public abstract bool Equals(IClass other);
+ }
+}
\ No newline at end of file
diff --git a/BotSharp.Algorithm/Bayesian/CombinedConditionalProbabilities.cs b/BotSharp.Algorithm/Bayesian/CombinedConditionalProbabilities.cs
new file mode 100644
index 00000000..f5ee13fe
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/CombinedConditionalProbabilities.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Struct CombinedConditionalProbability
+ ///
+ [DebuggerDisplay("P({Class}|{TokenProbabilities.Count} tokens)={Probability}")]
+ public struct CombinedConditionalProbability
+ {
+ ///
+ /// The class
+ ///
+ public readonly IClass Class;
+
+ ///
+ /// The token probabilities
+ ///
+ public ICollection TokenProbabilities;
+
+ ///
+ /// The probability
+ ///
+ public double Probability;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The class.
+ /// The probability.
+ /// The tokenProbabilities.
+ public CombinedConditionalProbability(IClass @class, double probability, ICollection tokenProbabilities)
+ {
+ if (ReferenceEquals(@class, null)) throw new ArgumentNullException("class");
+ if (ReferenceEquals(tokenProbabilities, null)) throw new ArgumentNullException("tokenProbabilities");
+ if (probability < 0) throw new ArgumentOutOfRangeException("probability", probability, "Probability must greater than or equal to zero");
+ if (probability > 1) throw new ArgumentOutOfRangeException("probability", probability, "Probability must less than or equal to one");
+
+ Class = @class;
+ TokenProbabilities = tokenProbabilities;
+ Probability = probability;
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/ConditionalProbability.cs b/BotSharp.Algorithm/Bayesian/ConditionalProbability.cs
new file mode 100644
index 00000000..a1788c78
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/ConditionalProbability.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Diagnostics;
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Struct ConditionalProbability
+ ///
+ [DebuggerDisplay("P({Class}|{Token})={Probability}")]
+ public struct ConditionalProbability : IEquatable
+ {
+ ///
+ /// The class
+ ///
+ public readonly IClass Class;
+
+ ///
+ /// The token
+ ///
+ public readonly IToken Token;
+
+ ///
+ /// The conditional probability
+ ///
+ public readonly double Probability;
+
+ ///
+ /// The occurrence of the token during the training phase.
+ ///
+ public readonly long Occurrence;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The class.
+ /// The token.
+ /// The probability.
+ /// The occurrence.
+ /// @class
+ /// or
+ /// token
+ /// probability;Probability must greater than or equal to zero
+ /// or
+ /// probability;Probability must less than or equal to one
+ public ConditionalProbability(IClass @class, IToken token, double probability, long occurrence)
+ {
+ if (ReferenceEquals(@class, null)) throw new ArgumentNullException("class");
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+ if (probability < 0) throw new ArgumentOutOfRangeException("probability", probability, "Probability must greater than or equal to zero");
+ if (probability > 1) throw new ArgumentOutOfRangeException("probability", probability, "Probability must less than or equal to one");
+ if (probability < 0) throw new ArgumentOutOfRangeException("occurrence", occurrence, "Occurrence must greater than or equal to zero");
+
+ Class = @class;
+ Token = token;
+ Probability = probability;
+ Occurrence = occurrence;
+ }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ /// Another object to compare to.
+ /// if the specified is equal to this instance; otherwise, .
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(obj, null)) return false;
+ return obj is ConditionalProbability && Equals((ConditionalProbability) obj);
+ }
+
+ ///
+ /// Indicates whether the current object is equal to another object of the same type.
+ ///
+ /// An object to compare with this object.
+ /// true if the current object is equal to the parameter; otherwise, false.
+ public bool Equals(ConditionalProbability other)
+ {
+ return Class.Equals(other.Class)
+ && Token.Equals(other.Token)
+ && Probability.Equals(other.Probability);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ public override int GetHashCode()
+ {
+ var hash = 27;
+ hash = (13 * hash) + Class.GetHashCode();
+ hash = (13 * hash) + Token.GetHashCode();
+ hash = (13 * hash) + Probability.GetHashCode();
+ return hash;
+ }
+
+ ///
+ /// Returns a that represents this instance.
+ ///
+ /// A that represents this instance.
+ public override string ToString()
+ {
+ return String.Format("P({0}|{1})={2:P}", Class, Token, Probability);
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/DataSet.cs b/BotSharp.Algorithm/Bayesian/DataSet.cs
new file mode 100644
index 00000000..91a56ac7
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/DataSet.cs
@@ -0,0 +1,358 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Class DataSet.
+ ///
+ [DebuggerDisplay("Data set for class P({Class.Name})={Class.Probability}")]
+ public sealed class DataSet : IDataSet
+ {
+ ///
+ /// The default smoothing alpha
+ ///
+ public const double DefaultSmoothingAlpha = 0D;
+
+ ///
+ /// The token count
+ ///
+ private readonly ConcurrentDictionary _tokenCount = new ConcurrentDictionary();
+
+ ///
+ /// The set size, i.e. the number of all tokens
+ ///
+ private long _setSize;
+
+ ///
+ /// Gets the number of distinct tokens,
+ /// i.e. every token counted at exactly once.
+ ///
+ /// The token count.
+ ///
+ public long TokenCount
+ {
+ get { return _tokenCount.Count; }
+ }
+
+ ///
+ /// Gets the size of the set.
+ ///
+ /// The size of the set.
+ ///
+ public long SetSize
+ {
+ get { return _setSize; }
+ }
+
+ ///
+ /// Gets the class.
+ ///
+ /// The class.
+ public IClass Class { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The class.
+ /// @class
+ public DataSet(IClass @class)
+ {
+ if (ReferenceEquals(@class, null)) throw new ArgumentNullException("class");
+ Class = @class;
+ }
+
+ ///
+ /// Gets the with the specified token.
+ ///
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// TokenInformation<IToken>.
+ /// token
+ public TokenInformation this[IToken token, double alpha = DefaultSmoothingAlpha]
+ {
+ get
+ {
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+
+ long count;
+ if (!_tokenCount.TryGetValue(token, out count))
+ {
+ return new TokenInformation(token, 0L, 0D);
+ }
+
+ var percentage = GetPercentage(count, alpha);
+ return new TokenInformation(token, count, percentage);
+ }
+ }
+
+ ///
+ /// Gets the number of occurrences of the given token.
+ ///
+ /// The token.
+ /// System.Int64.
+ ///
+ public long GetCount(IToken token)
+ {
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+
+ long count;
+ return !_tokenCount.TryGetValue(token, out count) ? 0 : count;
+ }
+
+ ///
+ /// Gets the approximated percentage of the given
+ /// in this data set
+ /// by determining its occurrence count over the whole population.
+ ///
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// System.Double.
+ /// token
+ ///
+ public double GetPercentage(IToken token, double alpha = DefaultSmoothingAlpha)
+ {
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+ if (alpha < 0) throw new ArgumentOutOfRangeException("alpha", alpha, "Smoothing parameter alpha must be greater than or equal to zero.");
+
+ var count = GetCount(token);
+ return GetPercentage(count, alpha);
+ }
+
+ ///
+ /// Gets the approximated percentage of the given
+ /// in this data set
+ /// by determining its occurrence count over the whole population.
+ ///
+ /// The token count.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// System.Double.
+ /// token
+ ///
+ private double GetPercentage(long tokenCount, double alpha = DefaultSmoothingAlpha)
+ {
+ Debug.Assert(alpha >= 0, "alpha >= 0");
+ Debug.Assert(tokenCount >= 0, "tokenCount >= 0");
+
+ var totalCount = _setSize; // TODO: cache inverse set size
+ var vocabularySize = TokenCount;
+
+ return (double)(tokenCount + alpha)/(double)(totalCount + alpha*vocabularySize);
+ }
+
+ ///
+ /// Adds the given tokens a single time, incrementing the
+ /// and, at the first addition, the .
+ ///
+ /// The token.
+ /// The additional tokens.
+ ///
+ /// token
+ /// or
+ /// additionalTokens
+ ///
+ public void AddToken(IToken token, params IToken[] additionalTokens)
+ {
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+ if (ReferenceEquals(additionalTokens, null)) throw new ArgumentNullException("additionalTokens");
+
+ _tokenCount.AddOrUpdate(token, AddFirsIToken, IncremenITokenCount);
+ Interlocked.Increment(ref _setSize);
+
+ AddToken(additionalTokens);
+ }
+
+ ///
+ /// Adds the given tokens a single time, incrementing the
+ /// and, at the first addition, the .
+ ///
+ /// The tokens.
+ /// tokens
+ public void AddToken(IEnumerable tokens)
+ {
+ if (ReferenceEquals(tokens, null)) throw new ArgumentNullException("tokens");
+
+ foreach (var token in tokens)
+ {
+ _tokenCount.AddOrUpdate(token, AddFirsIToken, IncremenITokenCount);
+ Interlocked.Increment(ref _setSize);
+ }
+ }
+
+ ///
+ /// Removes the given tokens a single time, decrementing the and,
+ /// eventually, the .
+ ///
+ /// The token.
+ /// The additional tokens.
+ ///
+ /// token
+ /// or
+ /// additionalTokens
+ ///
+ ///
+ public void RemoveTokenOnce(IToken token, params IToken[] additionalTokens)
+ {
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+ if (ReferenceEquals(additionalTokens, null)) throw new ArgumentNullException("additionalTokens");
+
+ RemoveSingleTokenInternal(token);
+ RemoveTokenOnce(additionalTokens);
+ }
+
+ ///
+ /// Removes the given tokens a single time, decrementing the and,
+ /// eventually, the .
+ ///
+ /// The tokens.
+ /// tokens
+ ///
+ public void RemoveTokenOnce(IEnumerable tokens)
+ {
+ if (ReferenceEquals(tokens, null)) throw new ArgumentNullException("tokens");
+
+ foreach (var token in tokens)
+ {
+ RemoveSingleTokenInternal(token);
+ }
+ }
+
+ ///
+ /// Removes the given tokens a single time, decrementing the and,
+ /// eventually, the .
+ ///
+ /// The token.
+ /// The additional tokens.
+ ///
+ /// token
+ /// or
+ /// additionalTokens
+ ///
+ ///
+ public void PurgeToken(IToken token, params IToken[] additionalTokens)
+ {
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+ if (ReferenceEquals(additionalTokens, null)) throw new ArgumentNullException("additionalTokens");
+
+ PurgeTokenInternal(token);
+ PurgeToken(additionalTokens);
+ }
+
+ ///
+ /// Removes the given tokens a single time, decrementing the and,
+ /// eventually, the .
+ ///
+ /// The tokens.
+ /// tokens
+ ///
+ public void PurgeToken(IEnumerable tokens)
+ {
+ if (ReferenceEquals(tokens, null)) throw new ArgumentNullException("tokens");
+
+ foreach (var token in tokens)
+ {
+ PurgeTokenInternal(token);
+ }
+ }
+
+ ///
+ /// Purges the tokens fulfilling the given predicate.
+ ///
+ /// The predicate.
+ public void PurgeWhere(Predicate predicate)
+ {
+ var candidateForPurge = from pair in _tokenCount
+ let tokenCount = new TokenCount(pair.Key, pair.Value)
+ where predicate(tokenCount)
+ select pair.Key;
+ PurgeToken(candidateForPurge);
+ }
+
+ ///
+ /// Removes the single token internally.
+ ///
+ /// The token.
+ private void RemoveSingleTokenInternal(IToken token)
+ {
+ long count;
+ while (_tokenCount.TryGetValue(token, out count))
+ {
+ var newValue = count - 1;
+ var collectionUpdated = _tokenCount.TryUpdate(token, newValue: newValue, comparisonValue: count);
+ if (!collectionUpdated) continue;
+ Interlocked.Decrement(ref _setSize);
+
+ if (newValue == 0)
+ {
+ // explicit removal if the count is zero
+ var collection = _tokenCount as ICollection>;
+ collection.Remove(new KeyValuePair(token, 0));
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Purges a single token internally.
+ ///
+ /// The token.
+ private void PurgeTokenInternal(IToken token)
+ {
+ long count;
+ if (!_tokenCount.TryRemove(token, out count)) return;
+
+ // decrement 'count' times
+ // TODO: use Interlocked.CompareExchange
+ for (int i = 0; i < count; ++i)
+ {
+ Interlocked.Decrement(ref _setSize);
+ }
+ }
+
+ ///
+ /// Factory to initialize the value in for the given .
+ ///
+ /// The token.
+ /// System.Int64.
+ private static long AddFirsIToken(IToken token)
+ {
+ return 1;
+ }
+
+ ///
+ /// Factory to increment the value in for the given .
+ ///
+ /// The token.
+ /// The number of tokens.
+ /// System.Int64.
+ private static long IncremenITokenCount(IToken token, long count)
+ {
+ return count + 1;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// A that can be used to iterate through the collection.
+ public IEnumerator GetEnumerator()
+ {
+ return _tokenCount.Select(token => new TokenCount(token.Key, token.Value)).GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ /// An object that can be used to iterate through the collection.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/EmptyDataSet.cs b/BotSharp.Algorithm/Bayesian/EmptyDataSet.cs
new file mode 100644
index 00000000..a9a2690d
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/EmptyDataSet.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Class EmptyDataSet. This class cannot be inherited.
+ ///
+ internal sealed class EmptyDataSet : IDataSet
+ {
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// A that can be used to iterate through the collection.
+ public IEnumerator GetEnumerator()
+ {
+ yield break;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ /// An object that can be used to iterate through the collection.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// Gets the token count.
+ ///
+ /// The token count.
+ public long TokenCount { get { return 0; } }
+
+ ///
+ /// Gets the size of the set.
+ ///
+ /// The size of the set.
+ public long SetSize { get { return 0; } }
+
+ ///
+ /// Gets the class.
+ ///
+ /// The class.
+ public IClass Class { get; private set; }
+
+ ///
+ /// Gets the with the specified token.
+ ///
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// TokenInformation<IToken>.
+ public TokenInformation this[IToken token, double alpha = 0D]
+ {
+ get { return new TokenInformation(token, 0L, 0D); }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The class.
+ /// class
+ public EmptyDataSet(IClass @class)
+ {
+ if (ReferenceEquals(null, @class)) throw new ArgumentNullException("class");
+ Class = @class;
+ }
+
+ ///
+ /// Gets the count.
+ ///
+ /// The token.
+ /// System.Int64.
+ public long GetCount(IToken token)
+ {
+ return 0L;
+ }
+
+ ///
+ /// Gets the percentage.
+ ///
+ /// The token.
+ /// The alpha.
+ /// System.Double.
+ ///
+ public double GetPercentage(IToken token, double alpha = 0)
+ {
+ return 0D;
+ }
+
+ ///
+ /// Adds the token.
+ ///
+ /// The token.
+ /// The additional tokens.
+ /// Adding data to the empty data set is not allowed.
+ public void AddToken(IToken token, params IToken[] additionalTokens)
+ {
+ throw new InvalidOperationException("Adding data to the empty data set is not allowed.");
+ }
+
+ ///
+ /// Adds the token.
+ ///
+ /// The tokens.
+ /// Adding data to the empty data set is not allowed.
+ public void AddToken(IEnumerable tokens)
+ {
+ throw new InvalidOperationException("Adding data to the empty data set is not allowed.");
+ }
+
+ ///
+ /// Removes the token once.
+ ///
+ /// The token.
+ /// The additional tokens.
+ public void RemoveTokenOnce(IToken token, params IToken[] additionalTokens)
+ {
+ }
+
+ ///
+ /// Removes the token once.
+ ///
+ /// The tokens.
+ public void RemoveTokenOnce(IEnumerable tokens)
+ {
+ }
+
+ ///
+ /// Purges the token.
+ ///
+ /// The token.
+ /// The additional tokens.
+ public void PurgeToken(IToken token, params IToken[] additionalTokens)
+ {
+ }
+
+ ///
+ /// Purges the token.
+ ///
+ /// The tokens.
+ public void PurgeToken(IEnumerable tokens)
+ {
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/IClass.cs b/BotSharp.Algorithm/Bayesian/IClass.cs
new file mode 100644
index 00000000..e0d9c0fd
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/IClass.cs
@@ -0,0 +1,25 @@
+using System;
+using System.ComponentModel;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Interface IClass
+ ///
+ public interface IClass : IEquatable
+ {
+ ///
+ /// Gets the name.
+ ///
+ /// The name.
+ string Name { get; }
+
+ ///
+ /// Gets or sets the class' base probability.
+ ///
+ /// The probability.
+ [DefaultValue(1)]
+ double Probability { get; set; }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/IClassifier.cs b/BotSharp.Algorithm/Bayesian/IClassifier.cs
new file mode 100644
index 00000000..f1a2fa79
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/IClassifier.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Interface IClassifier
+ ///
+ public interface IClassifier
+ {
+ ///
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ ///
+ /// Laplace smoothing is required in the context or rare (i.e. untrained) tokens or tokens
+ /// that do not appear in some classes. With smoothing disabled, these tokens result
+ /// in a zero probability for the whole class. To counter that, a positive ("alpha")
+ /// value for smoothing can be set.
+ ///
+ ///
+ double SmoothingAlpha { get; set; }
+
+ ///
+ /// Calculates the probability of having the
+ /// given the occurrence of the .
+ ///
+ /// The class under test.
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied, setting to defaults to the values set in .
+ /// System.Double.
+ double CalculateProbability(IClass classUnderTest, IToken token, double? alpha = null);
+
+ ///
+ /// Calculates the probability of having the
+ ///
+ /// given the occurrence of the
+ /// .
+ ///
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied, setting to defaults to the values set in .
+ /// System.Double.
+ IEnumerable CalculateProbabilities(IToken token, double? alpha = null);
+
+ ///
+ /// Calculates the probability of having the
+ ///
+ /// given the occurrence of the
+ /// .
+ ///
+ /// The tokens.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied, setting to defaults to the values set in .
+ /// System.Double.
+ IEnumerable CalculateProbabilities(ICollection tokens, double? alpha = null);
+ }
+}
\ No newline at end of file
diff --git a/BotSharp.Algorithm/Bayesian/IDataSet.cs b/BotSharp.Algorithm/Bayesian/IDataSet.cs
new file mode 100644
index 00000000..f9118c50
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/IDataSet.cs
@@ -0,0 +1,9 @@
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Interface IDataSet
+ ///
+ public interface IDataSet : IDataSetAccessor, ITokenRegistration
+ {
+ }
+}
\ No newline at end of file
diff --git a/BotSharp.Algorithm/Bayesian/IDataSetAccessor.cs b/BotSharp.Algorithm/Bayesian/IDataSetAccessor.cs
new file mode 100644
index 00000000..3278bb60
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/IDataSetAccessor.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Interface IDataSetAccessor
+ ///
+ public interface IDataSetAccessor : IEnumerable
+ {
+ ///
+ /// Gets the number of distinct tokens,
+ /// i.e. every token counted at exactly once.
+ ///
+ /// The token count.
+ ///
+ long TokenCount { get; }
+
+ ///
+ /// Gets the size of the set.
+ ///
+ /// The size of the set.
+ ///
+ long SetSize { get; }
+
+ ///
+ /// Gets the class.
+ ///
+ /// The class.
+ IClass Class { get; }
+
+ ///
+ /// Gets the with the specified token.
+ ///
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// TokenInformation<IToken>.
+ /// token
+ TokenInformation this[IToken token, double alpha] { get; }
+
+ ///
+ /// Gets the number of occurrences of the given token.
+ ///
+ /// The token.
+ /// System.Int64.
+ /// token
+ ///
+ long GetCount(IToken token);
+
+ ///
+ /// Gets the approximated percentage of the given
+ /// in this data set
+ /// by determining its occurrence count over the whole population.
+ ///
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// System.Double.
+ /// token
+ ///
+ double GetPercentage(IToken token, double alpha);
+ }
+}
\ No newline at end of file
diff --git a/BotSharp.Algorithm/Bayesian/IToken.cs b/BotSharp.Algorithm/Bayesian/IToken.cs
new file mode 100644
index 00000000..165191ab
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/IToken.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Interface IToken
+ ///
+ public interface IToken : IEquatable
+ {
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/ITokenRegistration.cs b/BotSharp.Algorithm/Bayesian/ITokenRegistration.cs
new file mode 100644
index 00000000..bf2e53d9
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/ITokenRegistration.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Interface ITokenRegistration
+ ///
+ public interface ITokenRegistration
+ {
+ ///
+ /// Adds the given tokens a single time, incrementing the
+ /// and, at the first addition, the .
+ ///
+ /// The token.
+ /// The additional tokens.
+ ///
+ /// token
+ /// or
+ /// additionalTokens
+ ///
+ void AddToken(IToken token, params IToken[] additionalTokens);
+
+ ///
+ /// Adds the given tokens a single time, incrementing the
+ /// and, at the first addition, the .
+ ///
+ /// The tokens.
+ /// tokens
+ void AddToken(IEnumerable tokens);
+
+ ///
+ /// Removes the given tokens a single time, decrementing the and,
+ /// eventually, the .
+ ///
+ /// The token.
+ /// The additional tokens.
+ ///
+ /// token
+ /// or
+ /// additionalTokens
+ ///
+ ///
+ void RemoveTokenOnce(IToken token, params IToken[] additionalTokens);
+
+ ///
+ /// Removes the given tokens a single time, decrementing the and,
+ /// eventually, the .
+ ///
+ /// The tokens.
+ /// tokens
+ ///
+ void RemoveTokenOnce(IEnumerable tokens);
+
+ ///
+ /// Removes the given tokens a single time, decrementing the and,
+ /// eventually, the .
+ ///
+ /// The token.
+ /// The additional tokens.
+ ///
+ /// token
+ /// or
+ /// additionalTokens
+ ///
+ ///
+ void PurgeToken(IToken token, params IToken[] additionalTokens);
+
+ ///
+ /// Removes the given tokens a single time, decrementing the and,
+ /// eventually, the .
+ ///
+ /// The tokens.
+ /// tokens
+ ///
+ void PurgeToken(IEnumerable tokens);
+ }
+}
\ No newline at end of file
diff --git a/BotSharp.Algorithm/Bayesian/ITrainingSet.cs b/BotSharp.Algorithm/Bayesian/ITrainingSet.cs
new file mode 100644
index 00000000..dd6b0353
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/ITrainingSet.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Interface ITrainingSet
+ ///
+ public interface ITrainingSet : ITrainingSetAccessor
+ {
+ ///
+ /// Adds the specified data set.
+ ///
+ /// The data set.
+ /// The additional data sets.
+ /// dataSet
+ /// A data set for a given class was already registered.
+ void Add(IDataSet dataSet, params IDataSet[] additionalDataSets);
+
+ ///
+ /// Adds the specified data sets.
+ ///
+ /// The data sets.
+ /// dataSets
+ /// A data set for a given class was already registered.
+ void Add(IEnumerable dataSets);
+ }
+}
\ No newline at end of file
diff --git a/BotSharp.Algorithm/Bayesian/ITrainingSetAccessor.cs b/BotSharp.Algorithm/Bayesian/ITrainingSetAccessor.cs
new file mode 100644
index 00000000..3eebfe42
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/ITrainingSetAccessor.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Interface ITrainingSetAccerssor
+ ///
+ public interface ITrainingSetAccessor : IEnumerable
+ {
+ ///
+ /// Gets the with the specified class.
+ ///
+ /// The class.
+ /// IDataSet<TClass, TToken>.
+ /// No data set was registered for the given class;class
+ IDataSet this[IClass @class] { get; }
+ }
+}
\ No newline at end of file
diff --git a/BotSharp.Algorithm/Bayesian/LinqExtensions.cs b/BotSharp.Algorithm/Bayesian/LinqExtensions.cs
new file mode 100644
index 00000000..d97440ac
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/LinqExtensions.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ internal static class LinqExtensions
+ {
+ ///
+ /// Converts an enumerable to a collection
+ ///
+ ///
+ /// The enumerable.
+ /// ICollection<T>.
+ public static ICollection ToCollection(this IEnumerable enumerable)
+ {
+ var type = enumerable.GetType();
+ if (type.IsGenericCollectionType()) return (ICollection)enumerable;
+
+ var collection = new Collection();
+ foreach (var t in enumerable)
+ {
+ collection.Add(t);
+ }
+ return collection;
+ }
+
+ ///
+ /// Forces evaluation of the enumerable
+ ///
+ ///
+ /// The enumerable.
+ public static void Run(this IEnumerable enumerable)
+ {
+ var type = enumerable.GetType();
+ if (type.IsGenericCollectionType()) return;
+
+ foreach (var item in enumerable)
+ {
+ }
+ }
+
+ ///
+ /// The cache for
+ ///
+ private static readonly ConcurrentDictionary IsGenericCollectionTypeCache = new ConcurrentDictionary();
+
+ ///
+ /// Determines whether the specified type is a (generic) collection.
+ ///
+ /// The type.
+ /// true if the specified type is collection; otherwise, false.
+ public static bool IsGenericCollectionType(this Type type)
+ {
+ return IsGenericCollectionTypeCache.GetOrAdd(type, t => type.GetInterfaces()
+ .Any(ti => ti.IsGenericType
+ &&
+ ti.GetGenericTypeDefinition() ==
+ typeof (ICollection<>)));
+
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/NaiveClassifier.cs b/BotSharp.Algorithm/Bayesian/NaiveClassifier.cs
new file mode 100644
index 00000000..f045503d
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/NaiveClassifier.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Class NaiveClassifier. This class cannot be inherited.
+ ///
+ /// Assumes that all token occurrences are statistically independent.
+ ///
+ ///
+ public sealed class NaiveClassifier : IClassifier
+ {
+ ///
+ /// The training sets
+ ///
+ private readonly ITrainingSetAccessor _trainingSets;
+
+ ///
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ ///
+ private double _smoothingAlpha = 0.01D;
+
+ ///
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ ///
+ [DefaultValue(0.01D)]
+ public double SmoothingAlpha
+ {
+ get { return _smoothingAlpha; }
+ set
+ {
+ if (value <= 0) throw new ArgumentOutOfRangeException("value", value, "Value must be greater than zero.");
+ _smoothingAlpha = value;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The training sets.
+ /// trainingSets
+ public NaiveClassifier(ITrainingSetAccessor trainingSets)
+ {
+ if (ReferenceEquals(trainingSets, null)) throw new ArgumentNullException("trainingSets");
+ _trainingSets = trainingSets;
+ }
+
+ ///
+ /// Calculates the probability of having the
+ /// given the occurrence of the .
+ ///
+ /// The class under test.
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// System.Double.
+ public double CalculateProbability(IClass classUnderTest, IToken token, double? alpha = null)
+ {
+ var smoothingAlpha = alpha ?? _smoothingAlpha;
+
+ ICollection remainingSets;
+ var setForClassUnderTest = SplitDataSets(classUnderTest, out remainingSets);
+
+ // calculate the token's probability in the class under test
+ var percentageInClassUnderTest = setForClassUnderTest.GetPercentage(token, smoothingAlpha);
+ var probabilityInClassUnderTest = percentageInClassUnderTest * classUnderTest.Probability;
+
+ // calculate the token's probabilities for the remaining classes
+ double sumOfRemainingProbabilites;
+ CalculateTokenProbabilityGivenClass(token, remainingSets, out sumOfRemainingProbabilites, smoothingAlpha).Run();
+
+ // calculate total probability
+ var totalProbability = probabilityInClassUnderTest + sumOfRemainingProbabilites;
+
+ // calculate the class' probability given the token
+ var probabilityForClass = probabilityInClassUnderTest/totalProbability;
+
+ // correct for rare words
+ return probabilityForClass;
+ }
+
+ ///
+ /// Calculates the probability of having the
+ ///
+ /// given the occurrence of the
+ /// .
+ ///
+ /// The token.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// System.Double.
+ public IEnumerable CalculateProbabilities(IToken token, double? alpha = null)
+ {
+ var smoothingAlpha = alpha ?? _smoothingAlpha;
+
+ // calculate the token's probabilities for all classes
+ double totalProbability;
+ var probabilities = CalculateTokenProbabilityGivenClass(token, _trainingSets, out totalProbability, smoothingAlpha);
+
+ // apply Bayes theorem
+ var inverseOfTotalProbability = 1.0D/totalProbability;
+ return from cp in probabilities
+ let conditionalProbability = cp.Probability * inverseOfTotalProbability
+ select new ConditionalProbability(cp.Class, cp.Token, conditionalProbability, cp.Occurrence);
+ }
+
+ ///
+ /// Calculates the probability of having the
+ ///
+ /// given the occurrence of the
+ /// .
+ ///
+ /// The tokens.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// System.Double.
+ public IEnumerable CalculateProbabilities(ICollection tokens, double? alpha = null)
+ {
+ var smoothingAlpha = alpha ?? _smoothingAlpha;
+
+ var cpgs = tokens
+ .SelectMany(token => CalculateProbabilities(token, smoothingAlpha))
+ .GroupBy(cp => cp.Class)
+ .ToCollection();
+
+ return from @group in cpgs
+ let cps = @group.ToCollection()
+ let eta = cps.Select(cp => cp.Probability)
+ .Sum(p => Math.Log(1 - p) - Math.Log(p))
+ let probability = 1/(1 + Math.Exp(eta))
+ select new CombinedConditionalProbability(@group.Key, probability, cps);
+ }
+
+ ///
+ /// Calculates the token probabilities given a class.
+ ///
+ /// The token.
+ /// The sets.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// IEnumerable<ConditionalProbability<IClass, IToken>>.
+ private IEnumerable CalculateTokenProbabilityGivenClass(IToken token, IEnumerable sets, double alpha)
+ {
+ return from set in sets
+ let @class = set.Class
+ let classProbability = @class.Probability
+ let percentageInClass = set.GetPercentage(token, alpha)
+ let countInClass = set.GetCount(token)
+ let probabilityInClass = percentageInClass*classProbability
+ select new ConditionalProbability(@class, token, probabilityInClass, countInClass);
+ }
+
+ ///
+ /// Calculates the token probabilities given a class.
+ ///
+ /// The token.
+ /// The sets.
+ /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied.
+ /// The total probability for the given classes.
+ /// IEnumerable<ConditionalProbability<IClass, IToken>>.
+ private IEnumerable CalculateTokenProbabilityGivenClass(IToken token, IEnumerable sets, out double totalProbability, double alpha)
+ {
+ var probabilities = CalculateTokenProbabilityGivenClass(token, sets, alpha).ToCollection();
+ totalProbability = probabilities.Sum(p => p.Probability);
+ return probabilities;
+ }
+
+ ///
+ /// Splits the data sets.
+ ///
+ /// The class under test.
+ /// The remaining sets.
+ /// IDataSet<IClass, IToken>.
+ private IDataSetAccessor SplitDataSets(IClass classUnderTest, out ICollection remainingSets)
+ {
+ IDataSet setForClassUnderTest = null;
+ remainingSets = new Collection();
+
+ // split data sets by selected class and other classes
+ foreach (var trainingSet in _trainingSets)
+ {
+ // select the set for the class under test
+ if (trainingSet.Class.Equals(classUnderTest))
+ {
+ Debug.Assert(setForClassUnderTest == null,
+ "The class under test must not have multiple sets registered in the DataSet");
+ setForClassUnderTest = trainingSet;
+ continue;
+ }
+
+ // select remaining sets
+ remainingSets.Add(trainingSet);
+ }
+
+ // return the found set or an empty set
+ return setForClassUnderTest ?? new EmptyDataSet(classUnderTest);
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/StringClass.cs b/BotSharp.Algorithm/Bayesian/StringClass.cs
new file mode 100644
index 00000000..7f0b9fa1
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/StringClass.cs
@@ -0,0 +1,84 @@
+using System;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Class StringClass. This class cannot be inherited.
+ ///
+ public sealed class StringClass : ClassBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name.
+ /// The probability.
+ /// name
+ ///
+ /// probability;Class base probability must be greater than or equal to zero.
+ /// or
+ /// probability;Class base probability must be less than or equal to one.
+ ///
+ public StringClass(string name, double probability)
+ : base(name, probability)
+ {
+ }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ /// The to compare with the current .
+ /// if the specified is equal to this instance; otherwise, .
+ public override bool Equals(IClass other)
+ {
+ var otherAsObject = (object) other;
+ return Equals(otherAsObject);
+ }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ /// The to compare with the current .
+ /// if the specified is equal to this instance; otherwise, .
+ private bool Equals(StringClass other)
+ {
+ if (ReferenceEquals(other, null)) return false;
+ if (ReferenceEquals(other, this)) return true;
+ return String.Equals(Name, other.Name)
+ && Math.Abs(Probability - other.Probability) < 0.0001D;
+ }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ /// The to compare with the current .
+ /// if the specified is equal to this instance; otherwise, .
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ return obj is StringClass && Equals((StringClass) obj);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ public override int GetHashCode()
+ {
+ var hash = 27;
+ hash = (13 * hash) + Name.GetHashCode();
+ hash = (13 * hash) + Probability.GetHashCode();
+ return hash;
+ }
+
+ ///
+ /// Returns a that represents this instance.
+ ///
+ /// A that represents this instance.
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/StringToken.cs b/BotSharp.Algorithm/Bayesian/StringToken.cs
new file mode 100644
index 00000000..6f19633c
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/StringToken.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Diagnostics;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Class StringToken. This class cannot be inherited.
+ ///
+ [DebuggerDisplay("{Value}")]
+ public sealed class StringToken : IToken
+ {
+ ///
+ /// Gets the value.
+ ///
+ /// The value.
+ public string Value { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value.
+ public StringToken(string value)
+ {
+ if (ReferenceEquals(value, null)) throw new ArgumentNullException("value");
+ Value = value;
+ }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ /// The to compare with the current .
+ /// if the specified is equal to this instance; otherwise, .
+ private bool Equals(StringToken other)
+ {
+ return string.Equals(Value, other.Value);
+ }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ /// The to compare with the current .
+ /// if the specified is equal to this instance; otherwise, .
+ bool IEquatable.Equals(IToken other)
+ {
+ var otherAsObject = (object)other;
+ return Equals(otherAsObject);
+ }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ /// The to compare with the current .
+ /// if the specified is equal to this instance; otherwise, .
+ public override bool Equals(object obj)
+ {
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ return obj is StringToken && Equals((StringToken) obj);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ public override int GetHashCode()
+ {
+ return Value.GetHashCode();
+ }
+
+ ///
+ /// Returns a that represents this instance.
+ ///
+ /// A that represents this instance.
+ public override string ToString()
+ {
+ return Value;
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/TokenCount.cs b/BotSharp.Algorithm/Bayesian/TokenCount.cs
new file mode 100644
index 00000000..bc72d351
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/TokenCount.cs
@@ -0,0 +1,37 @@
+using System;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Struct TokenCount
+ ///
+ public struct TokenCount
+ {
+ ///
+ /// The token
+ ///
+ public readonly IToken Token;
+
+ ///
+ /// The number of occurrences
+ ///
+ public readonly long Count;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The token.
+ /// The count.
+ /// token
+ /// count;Count must be positive or zero.
+ public TokenCount(IToken token, long count)
+ {
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+ if (count < 0) throw new ArgumentOutOfRangeException("count", count, "Count must be positive or zero.");
+
+ Token = token;
+ Count = count;
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/TokenInformation.cs b/BotSharp.Algorithm/Bayesian/TokenInformation.cs
new file mode 100644
index 00000000..44fbad95
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/TokenInformation.cs
@@ -0,0 +1,50 @@
+using System;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Struct TokenInformation
+ ///
+ public struct TokenInformation
+ where TToken: IToken
+ {
+ ///
+ /// The token
+ ///
+ public readonly TToken Token;
+
+ ///
+ /// The count in the class
+ ///
+ public long Count;
+
+ ///
+ /// The occurrence percentage of the token in the class.
+ ///
+ public double Percentage;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The token.
+ /// The count.
+ /// The percentage.
+ /// token
+ ///
+ /// count;Count must be positive or zero
+ /// or
+ /// percentage;Percentage must be positive or zero
+ ///
+ public TokenInformation(TToken token, long count, double percentage)
+ {
+ if (ReferenceEquals(token, null)) throw new ArgumentNullException("token");
+ if (count < 0) throw new ArgumentOutOfRangeException("count", count, "Count must be positive or zero");
+ if (percentage < 0) throw new ArgumentOutOfRangeException("percentage", percentage, "Percentage must be positive or zero");
+
+ Token = token;
+ Count = count;
+ Percentage = percentage;
+ }
+ }
+}
diff --git a/BotSharp.Algorithm/Bayesian/TrainingSet.cs b/BotSharp.Algorithm/Bayesian/TrainingSet.cs
new file mode 100644
index 00000000..8d9984fc
--- /dev/null
+++ b/BotSharp.Algorithm/Bayesian/TrainingSet.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+
+namespace BotSharp.Algorithm.Bayesian
+{
+ ///
+ /// Class TrainingSet. This class cannot be inherited.
+ ///
+ public sealed class TrainingSet : ITrainingSet
+ {
+ ///
+ /// The data sets
+ ///
+ private readonly ConcurrentDictionary _dataSets = new ConcurrentDictionary();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TrainingSet()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The data sets.
+ public TrainingSet(IEnumerable dataSets)
+ {
+ Add(dataSets);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The data set.
+ /// The additional data sets.
+ public TrainingSet(IDataSet dataSet, params IDataSet[] additionalDataSets)
+ {
+ Add(dataSet, additionalDataSets);
+ }
+
+ ///
+ /// Gets the with the specified class.
+ ///
+ /// The class.
+ /// IDataSet<IClass, IToken>.
+ /// No data set was registered for the given class;class
+ public IDataSet this[IClass @class]
+ {
+ get
+ {
+ IDataSet set;
+ if (_dataSets.TryGetValue(@class, out set)) return set;
+ throw new ArgumentException("No data set was registered for the given class", "class");
+ }
+ }
+
+ ///
+ /// Adds the specified data set.
+ ///
+ /// The data set.
+ /// The additional data sets.
+ /// dataSet
+ /// A data set for a given class was already registered.
+ public void Add(IDataSet dataSet, params IDataSet[] additionalDataSets)
+ {
+ if (ReferenceEquals(dataSet, null)) throw new ArgumentNullException("dataSet");
+
+ try
+ {
+ AddInternal(dataSet);
+ }
+ catch (ArgumentException e)
+ {
+ throw new ArgumentException("A data set for a given class was already registered.", e);
+ }
+
+ // may throw, that's anticipated
+ Add(additionalDataSets);
+ }
+
+ ///
+ /// Adds the specified data sets.
+ ///
+ /// The data sets.
+ /// dataSets
+ /// A data set for a given class was already registered.
+ public void Add(IEnumerable dataSets)
+ {
+ if (ReferenceEquals(dataSets, null)) throw new ArgumentNullException("dataSets");
+
+ try
+ {
+ foreach (var dataSet in dataSets)
+ {
+ AddInternal(dataSet);
+ }
+ }
+ catch (ArgumentException e)
+ {
+ throw new ArgumentException("A data set for a given class was already registered.", e);
+ }
+ }
+
+
+ ///
+ /// Adds the data set internally.
+ ///
+ /// The data set.
+ /// Data set for the given class was already registered.
+ private void AddInternal(IDataSet dataSet)
+ {
+ if (!_dataSets.TryAdd(dataSet.Class, dataSet))
+ {
+ throw new ArgumentException("Data set for the given class was already registered.");
+ }
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// A that can be used to iterate through the collection.
+ public IEnumerator GetEnumerator()
+ {
+ return _dataSets.Select(dataSet => dataSet.Value).GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ /// An object that can be used to iterate through the collection.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}