<commit_before># Measure represents a decimal value and a unit.
# It depends on nomenclatures Unit and Dimension.
class Measure
  class AmbiguousUnit < ArgumentError
  end

  class IncompatibleDimensions < ArgumentError
  end

  class InvalidExpression < ArgumentError
  end

  attr_reader :unit, :value
  cattr_reader :dimensions
  delegate :symbol, to: :nomenclature_unit

  @@dimensions = Nomen.find_or_initialize(:dimensions)
  @@units      = Nomen.find_or_initialize(:units)

  class << self
    # Lists all units. Can be filtered on a given dimension
    def units(dimension = nil)
      return @@units.all unless dimension
      unless @@dimensions.all.include?(dimension.to_s)
        raise ArgumentError, "Unknown dimension #{dimension.inspect}"
      end
      @@units.items.select do |_n, i|
        i.dimension.to_s == dimension.to_s
      end.keys.map(&:to_sym)
    end

    # Returns the units of same dimension of the given unit
    def siblings(unit)
      units(dimension(unit))
    end

    # Returns the dimension of the given unit
    def dimension(unit)
      @@units[unit].dimension.to_sym
    end
  end

  # Ways to instanciate a measure:
  # $ Measure.new(55.23, 'kilogram')
  # $ Measure.new(55.23, :kilogram)
  # $ Measure.new('55.23 kilogram')
  # $ Measure.new('55.23kilogram')
  # $ Measure.new('55.23 kg')
  # $ Measure.new('55.23kg')
  # $ 55.23.in_kilogram
  # $ 55.23.in(:kilogram)
  # $ 55.23.in('kilogram')
  def initialize(*args)
    value = nil
    unit = nil
    if args.size == 1
      expr = args.shift.to_s.gsub(/[[:space:]]+/, ' ').strip
      unless expr =~ /\A-?([\,\.]\d+|\d+([\,\.]\d+)?)\s*[^\s]+\z/
        raise InvalidExpression, "#{expr} cannot be parsed."
      end
      unit  = expr.gsub(/\A-?([\,\.]\d+|\d+([\,\.]\d+)?)\s*/, '').strip
      value = expr[0..-unit.size].strip.to_d # expr.split(/[a-zA-Z\s]/).first.strip.gsub(/\,/, '.').to_d
    elsif args.size == 2
      value = args.shift
      unit  = args.shift
    else
      raise ArgumentError, "wrong number of arguments (#{args.size} for 1 or 2)"
    end
    value = 0 if value.blank?
    unless value.is_a? Numeric
      raise ArgumentError, "Value can't be converted to float: #{value.inspect}"
    end
    @value = value.to_r
    unit = unit.name.to_s if unit.is_a?(Nomen::Item)
    @unit = unit.to_s
    unless @@units.items[@unit]
      units = @@units.where(symbol: @unit)
      if units.size > 1
        raise AmbiguousUnit, "The unit #{@unit} match with too many units: #{units.map(&:name).to_sentence}."
      elsif units.size.zero?
        # fail ArgumentError, "Unknown unit: #{unit.inspect}"
        @unit = 'unity'
      else
        @unit = units.first.name.to_s
      end
    end
  end

  # Returns a new measure in the given unit
  def convert(unit)
    Measure.new(to_r(unit), unit)
  end
  alias in convert

  # Converts measure inline without instanciating a new Measure
  def convert!(unit)
    @value = to_r(unit)
    @unit = unit.to_s
    self
  end
  alias in! convert!

  Measure.units.each do |unit|
    define_method "in_#{unit}".to_sym do
      self.in(unit)
    end
  end

  def round(ndigits = 0)
    Measure.new(to_d.round(ndigits), unit)
  end

  def inspect
    "#{@value.to_f} #{@unit}"
  end

  def to_s
    inspect
  end

  # Returns the dimension of a measure
  def dimension
    self.class.dimension(unit)
  end

  # Test if the other measure is equal to self
  def !=(other)
    return true unless other.is_a?(Measure)
    to_r != other.to_r(unit)
  end

  # Test if the other measure is equal to self
  def ==(other)
    return false unless other.is_a?(Measure)
    to_r == other.to_r(unit)
  end

  # Returns if self is less than other
  def <(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r < other.to_r(unit)
  end

  # Returns if self is greater than other
  def >(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r > other.to_r(unit)
  end

  # Returns if self is less than or equal to other
  def <=(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r <= other.to_r(unit)
  end

  # Returns if self is greater than or equal to other
  def >=(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r >= other.to_r(unit)
  end

  # Returns if self is greater than other
  def <=>(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r <=> other.to_r(unit)
  end

  # Test if measure is null
  def zero?
    @value.zero?
  end

  # Returns the dimension of a other
  def +(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be added to another measure'
    end
    self.class.new(@value + other.to_r(unit), unit)
  end

  def -(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be substracted to another measure'
    end
    self.class.new(@value - other.to_r(unit), unit)
  end

  # Returns opposite of its value
  def -@
    self.class.new(-value, unit)
  end

  # Returns self of its value<commit_msg>Added #nonzero? to Measure.<commit_after># Measure represents a decimal value and a unit.
# It depends on nomenclatures Unit and Dimension.
class Measure
  class AmbiguousUnit < ArgumentError
  end

  class IncompatibleDimensions < ArgumentError
  end

  class InvalidExpression < ArgumentError
  end

  attr_reader :unit, :value
  cattr_reader :dimensions
  delegate :symbol, to: :nomenclature_unit

  @@dimensions = Nomen.find_or_initialize(:dimensions)
  @@units      = Nomen.find_or_initialize(:units)

  class << self
    # Lists all units. Can be filtered on a given dimension
    def units(dimension = nil)
      return @@units.all unless dimension
      unless @@dimensions.all.include?(dimension.to_s)
        raise ArgumentError, "Unknown dimension #{dimension.inspect}"
      end
      @@units.items.select do |_n, i|
        i.dimension.to_s == dimension.to_s
      end.keys.map(&:to_sym)
    end

    # Returns the units of same dimension of the given unit
    def siblings(unit)
      units(dimension(unit))
    end

    # Returns the dimension of the given unit
    def dimension(unit)
      @@units[unit].dimension.to_sym
    end
  end

  # Ways to instanciate a measure:
  # $ Measure.new(55.23, 'kilogram')
  # $ Measure.new(55.23, :kilogram)
  # $ Measure.new('55.23 kilogram')
  # $ Measure.new('55.23kilogram')
  # $ Measure.new('55.23 kg')
  # $ Measure.new('55.23kg')
  # $ 55.23.in_kilogram
  # $ 55.23.in(:kilogram)
  # $ 55.23.in('kilogram')
  def initialize(*args)
    value = nil
    unit = nil
    if args.size == 1
      expr = args.shift.to_s.gsub(/[[:space:]]+/, ' ').strip
      unless expr =~ /\A-?([\,\.]\d+|\d+([\,\.]\d+)?)\s*[^\s]+\z/
        raise InvalidExpression, "#{expr} cannot be parsed."
      end
      unit  = expr.gsub(/\A-?([\,\.]\d+|\d+([\,\.]\d+)?)\s*/, '').strip
      value = expr[0..-unit.size].strip.to_d # expr.split(/[a-zA-Z\s]/).first.strip.gsub(/\,/, '.').to_d
    elsif args.size == 2
      value = args.shift
      unit  = args.shift
    else
      raise ArgumentError, "wrong number of arguments (#{args.size} for 1 or 2)"
    end
    value = 0 if value.blank?
    unless value.is_a? Numeric
      raise ArgumentError, "Value can't be converted to float: #{value.inspect}"
    end
    @value = value.to_r
    unit = unit.name.to_s if unit.is_a?(Nomen::Item)
    @unit = unit.to_s
    unless @@units.items[@unit]
      units = @@units.where(symbol: @unit)
      if units.size > 1
        raise AmbiguousUnit, "The unit #{@unit} match with too many units: #{units.map(&:name).to_sentence}."
      elsif units.size.zero?
        # fail ArgumentError, "Unknown unit: #{unit.inspect}"
        @unit = 'unity'
      else
        @unit = units.first.name.to_s
      end
    end
  end

  # Returns a new measure in the given unit
  def convert(unit)
    Measure.new(to_r(unit), unit)
  end
  alias in convert

  # Converts measure inline without instanciating a new Measure
  def convert!(unit)
    @value = to_r(unit)
    @unit = unit.to_s
    self
  end
  alias in! convert!

  Measure.units.each do |unit|
    define_method "in_#{unit}".to_sym do
      self.in(unit)
    end
  end

  def round(ndigits = 0)
    Measure.new(to_d.round(ndigits), unit)
  end

  def inspect
    "#{@value.to_f} #{@unit}"
  end

  def to_s
    inspect
  end

  # Returns the dimension of a measure
  def dimension
    self.class.dimension(unit)
  end

  # Test if the other measure is equal to self
  def !=(other)
    return true unless other.is_a?(Measure)
    to_r != other.to_r(unit)
  end

  # Test if the other measure is equal to self
  def ==(other)
    return false unless other.is_a?(Measure)
    to_r == other.to_r(unit)
  end

  # Returns if self is less than other
  def <(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r < other.to_r(unit)
  end

  # Returns if self is greater than other
  def >(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r > other.to_r(unit)
  end

  # Returns if self is less than or equal to other
  def <=(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r <= other.to_r(unit)
  end

  # Returns if self is greater than or equal to other
  def >=(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r >= other.to_r(unit)
  end

  # Returns if self is greater than other
  def <=>(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be compared to another measure'
    end
    to_r <=> other.to_r(unit)
  end

  # Test if measure is null
  def zero?
    @value.zero?
  end

  def nonzero?
    zero? ? nil : self
  end

  # Returns the dimension of a other
  def +(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be added to another measure'
    end
    self.class.new(@value + other.to_r(unit), unit)
  end

  def -(other)
    unless other.is_a?(Measure)
      raise ArgumentError, 'Only measure can be substracted to another measure'
    end
    self.class.new(@value - other.to_r(unit), unit)
  end

  # Returns opposite of its value
  def -@
    self.class.new(-value, unit)
  end

  # Returns self of its value
{'fraction_non_alphanumeric': 0.11920097078316065, 'fraction_numerical': 0.008214319051619528, 'mean_word_length': 2.717557251908397, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '294778', 'n_tokens_mistral': 3724, 'n_tokens_neox': 3566, 'n_words': 1076}