<commit_before>﻿// Spines.Mahjong.Analysis.Analyzer.cs
// 
// Copyright (C) 2016  <NAME>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System.Collections.Generic;
using System.Linq;

namespace Spines.Mahjong.Analysis.Combinations
{
  internal class Analyzer
  {
    private readonly ISet<Arrangement> _arrangements = new HashSet<Arrangement>();
    private readonly List<int> _concealed;
    private readonly int _meldCount;

    private readonly IReadOnlyList<ProtoGroup> _protoGroups = new List<ProtoGroup>
    {
      ProtoGroup.Jantou2,
      ProtoGroup.Jantou1,
      ProtoGroup.Shuntsu111,
      ProtoGroup.Shuntsu110,
      ProtoGroup.Shuntsu101,
      ProtoGroup.Shuntsu011,
      ProtoGroup.Shuntsu100,
      ProtoGroup.Shuntsu010,
      ProtoGroup.Shuntsu001,
      ProtoGroup.Koutsu3,
      ProtoGroup.Koutsu2,
      ProtoGroup.Koutsu1
    };

    private readonly List<int> _used;

    /// <summary>
    /// Creates a new instance of Analyzer.
    /// </summary>
    public Analyzer(Combination concealedTiles, Combination meldedTiles, int meldCount)
    {
      _meldCount = meldCount;
      _concealed = concealedTiles.Counts.ToList();
      _used = meldedTiles.Counts.ToList();
    }

    /// <summary>
    /// Returns all possible arrangements for the given hand.
    /// </summary>
    public IEnumerable<Arrangement> Analyze()
    {
      var totalTiles = _concealed.Sum();
      var arrangement = new Arrangement(0, _meldCount, _meldCount * 3);
      Analyze(arrangement, 0, 0);
      return _arrangements.Where(a => !_arrangements.Any(other => IsWorseThan(a, other, totalTiles)));
    }

    /// <summary>
    /// Determines whether an arrangement is worse than another.
    /// Correctness pending.
    /// </summary>
    private static bool IsWorseThan(Arrangement lhs, Arrangement rhs, int tileCount)
    {
      // Not enough tiles in other suits to reach the same value.
      var tilesInOtherSuits = 14 - tileCount;
      if (lhs.Value + tilesInOtherSuits < rhs.Value)
      {
        return true;
      }
      // Not enough unused groups to reach the same value in other suits.
      var maxWithUnusedGroups = (4 - lhs.Mentsu) * 3 + (1 - lhs.Jantou) * 2;
      if (lhs.Value + maxWithUnusedGroups < rhs.Value)
      {
        return true;
      }
      // Equal pairs.
      if (lhs.Jantou == rhs.Jantou)
      {
        // Perfect with more mentsu is better than perfect with less mentsu.
        if (lhs.Mentsu < rhs.Mentsu)
        {
          return IsPerfect(lhs) && IsPerfect(rhs);
        }
        // Same value with more mentsu is worse. With a larger mentsu difference, difference in value increases.
        return lhs.Value - lhs.Mentsu < rhs.Value - rhs.Mentsu;
      }
      // Same value with more mentsu or pairs is worse.
      if (lhs.Jantou == 1 && lhs.Mentsu >= rhs.Mentsu)
      {
        return lhs.Value <= rhs.Value;
      }
      // less pairs
      return false;
    }

    private static bool IsPerfect(Arrangement arrangement)
    {
      return arrangement.Value == arrangement.Mentsu * 3 + arrangement.Jantou * 2;
    }

    private void Analyze(Arrangement arrangement, int currentTileType, int currentProtoGroup)
    {
      if (currentTileType >= 9)
      {
        _arrangements.Add(arrangement);
        return;
      }

      Analyze(arrangement, currentTileType + 1, 0);

      for (var i = currentProtoGroup; i < _protoGroups.Count; ++i)
      {
        var protoGroup = _protoGroups[i];
        if (CanNotInsert(arrangement, currentTileType, protoGroup))
        {
          continue;
        }
        protoGroup.Insert(_concealed, _used, currentTileType);
        var added = protoGroup.IsJantou
          ? arrangement.AddJantou(protoGroup.Value)
          : arrangement.AddMentsu(protoGroup.Value);
        Analyze(added, currentTileType, i);
        protoGroup.Remove(_concealed, _used, currentTileType);
      }
    }

    private bool CanNotInsert(Arrangement arrangement, int currentTileType, ProtoGroup protoGroup)
    {
      if (protoGroup.IsJantou && arrangement.Jantou > 0)
      {
        return true;
      }
      if (!protoGroup.IsJantou && arrangement.Mentsu > 3)
      {
        return true;
      }
      return !protoGroup.CanInsert(_concealed, _used, currentTileType);
    }
  }
}<commit_msg>Performance<commit_after>﻿// Spines.Mahjong.Analysis.Analyzer.cs
// 
// Copyright (C) 2016  <NAME>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System.Collections.Generic;
using System.Linq;

namespace Spines.Mahjong.Analysis.Combinations
{
  internal class Analyzer
  {
    private readonly ISet<Arrangement> _arrangements = new HashSet<Arrangement>();
    private readonly List<int> _concealed;
    private readonly int _meldCount;

    private readonly IReadOnlyList<ProtoGroup> _protoGroups = new List<ProtoGroup>
    {
      ProtoGroup.Jantou2,
      ProtoGroup.Jantou1,
      ProtoGroup.Shuntsu111,
      ProtoGroup.Shuntsu110,
      ProtoGroup.Shuntsu101,
      ProtoGroup.Shuntsu011,
      ProtoGroup.Shuntsu100,
      ProtoGroup.Shuntsu010,
      ProtoGroup.Shuntsu001,
      ProtoGroup.Koutsu3,
      ProtoGroup.Koutsu2,
      ProtoGroup.Koutsu1
    };

    private readonly List<int> _used;

    /// <summary>
    /// Creates a new instance of Analyzer.
    /// </summary>
    public Analyzer(Combination concealedTiles, Combination meldedTiles, int meldCount)
    {
      _meldCount = meldCount;
      _concealed = concealedTiles.Counts.ToList();
      _used = meldedTiles.Counts.ToList();
    }

    /// <summary>
    /// Returns all possible arrangements for the given hand.
    /// </summary>
    public IEnumerable<Arrangement> Analyze()
    {
      var totalTiles = _concealed.Sum();
      var arrangement = new Arrangement(0, _meldCount, _meldCount * 3);
      Analyze(arrangement, 0, 0);
      return _arrangements.Where(a => !_arrangements.Any(other => IsWorseThan(a, other, totalTiles)));
    }

    /// <summary>
    /// Determines whether an arrangement is worse than another.
    /// Correctness pending.
    /// </summary>
    private static bool IsWorseThan(Arrangement lhs, Arrangement rhs, int tileCount)
    {
      // Not enough tiles in other suits to reach the same value.
      var tilesInOtherSuits = 14 - tileCount;
      if (lhs.Value + tilesInOtherSuits < rhs.Value)
      {
        return true;
      }
      // Not enough unused groups to reach the same value in other suits.
      var maxWithUnusedGroups = (4 - lhs.Mentsu) * 3 + (1 - lhs.Jantou) * 2;
      if (lhs.Value + maxWithUnusedGroups < rhs.Value)
      {
        return true;
      }
      // Equal pairs.
      if (lhs.Jantou == rhs.Jantou)
      {
        // Perfect with more mentsu is better than perfect with less mentsu.
        if (lhs.Mentsu < rhs.Mentsu)
        {
          return IsPerfect(lhs) && IsPerfect(rhs);
        }
        // Same value with more mentsu is worse. With a larger mentsu difference, difference in value increases.
        return lhs.Value - lhs.Mentsu < rhs.Value - rhs.Mentsu;
      }
      // Same value with more mentsu or pairs is worse.
      if (lhs.Jantou == 1 && lhs.Mentsu >= rhs.Mentsu)
      {
        return lhs.Value <= rhs.Value;
      }
      // less pairs
      return false;
    }

    private static bool IsPerfect(Arrangement arrangement)
    {
      return arrangement.Value == arrangement.Mentsu * 3 + arrangement.Jantou * 2;
    }

    private void Analyze(Arrangement arrangement, int currentTileType, int currentProtoGroup)
    {
      if (currentTileType >= 9)
      {
        _arrangements.Add(arrangement);
        return;
      }

      Analyze(arrangement, currentTileType + 1, 0);

      for (var i = currentProtoGroup; i < _protoGroups.Count; ++i)
      {
        var protoGroup = _protoGroups[i];
        if (CanNotInsert(arrangement, currentTileType, protoGroup))
        {
          continue;
        }
        protoGroup.Insert(_concealed, _used, currentTileType);
        var added = protoGroup.IsJantou
          ? arrangement.AddJantou(protoGroup.Value)
          : arrangement.AddMentsu(protoGroup.Value);
        Analyze(added, currentTileType, i);
        protoGroup.Remove(_concealed, _used, currentTileType);
      }
    }

    private bool CanNotInsert(Arrangement arrangement, int currentTileType, ProtoGroup protoGroup)
    {
      var isJantou = protoGroup.IsJantou;
      if (isJantou && arrangement.Jantou > 0)
      {
        return true;
      }
      if (!isJantou && arrangement.Mentsu > 3)
      {
        return true;
      }
      return !protoGroup.CanInsert(_concealed, _used, currentTileType);
    }
  }
}
{'fraction_non_alphanumeric': 0.0955537426065674, 'fraction_numerical': 0.009993881297165, 'mean_word_length': 2.485074626865672, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4332032', 'n_tokens_mistral': 3100, 'n_tokens_neox': 2953, 'n_words': 835}