Ruby Logo

String Manipulation

Learn comprehensive string operations including splitting, joining, case conversion, and advanced string processing.

Home Ruby String Manipulation

String Manipulation in Ruby

Why Master String Manipulation?

String manipulation is fundamental to programming. Most applications process text data, and Ruby provides powerful, intuitive string methods that solve common problems:

  • Data Processing: Clean, format, and transform user input, CSV data, logs
  • Text Analysis: Parse documents, extract keywords, analyze content
  • User Interface: Format output, create readable messages, generate reports
  • API Integration: Transform data between different formats and systems
  • Search & Filtering: Find, extract, and manipulate specific text patterns

Problems String Manipulation Solves

1. Data Cleaning & Normalization

Remove whitespace, standardize case, eliminate unwanted characters, and ensure consistent formatting. Essential for processing user input, importing data, and maintaining data quality.

2. Text Assembly & Decomposition

Split strings into arrays for processing, join arrays into formatted text, and build complex strings from components. Critical for parsing CSV data, creating URLs, building SQL queries, and generating reports.

3. Content Transformation

Change text case for consistency, format names and titles properly, and transform text for different contexts. Important for display formatting, database storage, and user experience.

Learning Path

1 Basic string operations and formatting
2 Splitting and joining text efficiently
3 Case conversion and text normalization
4 Character manipulation and cleaning
5 Performance optimization and best practices

1. Essential String Operations

String Basics - Length, Access, and Concatenation

Understanding basic string operations is crucial. Ruby strings are mutable (unlike some languages), which means you can modify them in place, but you should understand when this happens and when new strings are created.

# String creation and basic properties
text = "Hello, Ruby!"
puts text.length          # => 12
puts text.size            # => 12 (alias for length)
puts text.empty?          # => false
puts "".empty?            # => true

# Character access
puts text[0]              # => "H" (first character)
puts text[-1]             # => "!" (last character)
puts text[0, 5]           # => "Hello" (substring from index, length)
puts text[7..11]          # => "Ruby!" (range of characters)

# String concatenation
greeting = "Hello"
name = "Ruby"

# Different ways to combine strings
puts greeting + ", " + name + "!"     # => "Hello, Ruby!"
puts "#{greeting}, #{name}!"          # => "Hello, Ruby!" (interpolation)
puts [greeting, name].join(", ") + "!" # => "Hello, Ruby!" (using join)

# Building strings efficiently
result = String.new
result << "Hello"                     # Mutates the string (efficient)
result << ", "
result << "Ruby!"
puts result                           # => "Hello, Ruby!"
💡 Performance Tips
  • Use interpolation: "#{var}" is faster than "" + var
  • Use << for building: str << "text" modifies in place (faster)
  • Join arrays: array.join is efficient for multiple concatenations

2. Splitting and Joining Text

The split Method - Breaking Text Apart

The split method is essential for parsing structured text like CSV data, breaking sentences into words, or extracting data from formatted strings. It returns an array, making the data easy to process.

# Basic splitting
text = "apple,banana,cherry,date"
fruits = text.split(",")
puts fruits  # => ["apple", "banana", "cherry", "date"]

# Split on whitespace (default)
sentence = "Ruby is a great language"
words = sentence.split
puts words  # => ["Ruby", "is", "a", "great", "language"]

# Split with limit (maximum number of parts)
data = "name:john:age:25:city:nyc"
parts = data.split(":", 3)
puts parts  # => ["name", "john", "age:25:city:nyc"]

# Split with regex patterns
mixed = "apple123banana456cherry"
items = mixed.split(/\d+/)
puts items  # => ["apple", "banana", "cherry"]

# Real-world examples

# Parse CSV-like data
csv_row = "John,Doe,30,Engineer,New York"
name, surname, age, job, city = csv_row.split(",")
puts "#{name} #{surname} is a #{age}-year-old #{job} from #{city}"

# Extract domain from email
email = "user@example.com"
username, domain = email.split("@")
puts "Username: #{username}, Domain: #{domain}"

# Parse log entries
log_entry = "2024-03-15 10:30:45 INFO User login successful"
parts = log_entry.split(" ", 4)
date, time, level, message = parts
puts "#{level}: #{message} at #{date} #{time}"

The join Method - Assembling Text

The join method is the reverse of split - it combines array elements into a string with a separator. This is extremely useful for creating formatted output, building URLs, or generating SQL queries.

# Basic joining
fruits = ["apple", "banana", "cherry"]
puts fruits.join(", ")          # => "apple, banana, cherry"
puts fruits.join(" | ")         # => "apple | banana | cherry"
puts fruits.join                # => "applebananacherry" (no separator)

# Building sentences
words = ["Ruby", "is", "awesome"]
sentence = words.join(" ")
puts sentence  # => "Ruby is awesome"

# Creating file paths
path_parts = ["home", "user", "documents", "file.txt"]
file_path = path_parts.join("/")
puts file_path  # => "home/user/documents/file.txt"

# Building URLs
base_url = "https://api.example.com"
endpoint_parts = ["v1", "users", "123", "posts"]
full_url = base_url + "/" + endpoint_parts.join("/")
puts full_url  # => "https://api.example.com/v1/users/123/posts"

# Real-world examples

# Generate CSV output
user_data = ["John", "Doe", "30", "Engineer", "New York"]
csv_line = user_data.join(",")
puts csv_line  # => "John,Doe,30,Engineer,New York"

# Build SQL WHERE clause
conditions = ["age > 18", "status = 'active'", "city = 'New York'"]
where_clause = "WHERE " + conditions.join(" AND ")
puts where_clause  # => "WHERE age > 18 AND status = 'active' AND city = 'New York'"

# Create readable lists
items = ["apples", "bananas", "cherries"]
if items.length > 1
  list = items[0..-2].join(", ") + " and " + items[-1]
else
  list = items[0]
end
puts "We have #{list}"  # => "We have apples, bananas and cherries"
🎯 Common Use Cases
  • CSV Processing: Split rows into fields, join fields into rows
  • URL Building: Join path segments with "/" separators
  • SQL Generation: Join conditions with "AND" or "OR"
  • Tag Lists: Split "tag1,tag2,tag3" or join tags for display

3. Case Conversion and Text Formatting

Case Conversion Methods

Case conversion is critical for data normalization, user interface formatting, and ensuring consistency. Different contexts require different case formats - understanding when to use each is important.

# Basic case conversion
text = "Hello Ruby World"

puts text.downcase          # => "hello ruby world"
puts text.upcase            # => "HELLO RUBY WORLD"
puts text.swapcase          # => "hELLO rUBY wORLD"
puts text.capitalize        # => "Hello ruby world" (only first letter)

# Title case (each word capitalized) - not built-in, but common need
def title_case(str)
  str.split.map(&:capitalize).join(" ")
end
puts title_case(text)       # => "Hello Ruby World"

# Case conversion with Unicode support
international = "café naïve résumé"
puts international.upcase   # => "CAFÉ NAÏVE RÉSUMÉ"
puts international.downcase # => "café naïve résumé"

# Real-world examples

# Normalize user input for comparison
user_input = "JOHN@EXAMPLE.COM"
normalized_email = user_input.downcase
puts normalized_email       # => "john@example.com"

# Format names properly
first_name = "john"
last_name = "DOE"
full_name = "#{first_name.capitalize} #{last_name.capitalize}"
puts full_name              # => "John Doe"

# Create constants from user input
class_name = "user profile"
constant_name = class_name.upcase.gsub(" ", "_")
puts constant_name          # => "USER_PROFILE"

# Generate URL-friendly slugs
title = "My Amazing Blog Post!"
slug = title.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/^-|-$/, "")
puts slug                   # => "my-amazing-blog-post"

Advanced Text Formatting

Beyond simple case conversion, Ruby provides methods for advanced text formatting including padding, centering, and special formatting needs.

# String padding and alignment
text = "Ruby"

puts text.center(10)        # => "   Ruby   " (centered in 10 chars)
puts text.ljust(10)         # => "Ruby      " (left justified)
puts text.rjust(10)         # => "      Ruby" (right justified)
puts text.center(10, "*")   # => "***Ruby***" (custom padding character)

# Creating formatted tables
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]

puts "Name".ljust(10) + "Age".rjust(5)
puts "-" * 15
names.zip(ages).each do |name, age|
  puts name.ljust(10) + age.to_s.rjust(5)
end
# Output:
# Name      Age
# ---------------
# Alice      25
# Bob        30
# Charlie    35

# String repetition for formatting
separator = "=" * 50
puts separator
puts "REPORT TITLE".center(50)
puts separator

# Number formatting with padding
invoice_numbers = [1, 23, 456]
invoice_numbers.each do |num|
  formatted = "INV-" + num.to_s.rjust(5, "0")
  puts formatted  # => "INV-00001", "INV-00023", "INV-00456"
end

4. String Cleaning and Character Manipulation

Whitespace Management

Whitespace handling is crucial when processing user input, reading files, or parsing data. Unexpected whitespace is a common source of bugs in text processing applications.

# Whitespace removal
messy_text = "  Hello, Ruby!  \n\t"

puts messy_text.strip         # => "Hello, Ruby!" (removes leading/trailing)
puts messy_text.lstrip        # => "Hello, Ruby!  \n\t" (removes leading only)
puts messy_text.rstrip        # => "  Hello, Ruby!" (removes trailing only)

# Check for whitespace
puts "   ".strip.empty?       # => true (useful for validation)

# Remove specific characters
phone = "(123) 456-7890"
digits_only = phone.delete("()- ")
puts digits_only              # => "1234567890"

# Remove multiple character sets
text = "Hello123World456!"
letters_only = text.delete("0-9!@#$%^&*()")
puts letters_only             # => "HelloWorld"

# Squeeze repeated characters
spaced = "Hello     Ruby    World"
normalized = spaced.squeeze(" ")
puts normalized               # => "Hello Ruby World"

# Real-world cleaning examples

# Clean user input
user_name = "  John    Doe  \n"
clean_name = user_name.strip.squeeze(" ")
puts clean_name               # => "John Doe"

# Normalize phone numbers
raw_phone = "+1 (555) 123-4567 ext. 890"
clean_phone = raw_phone.delete("^0-9")  # Keep only digits
puts clean_phone              # => "15551234567890"

# Clean CSV data
csv_field = " \"Product Name\" \t"
clean_field = csv_field.strip.delete('"')
puts clean_field              # => "Product Name"

Character Replacement and Transformation

The tr (translate) method provides efficient character-to-character replacement, perfect for simple transformations like converting characters or creating simple ciphers.

# Character translation (tr method)
text = "Hello, Ruby World!"

# Replace characters one-to-one
puts text.tr("aeiou", "*")           # => "H*ll*, R*by W*rld!"
puts text.tr("A-Z", "a-z")           # => "hello, ruby world!" (downcase)
puts text.tr("a-z", "A-Z")           # => "HELLO, RUBY WORLD!" (upcase)

# Character ranges and sets
puts text.tr("0-9", "X")             # Replace digits with X
puts text.tr("^a-zA-Z", "")          # Keep only letters (^ means "not")

# ROT13 encoding (simple cipher)
alphabet = "abcdefghijklmnopqrstuvwxyz"
rot13_key = "nopqrstuvwxyzabcdefghijklm"
message = "hello ruby"
encoded = message.tr(alphabet, rot13_key)
puts encoded                         # => "uryyb ehol"
decoded = encoded.tr(rot13_key, alphabet)
puts decoded                         # => "hello ruby"

# Real-world transformations

# Convert spaces to underscores for file names
filename = "My Important Document.txt"
safe_filename = filename.tr(" ", "_").tr("A-Z", "a-z")
puts safe_filename                   # => "my_important_document.txt"

# Remove accents for URL slugs (basic version)
accented = "café naïve"
unaccented = accented.tr("àáâãäåæçèéêëìíîïñòóôõöøùúûüý",
                        "aaaaaaceeeeiiiinoooooouuuuy")
puts unaccented                      # => "cafe naive"

# Simple text obfuscation
credit_card = "1234-5678-9012-3456"
masked = credit_card.tr("0-9", "*").gsub(/-\*{4}$/, "-#{credit_card[-4..-1]}")
puts masked                          # => "****-****-****-3456"

Real-World String Manipulation Examples

CSV Data Processing

# Process CSV data with string methods
def process_csv_row(row)
  # Split and clean each field
  fields = row.split(",").map do |field|
    field.strip.delete('"')  # Remove quotes and whitespace
  end

  # Return structured data
  {
    name: fields[0]&.split&.map(&:capitalize)&.join(" "),
    email: fields[1]&.downcase&.strip,
    age: fields[2]&.to_i,
    city: fields[3]&.strip&.split&.map(&:capitalize)&.join(" ")
  }
end

csv_data = 'john doe,"JOHN@EXAMPLE.COM",  25  ,"new york"'
result = process_csv_row(csv_data)
puts result
# => {:name=>"John Doe", :email=>"john@example.com", :age=>25, :city=>"New York"}

Log File Processing

# Parse and analyze log entries
def parse_log_entry(line)
  parts = line.strip.split(" ", 4)
  return nil if parts.length < 4

  {
    date: parts[0],
    time: parts[1],
    level: parts[2].upcase,
    message: parts[3],
    severity: severity_score(parts[2])
  }
end

def severity_score(level)
  case level.upcase
  when "ERROR" then 3
  when "WARN"  then 2
  when "INFO"  then 1
  else 0
  end
end

logs = [
  "2024-03-15 10:30:45 error Database connection failed",
  "2024-03-15 10:30:46 info User login successful",
  "2024-03-15 10:30:47 warn Cache miss for key user:123"
]

logs.each do |log|
  parsed = parse_log_entry(log)
  puts "#{parsed[:level]}: #{parsed[:message]}" if parsed[:severity] > 1
end

Name Formatting & Validation

# Intelligent name formatting
def format_name(full_name)
  return "" if full_name.nil? || full_name.strip.empty?

  # Clean and split
  parts = full_name.strip.squeeze(" ").split

  # Capitalize each part, handling special cases
  formatted_parts = parts.map do |part|
    # Handle prefixes and suffixes
    case part.downcase
    when "mc", "mac", "o'"
      part.downcase.capitalize
    when "ii", "iii", "iv", "jr", "sr"
      part.upcase
    else
      # Handle hyphenated names
      if part.include?("-")
        part.split("-").map(&:capitalize).join("-")
      else
        part.capitalize
      end
    end
  end

  formatted_parts.join(" ")
end

names = [
  "john doe",
  "mary o'connor",
  "james smith jr",
  "anne-marie dubois",
  "robert mac donald iii"
]

names.each do |name|
  puts "#{name} -> #{format_name(name)}"
end

URL and Slug Generation

# Generate URL-friendly slugs from titles
def create_slug(title)
  return "" if title.nil? || title.strip.empty?

  # Convert to lowercase and handle special characters
  slug = title.downcase
               .strip
               .gsub(/[àáâãäå]/, 'a')
               .gsub(/[èéêë]/, 'e')
               .gsub(/[ìíîï]/, 'i')
               .gsub(/[òóôõö]/, 'o')
               .gsub(/[ùúûü]/, 'u')
               .gsub(/[ñ]/, 'n')
               .gsub(/[ç]/, 'c')
               .gsub(/[^a-z0-9\s-]/, '')  # Remove special chars
               .gsub(/\s+/, '-')          # Replace spaces with hyphens
               .gsub(/-+/, '-')           # Remove duplicate hyphens
               .gsub(/^-|-$/, '')         # Remove leading/trailing hyphens

  slug.empty? ? "untitled" : slug
end

titles = [
  "My Amazing Blog Post!",
  "café & Restaurant Guide",
  "  How to... Program? (Beginner's Guide)  ",
  "Ruby on Rails: The Complete Guide",
  "!!!@#$%"
]

titles.each do |title|
  puts "\"#{title}\" -> \"#{create_slug(title)}\""
end

Best Practices and Common Pitfalls

🎯 Best Practices

  • Always validate input: Check for nil, empty strings, and unexpected formats
  • Use appropriate methods: join() for arrays, interpolation for known strings, << for building
  • Consider encoding: Be aware of character encoding when processing international text
  • Normalize consistently: Apply the same cleaning/formatting rules throughout your application
  • Test edge cases: Empty strings, nil values, unicode characters, very long strings

⚠️ Common Pitfalls

Mutating vs Non-Mutating Methods

Methods with ! modify the original string, methods without ! return a new string. str.strip! changes str, str.strip returns a new string.

Performance in Loops

Avoid using + for string concatenation in loops. Use <<, join(), or StringIO instead.

Encoding Issues

Always handle encoding properly, especially when reading files or processing user input with international characters.

🚀 Next Steps

With solid string manipulation skills, you can handle most text processing tasks. Continue building your expertise with:

  • Regular Expressions: Pattern matching for complex text processing
  • Parsing & Formatting: Advanced text formatting with sprintf and custom formatters
  • File Processing: Apply these techniques to read and process large text files
  • Web Development: Use string manipulation for URL processing, form validation, and data sanitization

Quick Navigation

Related Topics

Video Tutorial

Watch and learn string manipulation

Pro Tip: After reading through the content above, watch this video to reinforce your understanding and see the concepts in action!