CrackedRuby logo

CrackedRuby

Array Access and Assignment

Array access and assignment operations in Ruby, including indexing, slicing, negative indices, and modification patterns.

Core Built-in Classes Array Class
2.4.2

Overview

Ruby arrays support multiple access and assignment patterns through bracket notation and specialized methods. The Array class implements #[] for element access and #[]= for element assignment, supporting integer indices, ranges, and slice operations.

Arrays use zero-based indexing with support for negative indices that count from the end. Ruby returns nil for out-of-bounds access and expands arrays automatically during assignment operations. The bracket operators handle both single element operations and slice operations that work with multiple elements.

arr = [10, 20, 30, 40, 50]
arr[0]     # => 10
arr[-1]    # => 50
arr[1, 3]  # => [20, 30, 40]

Array access operations integrate with Ruby's method call syntax, allowing bracket notation to function as syntactic sugar for method calls. The #[] method accepts integers, ranges, or two integers representing start position and length.

arr = ['a', 'b', 'c', 'd']
arr[1..2]   # => ["b", "c"]
arr[1, 2]   # => ["b", "c"]
arr.slice(1, 2)  # => ["b", "c"]

Assignment operations modify arrays in place and can extend array bounds automatically. Ruby fills gaps with nil values when assigning beyond current array boundaries. Multiple assignment patterns support replacing single elements, slices, or inserting new elements.

Basic Usage

Single element access uses integer indices within square brackets. Positive indices start from zero at the beginning, while negative indices start from -1 at the end. Out-of-bounds access returns nil without raising exceptions.

colors = ['red', 'green', 'blue', 'yellow']
colors[0]   # => "red"
colors[3]   # => "yellow"  
colors[4]   # => nil
colors[-1]  # => "yellow"
colors[-4]  # => "red"
colors[-5]  # => nil

Range-based access extracts slices using inclusive or exclusive ranges. Inclusive ranges include both endpoints, while exclusive ranges exclude the final element. Range access returns new arrays containing the selected elements.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers[2..5]   # => [3, 4, 5, 6]
numbers[2...5]  # => [3, 4, 5]
numbers[-3..-1] # => [8, 9, 10]
numbers[7..-1]  # => [8, 9, 10]

Two-argument access specifies starting index and length, providing an alternative to range syntax. The first argument indicates the starting position, and the second specifies how many elements to extract. This pattern works with both positive and negative starting indices.

letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
letters[1, 3]   # => ["b", "c", "d"]
letters[4, 2]   # => ["e", "f"]
letters[-3, 2]  # => ["e", "f"]
letters[2, 10]  # => ["c", "d", "e", "f", "g"]

Single element assignment replaces existing values or extends arrays when assigning beyond current bounds. Ruby automatically expands arrays and fills intermediate positions with nil values when necessary.

data = [100, 200, 300]
data[1] = 250        # data => [100, 250, 300]
data[3] = 400        # data => [100, 250, 300, 400]
data[6] = 700        # data => [100, 250, 300, 400, nil, nil, 700]
data[-1] = 750       # data => [100, 250, 300, 400, nil, nil, 750]

Advanced Usage

Multi-element assignment supports replacing slices with arrays of different lengths. When the replacement array has fewer elements than the slice being replaced, the array contracts. When the replacement has more elements, the array expands to accommodate the new data.

sequence = [1, 2, 3, 4, 5, 6, 7, 8, 9]
sequence[2, 3] = [30, 40]           # => [1, 2, 30, 40, 6, 7, 8, 9]
sequence[1, 2] = [20, 25, 27, 29]   # => [1, 20, 25, 27, 29, 40, 6, 7, 8, 9]
sequence[3, 4] = []                 # => [1, 20, 25, 6, 7, 8, 9]

Range assignment replaces elements within the specified range with new values. The replacement can contain any number of elements, causing the array to expand or contract as needed. Range assignment provides precise control over which elements get replaced.

items = ['apple', 'banana', 'cherry', 'date', 'elderberry']
items[1..3] = ['blueberry', 'coconut']
# => ['apple', 'blueberry', 'coconut', 'elderberry']

items[2...2] = ['cantaloupe', 'durian'] 
# => ['apple', 'blueberry', 'cantaloupe', 'durian', 'coconut', 'elderberry']

Slice assignment with zero length inserts elements at specific positions without replacing existing elements. This pattern uses a starting index with length zero, effectively performing insertion operations at precise locations.

roster = ['Alice', 'Bob', 'Charlie']
roster[1, 0] = ['Alex']      # => ['Alice', 'Alex', 'Bob', 'Charlie']  
roster[3, 0] = ['Betty']     # => ['Alice', 'Alex', 'Bob', 'Betty', 'Charlie']
roster[-1, 0] = ['Carl']     # => ['Alice', 'Alex', 'Bob', 'Betty', 'Carl', 'Charlie']

Complex access patterns combine multiple operations for sophisticated array manipulation. These patterns support building data transformation pipelines and handling nested array structures with precise element selection.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix[1][2] = 60                    # => [[1, 2, 3], [4, 5, 60], [7, 8, 9]]

coordinates = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16]]
coordinates[2..-1].map { |pair| pair[1] }  # => [4, 9, 16]

nested_data = [['a', 'b'], ['c', 'd', 'e'], ['f']]
nested_data.map { |sub| sub[1..-1] || [] }  # => [["b"], ["d", "e"], []]

Performance & Memory

Array access performance varies significantly based on access patterns and array size. Single element access by index operates in constant time O(1) regardless of array size, while slice operations require time proportional to the slice length.

# Benchmark different access patterns
large_array = (1..1_000_000).to_a

# O(1) - constant time
element = large_array[500_000]

# O(n) - linear with slice size  
small_slice = large_array[0, 1000]      # Fast
medium_slice = large_array[0, 50_000]   # Slower  
large_slice = large_array[0, 500_000]   # Much slower

Memory allocation behavior differs between access and assignment operations. Element access creates minimal overhead, while slice operations allocate new arrays. Assignment operations may trigger array reallocation when expanding beyond current capacity.

original = [1, 2, 3, 4, 5] * 100_000

# No new allocation - returns reference to existing element
single_element = original[50_000]

# Allocates new array with copied elements
slice_copy = original[10_000, 20_000]

# May trigger reallocation if expanding significantly
original[600_000] = 99  # Likely causes reallocation and nil filling

Assignment patterns that cause frequent reallocations impact performance substantially. Growing arrays incrementally by single elements at the end triggers fewer reallocations than assignments at arbitrary positions beyond current bounds.

# Efficient growth pattern
efficient_array = []
1000.times { |i| efficient_array[i] = i * 2 }

# Inefficient growth pattern - causes multiple reallocations
inefficient_array = []
1000.times { |i| inefficient_array[i * 2] = i }  # Creates many gaps

Cache performance affects access patterns for large arrays. Sequential access patterns utilize processor cache effectively, while random access patterns may cause cache misses that slow operations.

huge_array = (1..10_000_000).to_a

# Cache-friendly sequential access
sequential_sum = 0  
huge_array.each_with_index { |val, idx| sequential_sum += huge_array[idx] }

# Cache-unfriendly random access  
random_indices = (0...10_000_000).to_a.shuffle[0, 100_000]
random_sum = random_indices.sum { |idx| huge_array[idx] }

Common Pitfalls

Index boundary errors occur when accessing elements beyond array bounds, but Ruby returns nil instead of raising exceptions. This behavior can mask logical errors where code expects valid elements but receives nil values.

short_list = ['first', 'second']
result = short_list[5]      # => nil (not an error)

# Problem: nil propagates through calculations
doubled = result * 2        # NoMethodError: undefined method `*' for nil:NilClass

# Solution: Check bounds or use fetch
safe_result = short_list.fetch(5, 'default')  # => "default"

Negative index confusion happens when developers misunderstand which element negative indices reference. The index -1 refers to the last element, -2 to the second-to-last, continuing backward through the array.

values = [10, 20, 30, 40, 50]
values[-1]  # => 50 (last element, not first)  
values[-5]  # => 10 (first element with negative index)
values[-6]  # => nil (beyond beginning)

# Common mistake: expecting -0 to work
values[-0]  # => 10 (same as values[0])

Slice assignment length mismatches cause arrays to expand or contract unexpectedly. When replacement arrays differ in size from the slice being replaced, the total array length changes, potentially affecting subsequent code that assumes fixed positions.

items = ['a', 'b', 'c', 'd', 'e']
items[1, 2] = ['x']     # => ['a', 'x', 'd', 'e'] (contracted)
items[2, 1] = ['y', 'z']  # => ['a', 'x', 'y', 'z', 'e'] (expanded)

# Original indices no longer valid
# items[4] was 'e', now undefined behavior in subsequent code

Nil gap creation during sparse assignment fills intermediate positions with nil values, creating arrays with unexpected nil elements that can cause errors in iteration or calculation operations.

sparse = [1, 2, 3]
sparse[10] = 99  # => [1, 2, 3, nil, nil, nil, nil, nil, nil, nil, 99]

# Problems with nil gaps
total = sparse.sum         # NoMethodError: String can't be coerced into Integer  
compact_total = sparse.compact.sum  # => 105 (safe approach)

Range direction errors occur when end index is smaller than start index, or when using inappropriate range types. Ruby returns empty arrays for invalid ranges rather than raising errors.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers[5..2]    # => [] (end < start)
numbers[10..15]  # => nil (start beyond bounds)
numbers[3...3]   # => [] (exclusive range with same start/end)

# Debugging: Check range validity
start_idx, end_idx = 5, 2
if start_idx <= end_idx
  slice = numbers[start_idx..end_idx]  
else
  # Handle invalid range
  slice = []
end

Reference

Array Access Methods

Method Parameters Returns Description
#[](index) index (Integer) Object or nil Returns element at index position
#[](start, length) start (Integer), length (Integer) Array or nil Returns slice starting at start with length elements
#[](range) range (Range) Array or nil Returns slice covering range
#slice(index) index (Integer) Object or nil Alias for #[] with single index
#slice(start, length) start (Integer), length (Integer) Array or nil Alias for #[] with start and length
#slice(range) range (Range) Array or nil Alias for #[] with range
#fetch(index) index (Integer) Object Returns element at index, raises IndexError if out of bounds
#fetch(index, default) index (Integer), default (Object) Object Returns element at index or default if out of bounds
#at(index) index (Integer) Object or nil Returns element at index, supports negative indices
#first None Object or nil Returns first element
#first(n) n (Integer) Array Returns first n elements
#last None Object or nil Returns last element
#last(n) n (Integer) Array Returns last n elements

Array Assignment Methods

Method Parameters Returns Description
#[]=(index, value) index (Integer), value (Object) Object Assigns value at index position
#[]=(start, length, values) start (Integer), length (Integer), values (Array) Array Replaces slice with values array
#[]=(range, values) range (Range), values (Array) Array Replaces range slice with values
#fill(value) value (Object) Array Fills entire array with value
#fill(value, start) value (Object), start (Integer) Array Fills from start to end with value
#fill(value, start, length) value (Object), start (Integer), length (Integer) Array Fills slice with value
#fill(value, range) value (Object), range (Range) Array Fills range with value

Index Behavior

Index Type Behavior Example Result
Positive in bounds Returns element at position [1,2,3][1] 2
Positive out of bounds Returns nil [1,2,3][5] nil
Negative in bounds Counts from end [1,2,3][-1] 3
Negative out of bounds Returns nil [1,2,3][-5] nil
Zero Returns first element [1,2,3][0] 1

Slice Operation Patterns

Operation Syntax Behavior Example
Range inclusive arr[start..end] Includes both start and end indices [1,2,3,4][1..2][2,3]
Range exclusive arr[start...end] Includes start, excludes end [1,2,3,4][1...3][2,3]
Start and length arr[start, length] Length elements from start [1,2,3,4][1,2][2,3]
Assignment replacement arr[start, length] = values Replaces slice with values arr[1,2] = [9,8]
Assignment insertion arr[index, 0] = values Inserts values at index arr[2,0] = [5,6]

Error Conditions

Condition Method Result
Index beyond bounds #[] Returns nil
Index beyond bounds #fetch Raises IndexError
Negative start with length #[start, length] May return nil or partial array
Invalid range #[range] Returns empty array or nil
Non-integer index #[] Raises TypeError
Non-array assignment #[]= with slice Attempts conversion with to_ary