# A fixed-size, stack allocated array. # # `StaticArray` is a generic type with type argument `T` specifying the type of # its elements and `N` the fixed size. For example `StaticArray(Int32, 3)` # is a static array of `Int32` with three elements. # # Instantiations of this static array type: # # ``` # StaticArray(Int32, 3).new(42) # => StaticArray[42, 42, 42] # StaticArray(Int32, 3).new { |i| i * 2 } # => StaticArray[0, 2, 4] # StaticArray[0, 8, 15] # => StaticArray[0, 8, 15] # ``` # # This type can also be expressed as `Int32[3]` (only in type grammar). A typical use # case is in combination with `uninitialized`: # # ``` # ints = uninitialized Int32[3] # ints[0] = 0 # ints[1] = 8 # ints[2] = 15 # ``` # # For number types there is also `Number.static_array` which can be used to initialize # a static array: # # ``` # Int32.static_array(0, 8, 15) # => StaticArray[0, 8, 15] # ``` # # The generic argument type `N` is a special case in the type grammar as it # doesn't specify a type but a size. Its value can be an `Int32` literal or # constant. struct StaticArray(T, N) include Comparable(StaticArray) include Indexable::Mutable(T) # Creates a new `StaticArray` with the given *args*. The type of the # static array will be the union of the type of the given *args*, # and its size will be the number of elements in *args*. # # ``` # ary = StaticArray[1, 'a'] # ary[0] # => 1 # ary[1] # => 'a' # ary.class # => StaticArray(Char | Int32, 2) # ``` # # * `Number.static_array` is a convenient alternative for designating a # specific numerical item type. macro [](*args) %array = uninitialized ::StaticArray(typeof({{args.splat}}), {{args.size}}) {% for arg, i in args %} %array.to_unsafe[{{i}}] = {{arg}} {% end %} %array end # Creates a new static array and invokes the # block once for each index of the array, assigning the # block's value in that index. # # ``` # StaticArray(Int32, 3).new { |i| i * 2 } # => StaticArray[0, 2, 4] # ``` def self.new(& : Int32 -> T) array = uninitialized self N.times do |i| array.to_unsafe[i] = yield i end array end # Creates a new static array filled with the given value. # # ``` # StaticArray(Int32, 3).new(42) # => StaticArray[42, 42, 42] # ``` def self.new(value : T) new { value } end # Disallow creating an uninitialized StaticArray with new. # If this is desired, one can use `array = uninitialized ...` # which makes it clear that it's unsafe. private def initialize end # Equality. Returns `true` if each element in `self` is equal to each # corresponding element in *other*. # # ``` # array = StaticArray(Int32, 3).new 0 # => StaticArray[0, 0, 0] # array2 = StaticArray(Int32, 3).new 0 # => StaticArray[0, 0, 0] # array3 = StaticArray(Int32, 3).new 1 # => StaticArray[1, 1, 1] # array == array2 # => true # array == array3 # => false # ``` def ==(other : StaticArray) return false unless size == other.size each_with_index do |e, i| return false unless e == other[i] end true end # Equality with another object. Always returns `false`. # # ``` # array = StaticArray(Int32, 3).new 0 # => StaticArray[0, 0, 0] # array == nil # => false # ``` def ==(other) false end def <=>(other : StaticArray) to_slice <=> other.to_slice end @[AlwaysInline] def unsafe_fetch(index : Int) : T to_unsafe[index] end @[AlwaysInline] def unsafe_put(index : Int, value : T) to_unsafe[index] = value end # Returns the size of `self` # # ``` # array = StaticArray(Int32, 3).new { |i| i + 1 } # array.size # => 3 # ``` def size : Int32 N end # :inherit: def fill(value : T) : self # enable memset optimization to_slice.fill(value) self end # :inherit: def fill(value : T, start : Int, count : Int) : self to_slice.fill(value, start, count) self end # :inherit: def fill(value : T, range : Range) : self to_slice.fill(value, range) self end # Returns a new static array where elements are mapped by the given block. # # ``` # array = StaticArray[1, 2.5, "a"] # array.map &.to_s # => StaticArray["1", "2.5", "a"] # ``` def map(&block : T -> U) : StaticArray(U, N) forall U StaticArray(U, N).new { |i| yield to_unsafe[i] } end # Like `map`, but the block gets passed both the element and its index. # # Accepts an optional *offset* parameter, which tells it to start counting # from there. def map_with_index(offset = 0, &block : (T, Int32) -> U) : StaticArray(U, N) forall U StaticArray(U, N).new { |i| yield to_unsafe[i], offset + i } end # Returns a new instance with all elements sorted based on the return value of # their comparison method `T#<=>` (see `Comparable#<=>`), using a stable sort algorithm. # # ``` # a = StaticArray[3, 1, 2] # a.sort # => StaticArray[1, 2, 3] # a # => StaticArray[3, 1, 2] # ``` # # See `Indexable::Mutable#sort!` for details on the sorting mechanism. # # Raises `ArgumentError` if the comparison between any two elements returns `nil`. def sort : StaticArray(T, N) # the return value of `dup` must be assigned to a variable first, otherwise # `self` will be mutated if the `sort!` call is chained directly ary = dup ary.sort! end # Returns a new instance with all elements sorted based on the return value of # their comparison method `T#<=>` (see `Comparable#<=>`), using an unstable sort algorithm. # # ``` # a = StaticArray[3, 1, 2] # a.unstable_sort # => StaticArray[1, 2, 3] # a # => StaticArray[3, 1, 2] # ``` # # See `Indexable::Mutable#unstable_sort!` for details on the sorting mechanism. # # Raises `ArgumentError` if the comparison between any two elements returns `nil`. def unstable_sort : StaticArray(T, N) ary = dup ary.unstable_sort! end # Returns a new instance with all elements sorted based on the comparator in the # given block, using a stable sort algorithm. # # ``` # a = StaticArray[3, 1, 2] # b = a.sort { |a, b| b <=> a } # # b # => StaticArray[3, 2, 1] # a # => StaticArray[3, 1, 2] # ``` # # See `Indexable::Mutable#sort!(&block : T, T -> U)` for details on the sorting mechanism. # # Raises `ArgumentError` if for any two elements the block returns `nil`.= def sort(&block : T, T -> U) : StaticArray(T, N) forall U {% unless U <= Int32? %} {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#sort_by`?" %} {% end %} ary = dup ary.sort!(&block) end # Returns a new instance with all elements sorted based on the comparator in the # given block, using an unstable sort algorithm. # # ``` # a = StaticArray[3, 1, 2] # b = a.unstable_sort { |a, b| b <=> a } # # b # => StaticArray[3, 2, 1] # a # => StaticArray[3, 1, 2] # ``` # # See `Indexable::Mutable#unstable_sort!(&block : T, T -> U)` for details on the sorting mechanism. # # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort(&block : T, T -> U) : StaticArray(T, N) forall U {% unless U <= Int32? %} {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#unstable_sort_by`?" %} {% end %} ary = dup ary.unstable_sort!(&block) end # :inherit: def sort! : self to_slice.sort! self end # :inherit: def unstable_sort! : self to_slice.unstable_sort! self end # :inherit: def sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#sort_by!`?" %} {% end %} to_slice.sort!(&block) self end # :inherit: def unstable_sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#unstable_sort_by!`?" %} {% end %} to_slice.unstable_sort!(&block) self end # Returns a new instance with all elements sorted by the output value of the # block. The output values are compared via the comparison method `T#<=>` # (see `Comparable#<=>`), using a stable sort algorithm. # # ``` # a = StaticArray["apple", "pear", "fig"] # b = a.sort_by { |word| word.size } # b # => StaticArray["fig", "pear", "apple"] # a # => StaticArray["apple", "pear", "fig"] # ``` # # If stability is expendable, `#unstable_sort_by(&block : T -> _)` provides a # performance advantage over stable sort. # # See `Indexable::Mutable#sort_by!(&block : T -> _)` for details on the sorting mechanism. # # Raises `ArgumentError` if the comparison between any two comparison values returns `nil`. def sort_by(&block : T -> _) : StaticArray(T, N) ary = dup ary.sort_by! { |e| yield(e) } end # Returns a new instance with all elements sorted by the output value of the # block. The output values are compared via the comparison method `#<=>` # (see `Comparable#<=>`), using an unstable sort algorithm. # # ``` # a = StaticArray["apple", "pear", "fig"] # b = a.unstable_sort_by { |word| word.size } # b # => StaticArray["fig", "pear", "apple"] # a # => StaticArray["apple", "pear", "fig"] # ``` # # If stability is necessary, use `#sort_by(&block : T -> _)` instead. # # See `Indexable::Mutable#unstable_sort!(&block : T -> _)` for details on the sorting mechanism. # # Raises `ArgumentError` if the comparison between any two comparison values returns `nil`. def unstable_sort_by(&block : T -> _) : StaticArray(T, N) ary = dup ary.unstable_sort_by! { |e| yield(e) } end # :inherit: def sort_by!(&block : T -> _) : self sorted = map { |e| {e, yield(e)} }.sort! { |x, y| x[1] <=> y[1] } N.times do |i| to_unsafe[i] = sorted.to_unsafe[i][0] end self end # :inherit: def unstable_sort_by!(&block : T -> _) : self sorted = map { |e| {e, yield(e)} }.unstable_sort! { |x, y| x[1] <=> y[1] } N.times do |i| to_unsafe[i] = sorted.to_unsafe[i][0] end self end # :inherit: def rotate!(n : Int = 1) : self to_slice.rotate!(n) self end # Returns a slice that points to the elements of this static array. # Changes made to the returned slice also affect this static array. # # ``` # array = StaticArray(Int32, 3).new(2) # slice = array.to_slice # => Slice[2, 2, 2] # slice[0] = 3 # array # => StaticArray[3, 2, 2] # ``` def to_slice : Slice(T) Slice.new(to_unsafe, size) end # Returns a pointer to this static array's data. # # ``` # ary = StaticArray(Int32, 3).new(42) # ary.to_unsafe[0] # => 42 # ``` def to_unsafe : Pointer(T) pointerof(@buffer) end # Appends a string representation of this static array to the given `IO`. # # ``` # array = StaticArray(Int32, 3).new { |i| i + 1 } # array.to_s # => "StaticArray[1, 2, 3]" # ``` def to_s(io : IO) : Nil io << "StaticArray[" join io, ", ", &.inspect(io) io << ']' end def pretty_print(pp) # Don't pass `self` here because we'll pass `self` by # value and for big static arrays that seems to make # LLVM really slow. # TODO: investigate why, maybe report a bug to LLVM? pp.list("StaticArray[", to_slice, "]") end # Returns a new `StaticArray` where each element is cloned from elements in `self`. def clone array = uninitialized self N.times do |i| array.to_unsafe[i] = to_unsafe[i].clone end array end def index(object, offset : Int = 0) # Optimize for the case of looking for a byte in a byte slice if T.is_a?(UInt8.class) && (object.is_a?(UInt8) || (object.is_a?(Int) && 0 <= object < 256)) return to_slice.fast_index(object, offset) end super end end