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
|