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(); } } }