CrackedRuby logo

CrackedRuby

Pattern Matching with Find Patterns

Overview

Pattern matching with find patterns in Ruby provides a way to extract specific elements from arrays and other data structures without iterating through all elements manually. Find patterns use the ** splat operator combined with pattern matching syntax to locate and capture particular values while ignoring surrounding elements.

Ruby's find patterns work within the broader pattern matching system introduced in Ruby 2.7 and enhanced in subsequent versions. The core mechanism relies on the in operator and case statements, allowing developers to match against array structures and capture elements that meet specific criteria.

array = [1, 2, "target", 4, 5]
case array
in [*, "target", *]
  puts "Found target string"
end

Find patterns excel at extracting values from nested structures, parsing command-line arguments, and processing API responses where specific data appears at unknown positions. The pattern matching engine handles the iteration internally, making code more declarative and reducing manual loop construction.

data = [:header, {:name => "John", :age => 30}, :footer, {:name => "Jane", :age => 25}]
case data
in [*, {:name => name, :age => age}, *] if age > 28
  puts "Found adult: #{name}"
end
# => "Found adult: John"

The find pattern syntax integrates with Ruby's existing pattern matching operators, including array destructuring, hash matching, and guard clauses. This combination creates a powerful system for data extraction that handles both simple and complex scenarios.

Basic Usage

Find patterns use the splat operator * to match any number of elements before or after the target pattern. The basic syntax places the target pattern between splat operators, allowing Ruby to search through the array for matching elements.

numbers = [10, 20, 100, 30, 40]
case numbers
in [*, value, *] if value > 50
  puts "Found large number: #{value}"
end
# => "Found large number: 100"

Multiple elements can be captured in a single find pattern by specifying multiple variables between the splats. Ruby matches the first occurrence that satisfies the entire pattern structure.

mixed_data = ["start", 42, "middle", 99, "end"]
case mixed_data
in [*, Integer => first, String, Integer => second, *]
  puts "Numbers: #{first}, #{second}"
end
# => "Numbers: 42, 99"

Named splats capture the elements before and after the matched pattern, providing access to surrounding context when needed.

log_entries = ["INFO", "DEBUG", "ERROR: Failed", "DEBUG", "WARN"]
case log_entries
in [*prefix, /ERROR/ => error, *suffix]
  puts "Error found: #{error}"
  puts "Prefix: #{prefix}"
  puts "Suffix: #{suffix}"
end

Find patterns work with nested arrays by combining array destructuring with the find pattern syntax. This allows extraction of elements from complex hierarchical structures.

nested = [[1, 2], [3, "target", 4], [5, 6]]
case nested
in [*, [*, "target", *], *]
  puts "Found target in nested array"
end

Hash patterns combine with find patterns to extract specific key-value pairs from arrays containing mixed data types. The pattern matching engine handles type checking automatically.

messages = [{type: "info"}, {type: "error", message: "Failed"}, {type: "debug"}]
case messages
in [*, {type: "error", message: msg}, *]
  puts "Error message: #{msg}"
end

Advanced Usage

Complex find patterns combine multiple matching techniques to extract sophisticated data structures. Guard clauses enhance pattern specificity by adding conditional logic that patterns must satisfy beyond structural matching.

transaction_log = [
  {id: 1, type: "deposit", amount: 100, status: "pending"},
  {id: 2, type: "withdrawal", amount: 50, status: "completed"},
  {id: 3, type: "deposit", amount: 200, status: "failed"},
  {id: 4, type: "withdrawal", amount: 75, status: "completed"}
]

case transaction_log
in [*, {type: "withdrawal", amount: amt, status: "completed"}, *] if amt > 60
  puts "Large completed withdrawal: $#{amt}"
end

Nested find patterns handle deeply structured data by applying pattern matching at multiple levels. Each nesting level can use its own find pattern syntax with independent variable capture.

api_response = [
  {users: [{name: "Alice", roles: ["admin", "user"]}]},
  {users: [{name: "Bob", roles: ["user"]}, {name: "Carol", roles: ["admin", "moderator"]}]},
  {users: [{name: "Dave", roles: ["guest"]}]}
]

case api_response
in [*, {users: [*, {name: user_name, roles: [*, "admin", *]}, *]}, *]
  puts "Found admin user: #{user_name}"
end

Alternative patterns using the | operator allow matching against multiple possible structures within a single find pattern. This technique handles data that may appear in different formats.

mixed_formats = [
  "2023-01-15",
  {date: "2023-01-16", time: "14:30"},
  "2023-01-17",
  {timestamp: "2023-01-18T10:00:00Z"}
]

case mixed_formats
in [*, (String => date_str | {date: date_str} | {timestamp: date_str}), *] if date_str.include?("2023-01-16")
  puts "Found target date: #{date_str}"
end

Pattern matching with method calls enables extraction based on dynamic conditions. The pattern matching engine evaluates method calls during the matching process, allowing complex validation logic.

class Order
  attr_reader :items, :total, :status

  def initialize(items, total, status)
    @items, @total, @status = items, total, status
  end

  def high_value?
    total > 1000
  end
end

orders = [
  Order.new(["book"], 25, "shipped"),
  Order.new(["laptop", "mouse"], 1200, "pending"),
  Order.new(["coffee"], 15, "delivered")
]

case orders
in [*, Order[items: items, status: "pending"] => order, *] if order.high_value?
  puts "High-value pending order: #{items.join(', ')}"
end

Error Handling & Debugging

Pattern matching failures in find patterns raise NoMatchingPatternError when no array element matches the specified pattern. This exception occurs when the pattern structure cannot be satisfied by any portion of the target array.

data = [1, 2, 3, 4, 5]
begin
  case data
  in [*, String, *]
    puts "Found string"
  else
    raise NoMatchingPatternError
  end
rescue NoMatchingPatternError => e
  puts "No string found in numeric array"
end

Invalid pattern syntax generates SyntaxError exceptions during code parsing. Common syntax errors include malformed splat operators, incorrect variable bindings, and mismatched brackets in complex patterns.

# This generates a SyntaxError
# case array
# in [**, value, *]  # Invalid: double splat in array pattern
#   puts value
# end

Variable binding conflicts occur when the same variable name appears multiple times within a find pattern. Ruby requires each variable to bind consistently throughout the pattern matching expression.

test_array = ["a", "b", "a", "c"]
begin
  case test_array
  in [*, var, *, var, *]  # Error: var used twice
    puts "Matched: #{var}"
  end
rescue => e
  puts "Binding error: #{e.message}"
end

Debugging complex find patterns benefits from incremental pattern construction. Start with simple patterns and gradually add complexity while testing each modification to isolate problematic sections.

complex_data = [
  {type: "user", data: {name: "Alice", permissions: ["read", "write"]}},
  {type: "group", data: {name: "Admins", members: 5}},
  {type: "user", data: {name: "Bob", permissions: ["read"]}}
]

# Debug step by step
puts "Step 1: Basic structure match"
case complex_data
in [*, Hash, *]
  puts "✓ Contains hash elements"
end

puts "Step 2: Type-specific match"
case complex_data
in [*, {type: "user"}, *]
  puts "✓ Contains user type"
end

puts "Step 3: Full pattern match"
case complex_data
in [*, {type: "user", data: {name: name, permissions: [*, "write", *]}}, *]
  puts "✓ Found user with write permissions: #{name}"
end

Common Pitfalls

Find patterns match the first occurrence that satisfies the pattern, not necessarily the most specific or complete match. This behavior can lead to unexpected results when multiple elements could satisfy the pattern criteria.

ambiguous_data = [
  {id: 1, status: "active"},
  {id: 2, status: "inactive", priority: "high"},
  {id: 3, status: "active", priority: "low"}
]

# Matches first active status, not the one with priority
case ambiguous_data
in [*, {status: "active", **rest}, *]
  puts "Matched: #{rest}"  # => {} (empty, from first match)
end

Splat operator positioning affects pattern matching behavior significantly. Incorrect splat placement can cause patterns to fail or match unintended elements, especially in arrays with duplicate values.

duplicates = [1, 2, 2, 3, 2, 4]
case duplicates
in [*, 2, *, 2, *]  # Matches first two 2s (positions 1,2)
  puts "Found two 2s (early match)"
end

case duplicates
in [*, 2, *, 2, *, 2, *]  # Matches all three 2s
  puts "Found three 2s (complete match)"
end

Variable scope in find patterns can create confusion when variables with the same name exist in outer scopes. Pattern matching creates new bindings that shadow existing variables, potentially causing logic errors.

target_value = "important"
search_array = ["unimportant", "critical", "important", "trivial"]

case search_array
in [*, target_value, *]  # Creates new binding, doesn't use outer target_value
  puts "This always matches the third element"
  puts "Matched: #{target_value}"  # => "important"
end

# Use pin operator to reference outer variable
case search_array
in [*, ^target_value, *]  # Correctly uses outer target_value
  puts "Found the target value"
end

Guard clause evaluation order can cause performance issues in find patterns when expensive operations are performed for each potential match. The pattern matching engine evaluates guards for every matching element until one succeeds.

expensive_data = (1..1000).map { |n| {id: n, data: "item_#{n}"} }

# Inefficient: expensive_check runs for many elements
def expensive_check(id)
  sleep(0.01)  # Simulates expensive operation
  id > 500
end

case expensive_data
in [*, {id: id, data: data}, *] if expensive_check(id)
  puts "Found expensive match: #{data}"
end

Pattern complexity can lead to unmaintainable code when find patterns become deeply nested or combine too many matching techniques. Complex patterns are difficult to debug and modify, reducing code readability.

# Overly complex pattern - difficult to understand and maintain
overly_complex = [
  {users: [{profile: {settings: {theme: "dark", notifications: [{type: "email", enabled: true}]}}}]}
]

case overly_complex
in [*, {users: [*, {profile: {settings: {notifications: [*, {type: "email", enabled: true}, *]}}}, *]}, *]
  puts "Found email notifications enabled - but at what cost?"
end

Reference

Find Pattern Syntax

Pattern Description Example
[*, pattern, *] Basic find pattern [*, "target", *]
[*prefix, pattern, *suffix] Named splats [*start, value, *end]
[*, pattern1, *, pattern2, *] Multiple finds [*, String, *, Integer, *]
[*, pattern => var, *] Pattern with capture [*, Hash => data, *]

Pattern Matching Operators

Operator Purpose Usage
* Splat (any elements) [*, target, *]
** Double splat (hash rest) {key: value, **rest}
=> Value capture pattern => variable
^ Pin operator ^existing_variable
| Alternative patterns (String | Integer)

Guard Clause Integration

Syntax Description Example
if condition Simple guard in [*, x, *] if x > 10
unless condition Negative guard in [*, x, *] unless x.nil?
if method_call Method guard in [*, obj, *] if obj.valid?

Common Pattern Types

Data Type Pattern Captures
Array [*, [a, b], *] a, b
Hash [*, {key: val}, *] val
Object [*, Class[attr: x], *] x
String [*, /regex/ => match, *] match
Range [*, (1..10) => range, *] range

Error Types

Exception Cause Prevention
NoMatchingPatternError No pattern matches Use else clause
SyntaxError Invalid pattern syntax Validate pattern structure
NameError Undefined variable reference Check variable names
TypeError Type mismatch in pattern Verify data types

Performance Characteristics

Scenario Time Complexity Notes
Single element find O(n) Linear search through array
Multiple element find O(n) Single pass, early termination
Nested pattern match O(n*m) n elements, m nesting depth
Guard clause evaluation O(n*g) g = guard evaluation cost

Best Practices Matrix

Use Case Recommended Pattern Avoid
Simple extraction [*, target, *] Complex nested patterns
Multiple values [*, val1, *, val2, *] Overlapping captures
Conditional match [*, x, *] if condition Expensive guards
Type checking [*, String => s, *] Manual type validation
Nested data [*, {key: [*, val, *]}, *] Deep nesting (>3 levels)