File: numeric_array.rb

package info (click to toggle)
ruby-rgfa 1.3.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 824 kB
  • sloc: ruby: 5,649; makefile: 9
file content (196 lines) | stat: -rw-r--r-- 5,932 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
require_relative "error"

#
# A numeric array representable using the data type B of the GFA specification
#
class RGFA::NumericArray < Array

  # Subtypes for signed integers, from the smallest to the largest
  SIGNED_INT_SUBTYPE = %W[c s i]

  # Subtypes for unsigned integers, from the smallest to the largest
  UNSIGNED_INT_SUBTYPE = SIGNED_INT_SUBTYPE.map{|st|st.upcase}

  # Subtypes for integers
  INT_SUBTYPE = UNSIGNED_INT_SUBTYPE + SIGNED_INT_SUBTYPE

  # Subtypes for floats
  FLOAT_SUBTYPE = ["f"]

  # Subtypes
  SUBTYPE = INT_SUBTYPE + FLOAT_SUBTYPE

  # Number of bits of unsigned integer subtypes
  SUBTYPE_BITS = {"c" => 8, "s" => 16, "i" => 32}

  # Range for integer subtypes
  SUBTYPE_RANGE = Hash[
      INT_SUBTYPE.map do |subtype|
        [
         subtype,
         if subtype == subtype.upcase
           0..((2**SUBTYPE_BITS[subtype.downcase])-1)
         else
           (-(2**(SUBTYPE_BITS[subtype]-1)))..((2**(SUBTYPE_BITS[subtype]-1))-1)
         end
        ]
      end
    ]

  # Validate the numeric array
  #
  # @raise [RGFA::NumericArray::ValueError] if the array is not valid
  def validate!
    compute_subtype
  end

  # Computes the subtype of the array from its content.
  #
  # If all elements are float, then the computed subtype is "f".
  # If all elements are integer, the smallest possible numeric subtype
  # is computed; thereby,
  # if all elements are non-negative, an unsigned subtype is selected,
  # otherwise a signed subtype.
  # In all other cases an exception is raised.
  #
  # @raise [RGFA::NumericArray::ValueError] if the array is not a valid numeric
  #   array
  # @return [RGFA::NumericArray::SUBTYPE]
  def compute_subtype
    if all? {|f|f.kind_of?(Float)}
      return "f"
    else
      e_max = nil
      e_min = nil
      each do |e|
        if !e.kind_of?(Integer)
          raise RGFA::NumericArray::ValueError,
            "NumericArray does not contain homogenous numeric values\n"+
            "Content: #{inspect}"
        end
        e_max = e if e_max.nil? or e > e_max
        e_min = e if e_min.nil? or e < e_min
      end
      return RGFA::NumericArray.integer_type(e_min..e_max)
    end
  end

  # Computes the subtype for integers in a given range.
  #
  # If all elements are non-negative, an unsigned subtype is selected,
  # otherwise a signed subtype.
  #
  # @param range [Range] the integer range
  #
  # @raise [RGFA::NumericArray::ValueError] if the integer range is outside
  #   all subtype ranges
  #
  # @return [RGFA::NumericArray::INT_SUBTYPE] subtype code
  def self.integer_type(range)
    if range.min < 0
      SIGNED_INT_SUBTYPE.each do |st|
        st_range = RGFA::NumericArray::SUBTYPE_RANGE[st]
        if st_range.include?(range.min) and st_range.include?(range.max)
          return st
        end
      end
    else
      UNSIGNED_INT_SUBTYPE.each do |st|
        return st if range.max < RGFA::NumericArray::SUBTYPE_RANGE[st].max
      end
    end
    raise RGFA::NumericArray::ValueError,
      "NumericArray: values are outside of all integer subtype ranges\n"+
      "Content: #{inspect}"
  end

  # Return self
  # @param validate [Boolean] <i>(default: +false+)</i>
  #   if +true+, validate the range of the numeric values, according
  #   to the array subtype
  # @raise [RGFA::NumericArray::ValueError] if validate is set and
  #   any value is not compatible with the subtype
  # @return [RGFA::NumericArray]
  def to_numeric_array(validate: false)
    validate! if validate
    self
  end

  # GFA datatype B representation of the numeric array
  # @raise [RGFA::NumericArray::ValueError] if the array
  #   if not a valid numeric array
  # @return [String]
  def to_s
    subtype = compute_subtype
    "#{subtype},#{join(",")}"
  end

end

# Exception raised if a value in a numeric array is not compatible
# with the selected subtype
class RGFA::NumericArray::ValueError < RGFA::Error; end

# Exception raised if an invalid subtype code is found
class RGFA::NumericArray::TypeError < RGFA::Error; end

#
# Method to create a numeric array from an array
#
class Array
  # Create a numeric array from an Array instance
  # @param validate [Boolean] <i>(default: +true+)</i>
  #   if +true+, validate the range of the numeric values, according
  #   to the array subtype
  # @raise [RGFA::NumericArray::ValueError] if validate is set and
  #   any value is not compatible with the subtype
  # @return [RGFA::NumericArray] the numeric array
  def to_numeric_array(validate: true)
    na = RGFA::NumericArray.new(self)
    na.validate! if validate
    na
  end
end

#
# Method to create a numeric array from a string
#
class String
  # Create a numeric array from a string
  # @param validate [Boolean] <i>(default: +true+)</i>
  #   if +true+, validate the range of the numeric values, according
  #   to the array subtype
  # @raise [RGFA::NumericArray::ValueError] if validate is set and
  #   any value is not compatible with the subtype
  # @raise [RGFA::NumericArray::TypeError] if the subtype code is invalid
  # @return [RGFA::NumericArray] the numeric array
  def to_numeric_array(validate: true)
    elems = split(",")
    subtype = elems.shift
    integer = (subtype != "f")
    if integer
      range = RGFA::NumericArray::SUBTYPE_RANGE[subtype]
    elsif !RGFA::NumericArray::SUBTYPE.include?(subtype)
      raise RGFA::NumericArray::TypeError, "Subtype #{subtype} unknown"
    end
    elems.map do |e|
      begin
        if integer
          e = Integer(e)
          if validate and not range.include?(e)
            raise "NumericArray: "+
                  "value is outside of subtype #{subtype} range\n"+
                  "Value: #{e}\n"+
                  "Range: #{range.inspect}\n"+
                  "Content: #{inspect}"
          end
          e
        else
          Float(e)
        end
      rescue => msg
        raise RGFA::NumericArray::ValueError, msg
      end
    end
  end
end