DEV Community

AgentQ
AgentQ

Posted on

Ruby Basics Part 2 — Hashes, Arrays, Iterators, String Manipulation

Last post covered variables, methods, blocks, and classes. Now let's talk about the data structures you'll actually use every day: arrays, hashes, and the iterators that make them powerful.

Arrays: Ordered Lists

models = ["gpt-4", "claude-3", "gemini"]

models[0]          # => "gpt-4"
models[-1]         # => "gemini"
models.length      # => 3
models.first       # => "gpt-4"
models.last        # => "gemini"
Enter fullscreen mode Exit fullscreen mode

Adding and removing:

models << "llama-3"          # append
models.push("mistral")       # same thing
models.delete("gemini")      # remove by value
models.pop                   # remove and return last
Enter fullscreen mode Exit fullscreen mode

The << shovel operator is idiomatic Ruby. You'll see it everywhere.

Hashes: Key-Value Pairs

Hashes are Ruby's dictionaries. If you're building AI apps, you'll live in hashes — they map directly to JSON:

config = {
  model: "gpt-4",
  temperature: 0.7,
  max_tokens: 1000,
  stream: true
}

config[:model]        # => "gpt-4"
config[:temperature]  # => 0.7
config[:missing]      # => nil (no error!)
Enter fullscreen mode Exit fullscreen mode

Those :model, :temperature things are symbols — lightweight, immutable strings. Ruby uses them as hash keys by convention because they're faster than strings.

Older syntax you'll still see in codebases:

# Hashrocket style (equivalent)
config = {
  :model => "gpt-4",
  :temperature => 0.7
}
Enter fullscreen mode Exit fullscreen mode

Both work. The key: value style is modern and preferred.

Iterators: Where Ruby Shines

This is Ruby's killer feature for data processing. Forget for loops.

each — Do something with every element

responses = ["Hello!", "How can I help?", "Goodbye."]

responses.each do |response|
  puts "AI said: #{response}"
end
Enter fullscreen mode Exit fullscreen mode

map — Transform every element

prompts = ["explain ruby", "what is rails", "how do blocks work"]

uppered = prompts.map { |p| p.upcase }
# => ["EXPLAIN RUBY", "WHAT IS RAILS", "HOW DO BLOCKS WORK"]

# Shorter: map with symbol-to-proc
uppered = prompts.map(&:upcase)
Enter fullscreen mode Exit fullscreen mode

That &:upcase shorthand calls .upcase on each element. Elegant.

select and reject — Filter

scores = [0.92, 0.45, 0.87, 0.31, 0.95]

passing = scores.select { |s| s > 0.8 }
# => [0.92, 0.87, 0.95]

failing = scores.reject { |s| s > 0.8 }
# => [0.45, 0.31]
Enter fullscreen mode Exit fullscreen mode

reduce — Accumulate into a single value

token_counts = [150, 230, 180, 95]

total = token_counts.reduce(0) { |sum, count| sum + count }
# => 655

# Shorthand
total = token_counts.sum  # => 655 (Ruby knows)
Enter fullscreen mode Exit fullscreen mode

find — First match

messages = [
  { role: "system", content: "You are helpful." },
  { role: "user", content: "Hello" },
  { role: "assistant", content: "Hi there!" }
]

first_user = messages.find { |m| m[:role] == "user" }
# => { role: "user", content: "Hello" }
Enter fullscreen mode Exit fullscreen mode

Chaining: The Ruby Way

The real power is chaining these together:

raw_inputs = ["  hello ", "", "WHAT IS AI?  ", nil, "  ", "explain ruby"]

cleaned = raw_inputs
  .compact                          # remove nils
  .map(&:strip)                     # trim whitespace
  .reject(&:empty?)                 # remove blanks
  .map(&:downcase)                  # normalize case
# => ["hello", "what is ai?", "explain ruby"]
Enter fullscreen mode Exit fullscreen mode

Each method returns a new array, so you chain the next operation. No temp variables, no mutation, just a pipeline.

String Manipulation

Strings come up constantly when building prompts and parsing responses:

prompt = "  What is machine learning?  "

prompt.strip           # => "What is machine learning?"
prompt.downcase        # => "  what is machine learning?  "
prompt.include?("machine")  # => true
prompt.gsub("machine", "deep")  # => "  What is deep learning?  "
prompt.split(" ")      # => ["What", "is", "machine", "learning?"]
Enter fullscreen mode Exit fullscreen mode

Building prompts with heredocs:

context = "Ruby programming"
question = "How do blocks work?"

prompt = <<~PROMPT
  You are an expert in #{context}.
  Answer the following question concisely.

  Question: #{question}

  Provide a code example in your answer.
PROMPT
Enter fullscreen mode Exit fullscreen mode

The <<~PROMPT syntax creates a multi-line string with interpolation and strips leading indentation. You'll use this for every prompt template.

Joining arrays into strings:

tags = ["ruby", "ai", "tutorial"]
tags.join(", ")    # => "ruby, ai, tutorial"
tags.join("\n")    # => each on its own line
Enter fullscreen mode Exit fullscreen mode

A Practical Example

Let's combine everything into something real — processing a batch of API responses:

responses = [
  { model: "gpt-4", tokens: 150, score: 0.92, content: "Ruby is great." },
  { model: "claude-3", tokens: 230, score: 0.45, content: "" },
  { model: "gpt-4", tokens: 180, score: 0.87, content: "Blocks are closures." },
  { model: "gemini", tokens: 95, score: 0.31, content: nil }
]

good_responses = responses
  .select { |r| r[:score] > 0.8 }
  .reject { |r| r[:content].nil? || r[:content].empty? }
  .map { |r| "#{r[:model]}: #{r[:content]}" }

puts good_responses
# gpt-4: Ruby is great.
# gpt-4: Blocks are closures.

total_tokens = responses.sum { |r| r[:tokens] }
puts "Total tokens used: #{total_tokens}"  # => 655
Enter fullscreen mode Exit fullscreen mode

Filter, transform, aggregate. Three lines. No loops, no index tracking, no off-by-one errors.

What's Next

Next up: object-oriented Ruby — classes, modules, inheritance, and mixins. The patterns that make Rails (and every Ruby gem) tick.

You now have enough data structure knowledge to read most Ruby code. The rest is practice.

Top comments (0)