Add Array#insert(Int, Enumerable(T)) overload

I’d like to suggest adding an overload to Array(T)#insert that allows an Array (or Enumerable/Iterable/etc.) to be passed as an argument.

In my shard, I process a list of nodes, parsing any markup that may be present. I make use of a method I defined called insert_all, which inserts all elements of an array into another array. The downside is that it involves reopening the Array class, meaning any project using mine will also have the method, potentially causing issues.

I’m currently in the process of removing all instances of “reopening standard classes” and noticed that the standard library doesn’t seem to provide a “nice” way to insert an array into another array at a specific index. Please correct me if I’m wrong!

This is possible in Ruby with Array#insert, in C# with List.InsertRange, in Dart with List.insertAll, and probably others.

Below is an example adapted from the actual usage in my shard.

class Node; end

class UnparsedNode < Node
  getter text_content : String

  def initialize(@text_content : String)
  end
end

class ParsedNode < Node
  getter children : Array(Node)

  def initialize(str : String)
    @children = [] of Node
  end
end

class Document
  def parse(content : String) : Array(Node)
    block_nodes = parse_blocks(content)
    parse_inline_nodes(block_nodes)
  end

  def parse_blocks(content : String) : Array(Node)
    # ...
    content.split("\n\n").map { |raw| UnparsedNode.new(raw) }.as Array(Node)
  end

  def parse_inline(content : String) : Array(Node)
    # ...
    content.split(" ").map { |parsed| ParsedNode.new(parsed) }.as Array(Node)
  end

  def parse_inline_nodes(nodes : Array(Node)) : Nil
    i = 0
    while i < nodes.size
      node = nodes[i]
      if node.is_a? UnparsedNode
        inline_nodes = parse_inline(node.text_content)
        nodes.delete_at(i)
        nodes.insert(i, inline_nodes)            #################
        i += inline_nodes.size - 1
      elsif node.is_a?(ParsedNode) && node.children.size > 0
        parse_inline_nodes(node.children)
      end
      i += 1
    end
  end
end

Document.new.parse(%[
# Heading

inline *markup*
])

As far as I’m aware, there are two main ways to accomplish the same task (again, correct me if there’s something easier!)

arr = [1, 2, 3, 7]
# Option 1:
arr = arr[0..2] + [4, 5, 6] + arr[3..]
# Option 2:
arr[3, 0] = [4, 5, 6]

Personally, I think arr.insert(3, [4, 5, 6]) is cleaner and more explicit in what’s happening.

2 Likes

Well, there is also

x = [1,2,3,4,5]

x[1...1] = [6,7,8]

p x # => [1, 6, 7, 8, 2, 3, 4, 5]

Which is not really an argument either way, but it is possible without too much effort.

It’s nice to see proposals for enhancements that have proven themselves as useful local extensions already.

I think this makes a lot of sense. While there is already a syntax for this with range assignment, I believe an explicit method describes the intention much better and is easier to understand.

It would probably be best to keep the name #insert_all though to make a clear distinction between inserting a single element (which could also be an enumerable!) and inserting a list of elements.

It would probably be best to keep the name #insert_all though to make a clear distinction between inserting a single element (which could also be an enumerable!) and inserting a list of elements.

This would be my preference as well. I only suggested using insert to match Ruby.

While my implementation currently accepts Iterable(T), I’m doubtful that this alone is the best choice. Should Enumerable(T) be used or a combination of them (either through separate methods or a union type)?