How to create a (non-static) Nested Array with a variable nested size?

Ok; I’m finally circling back to How to create a StaticArray with a variable size?, but with a bit more details of what I’m trying to get at. See also: How to create a StaticArray with a variable size? - #12 by straight-shoota

So, here is what I’ve come up with, which errors trying to figure out the type of a block return type for a recursive param:

  • Crystal play link: Carcin
  • code for ref:
class MdArray(U)
  def initialize(@array_flattened : Array(U))
  end

  def multi_slice(dims)
    multi_slice(@array_flattened,dims)
  end

  # # e.g.:
  # For an array:
  #   array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]
  # If we slice it up in (nested) grids of given dimensions, we should get a nested array as shown:
  #   dims = [6,4]
  #   multi_slice(array,dims) #=> [[0,1,2,3,4,5],[6,7,8,9,10,11],[12,13,14,15,16,17],[18,19,20,21,22,23]]
  #
  #   dims = [2,12]
  #   multi_slice(array,dims) #=> [[0,1],[2,3],[4,5],[6,7],[8,9],[10,11],[12,13],[14,15],[16,17],[18,19],[20,21],[22,23]]
  #
  #   dims = [2,3,4]
  #   multi_slice(array,dims) #=> [[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]],[[12,13],[14,15],[16,17]],[[18,19],[20,21],[22,23]]]
  #
  def multi_slice(array_from, dims)
    step_size = as_array(dims[0..-1]).product
    array_with_subs = array_from.in_groups_of(step_size)
    case
    when dims.size > 2
      array_with_subs.map{|sub| multi_slice(sub, dims[0..-2])}

      # This errors with:
      #    70 | array_with_subs.map{|sub| multi_slice(sub, dims[0..-2])}
      #    ^--
      #   Error: can't infer block return type, try to cast the block body with `as`. See: https://crystal-lang.org/reference/syntax_and_semantics/as.html#usage-for-when-the-compiler-cant-infer-the-type-of-a-block
      #
      # The problem is that within each recursive loop, the nested-ness of the array increases, so I can't pre-define a block return type.
    when dims.size == 1
      array_with_subs
    else
      array_from
    end
  end

  def as_array(value)
    if value.is_a?(Array)
      value
    else
      [value]
    end
  end
end

# dims = [2]
# dims = [2,3]
dims = [2,3,4]

array_from = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23].map{|v| v.to_f}
p! MdArray.new(array_from).multi_slice(array_from, dims)

… and eventually do something like:

class MdArray(U)
  # ...
  
  def multi_slice(array_from, dims)
    # ..
  end

  def multi_slice(array_from, dims, new_value)
    # ..
  end

  # ...
end

dims = [2,3,4]
coords = [1,2,3]
mda = MdArray.new(array_from)
nested_array = mda.multi_slice(array_from, dims) #=> [[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]],[[12,13],[14,15],[16,17]],[[18,19],[20,21],[22,23]]]

nested_value = mda.coord_value(dims, coords) #=> 17 (I think)

new_value = -1
mda.coord_value(dims, coords, new_value) = -1

mda.multi_slice(array_from, dims) #=> [[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]],[[12,13],[14,15],[16,-1]],[[18,19],[20,21],[22,23]]]

There’s no way to do what you want. You can’t have a dynamic value determine a compile-time value. If the value is dynamic, the return type will be a union of all possible compile-time values.

You can do what you want with macros, provided that dims is known at compile time. But I don’t know if that fits your needs.

Thanks; bummer. :frowning:

That said, there’s no need to use different array types for this. The usual way to do this (in both statically typed and dynamic languages, because of efficiency) is to represent multi-dimension arrays internally as a flat array, and data for the dimension descriptor. Then whenever you access the data you check the descriptor to know which index to find.

Does that make sense? Like, if the dimension is dynamic then it shouldn’t be encoded in the type (types talk about static properties)

2 Likes