Blog

  • Swift Control Flow

    Decision-Making Statements in Swift

    Decision-making statements in Swift allow the program to choose a specific block of code to execute based on a given condition. These statements evaluate conditions and return a boolean value (true or false). If the condition is true, the associated block of code is executed; otherwise, an alternate block is executed. Swift supports five types of decision-making statements:

    1. Simple if
    2. if-else
    3. if-else if-else
    4. Nested if
    5. Switch

    Simple if Statement

    In Swift, the if statement is used to execute a block of code based on the evaluation of one or more conditions. This type of statement is often referred to as a branch statement. It allows the program to “branch” and execute specific blocks of code when certain conditions are met.

    For example, consider a real-life scenario: you plan to visit a market, and your parent instructs you, “If the plastic containers are on sale, buy two containers.” This is a conditional statement where the action, “buy two containers,” will only occur if the condition, “plastic containers are on sale,” is true.

    Conditional statements are essential in programming as they help introduce logic and decision-making into the code.

    Syntax:

    if (condition) {
        // Body of the if statement
    }
    • The if statement evaluates the condition inside the parentheses ().
    • If the condition evaluates to true, the block of code inside the curly braces {} is executed.
    • If the condition evaluates to false, the block of code is skipped, and the control moves to the next statement.

    Example 1: Basic if Statement

    // Swift program demonstrating the if statement
    
    let val = 30
    
    // Checking if the number is greater than zero
    if (val > 0) {
        // Body of the if statement
        print("The given number is positive.")
    }
    
    // Statement after the if statement (always executed)
    print("Learning if statement in Swift.")

    Output:

    The given number is positive.
    Learning if statement in Swift.

    Explanation:

    • A variable val is declared with a value of 30.
    • The condition val > 0 is evaluated. Since 30 > 0, the condition is true.
    • The code inside the if block is executed, printing: "The given number is positive."
    • The statement after the if block, "Learning if statement in Swift.", is executed regardless of the condition’s result.
    • If val were set to -20, the condition would be false, and only the statement outside the if block would be executed.

    Example 2: Checking Eligibility

    This person is eligible for voting.
    Only 18+ people are eligible for voting.

    Output:

    Integer Type        Min                    Max
    UInt8               0                     255
    UInt16              0                     65535
    UInt32              0                     4294967295
    UInt64              0                     18446744073709551615
    Int8               -128                   127
    Int16              -32768                 32767
    Int32              -2147483648            2147483647
    Int64              -9223372036854775808   9223372036854775807

    Example 3: Using Multiple if Statements

    You can use multiple if statements to check independent conditions instead of using if-else.

    // Swift program demonstrating multiple if statements
    
    let number = 9
    
    // First condition
    if (number % 2 == 0) {
        print("\(number) is an even number")
    }
    
    // Second condition
    if (number % 2 != 0) {
        print("\(number) is an odd number")
    }
    
    // Third condition
    if (number == 9) {
        print("Given number \(number) is equal to the given condition number")
    }

    Output:

    9 is an odd number
    Given number 9 is equal to the given condition number

    Explanation:

    • The first if checks if the number is even (number % 2 == 0), which is false for 9, so this block is skipped.
    • The second if checks if the number is odd (number % 2 != 0), which is true for 9, so it prints: "9 is an odd number."
    • The third if checks if the number equals 9, which is true, so it prints: "Given number 9 is equal to the given condition number."

    If-else Statement

    In Swift, the if-else statement is used to execute a block of code based on a condition and provide an alternative block of code to execute when the condition is not met. This allows programmers to handle conditional logic effectively, ensuring that the program can make decisions dynamically.

    For example, imagine you’re at the market, and your parent says, “If biscuits are on sale, buy biscuits; otherwise, buy chips.” This is a typical if-else condition where one action is executed if the condition is true, and an alternative action is executed if the condition is false.

    Syntax of the if-else Statement

    if (condition) {
        // Body of the if statement
    } else {
        // Body of the else statement
    }
    • The if condition is evaluated.
    • If the condition evaluates to true, the code inside the if block is executed.
    • If the condition evaluates to false, the code inside the else block is executed.
    • The program then continues with the code after the if-else block.

    Examples:

    // Swift program to demonstrate the use of if-else statement
    
    // Declare and initialize a variable
    let val = 40
    
    // Check if the number is equal to 40
    if (val == 40) {
        print("Both the numbers are equal")
    } else {
        print("Both the numbers are not equal")
    }
    
    // Code after if…else statement
    // This statement is always executed
    print("Learning if…else statement in Swift.")

    Output:

    Both the numbers are equal
    Learning if…else statement in Swift.

    Explanation:

    • The variable val is initialized with a value of 40.
    • The condition val == 40 evaluates to true, so the code inside the if block is executed, printing: "Both the numbers are equal".
    • The else block is skipped.
    • The statement after the if-else block, "Learning if…else statement in Swift.", is executed regardless of the condition.
    • If val were set to 45, the condition would evaluate to false, and the output would change to:
    Both the numbers are not equal
    Learning if…else statement in Swift.

    Example 2: Checking Voting Eligibility

    // Swift program to demonstrate the use of if-else statement
    
    // Declare and initialize a variable
    let age = 80
    
    // Checking if age is greater than or equal to 18
    if (age >= 18) {
        print("This person is eligible for voting")
    } else {
        // Executes when the condition is false
        print("This person is not eligible for voting")
    }
    
    // Code after if…else statement
    print("Only 18+ people are eligible for voting")

    Output:

    This person is eligible for voting
    Only 18+ people are eligible for voting

    Explanation:

    • The variable age is initialized with 80.
    • The condition age >= 18 evaluates to true, so the code inside the if block is executed, printing: "This person is eligible for voting".
    • The else block is skipped.
    • The final statement after the if-else block is executed: "Only 18+ people are eligible for voting".
    • If age were set to 16, the output would be:
    This person is not eligible for voting
    Only 18+ people are eligible for voting

    If-else-if Statement

    In Swift, the if-else if-else statement allows you to evaluate multiple conditions and execute the code block corresponding to the first true condition. If none of the conditions are satisfied, the else block executes by default.

    Syntax

    if (condition1) {
        // Block of code for condition1
    } else if (condition2) {
        // Block of code for condition2
    } else if (condition3) {
        // Block of code for condition3
    }
    .
    .
    .
    else {
        // Block of code for all other cases
    }
    • The program checks each condition in sequence.
    • The first condition that evaluates to true will execute its associated block of code.
    • If none of the conditions are true, the else block is executed.

    Example 1: Grading System

    let number = 85
    
    if (number >= 90) {
        print("Grade A")
    } else if (number >= 75) {
        print("Grade B")
    } else if (number >= 60) {
        print("Grade C")
    } else {
        print("Grade D")
    }

    Output:

    Grade B

    Explanation:

    • The variable number is assigned the value 85.
    • The first condition, number >= 90, evaluates to false.
    • The second condition, number >= 75, evaluates to true.
    • The program executes the code block associated with number >= 75, printing: "Grade B".
    • No further conditions are evaluated because one has already been satisfied.

    Example 2: Checking Specific Values

    let number = 20
    
    // Condition 1
    if (number == 10) {
        print("Number is 10")
    }
    // Condition 2
    else if (number == 15) {
        print("Number is 15")
    }
    // Condition 3
    else if (number == 20) {
        print("Number is 20")
    }
    // Default case
    else {
        print("Number is not present")
    }

    Output:

    Number is 20

    Explanation:

    • The variable number is initialized with the value 20.
    • The first condition, number == 10, evaluates to false.
    • The second condition, number == 15, also evaluates to false.
    • The third condition, number == 20, evaluates to true, so the corresponding code block executes, printing: "Number is 20".

    Nested if-else Statement

    In Swift, a nested if-else statement occurs when an if or else block contains another if-else statement inside it. This structure is used when multiple levels of conditions need to be checked. The outer if condition determines if the inner if-else will be executed.

    Syntax:

    // Outer if condition
    if (condition1) {
        // Inner if condition
        if (condition2) {
            // Block of Code for condition2
        } else {
            // Block of Code if condition2 is false
        }
    } else {
        // Inner if condition
        if (condition3) {
            // Block of Code for condition3
        } else {
            // Block of Code if condition3 is false
        }
    }
    • If condition1 is true, the outer if block is executed, and the program checks condition2.
    • If condition1 is false, the program moves to the outer else block and evaluates condition3.
    • Additional layers of if-else can be added as needed.

    Example 1: Finding the Greatest Value

    import Swift
    
    var a = 100
    var b = 200
    var c = 300
    
    // Outer if statement
    if (a > b) {
        // Inner if statement
        if (a > c) {
            // Statement 1
            print("100 is Greater")
        } else {
            // Statement 2
            print("300 is Greater")
        }
    } else {
        // Inner if statement
        if (b > c) {
            // Statement 3
            print("200 is Greater")
        } else {
            // Statement 4
            print("300 is Greater")
        }
    }

    Output:

    300 is Greater

    Explanation:

    1. The outer if condition checks whether a > b. Since 100 > 200 is false, the program skips to the else block.
    2. Inside the else block, the inner if checks whether b > c. Since 200 > 300 is also false, the program executes the inner else block.
    3. The inner else block prints “300 is Greater” because c has the highest value.

    Example 2: Checking Number Properties

    import Swift
    
    var number = 5
    
    // Outer if statement
    if (number >= 0) {
        // Inner if statement
        if (number == 0) {
            // Statement 1
            print("Number is 0")
        } else {
            // Statement 2
            print("Number is greater than 0")
        }
    } else {
        // Statement 3
        print("Number is smaller than 0")
    }

    Output:

    Number is greater than 0

    Switch Statement

    In Swift, a switch statement allows the program to control its flow based on the value of a variable. Once a matching condition is found, the corresponding block is executed, and the control exits the switch block. A default case handles values that do not match any condition, though it’s optional if the switch is exhaustive. Omitting a default in a non-exhaustive switch results in a compile-time error.

    Syntax:

    var myVariable = value
    
    switch myVariable {
        case condition1:
            expression1
            fallthrough // Optional
        case condition2:
            expression2
            fallthrough // Optional
        ...
        default: // Optional if the switch is exhaustive
            expression
    }
    Why Use Switch Statements?

    Switch statements provide a cleaner and more readable alternative to multiple if-else statements. They are especially useful when evaluating a variable against multiple cases.

    Example 1: Basic Switch Statement

    // Example: Print a string based on a character
    
    var myCharacter = "B"
    
    switch myCharacter {
    case "A":
        print("Apple")
    case "B":
        print("Boy")
    case "C":
        print("Cat")
    case "D":
        print("Dog")
    default:
        print("Invalid")
    }

    Output:

    Boy
    Fallthrough Statement

    The fallthrough keyword forces the execution to proceed to the next case, regardless of whether its condition matches.

    Example: Using Fallthrough

    var myCharacter = "B"
    
    switch myCharacter {
    case "A":
        print("Apple")
    case "B":
        print("Boy")
        fallthrough
    case "C":
        print("Cat")
    case "D":
        print("Dog")
    default:
        print("Invalid")
    }

    Output:

    Boy
    Cat
    No Implicit Fallthrough

    Unlike other languages like C, Swift doesn’t allow implicit fallthrough between cases. Each case must contain an executable body, or the compiler will raise an error.

    Example:

    var myCharacter = "B"
    
    switch myCharacter {
    case "A":
        print("Apple")
    case "B":
        // Missing body
    case "C":
        print("Cat")
    case "D":
        print("Dog")
    default:
        print("Invalid")
    }

    Error:

    'case' label in a 'switch' should have at least one executable statement
    Interval Matching

    Swift enables interval-based matching within case conditions.

    Example:

    var myInteger = 18
    
    switch myInteger {
    case 2:
        print("Equal to 2")
    case 3..<5:
        print("Between 3 and 5")
    case 6..<10:
        print("Between 6 and 10")
    case 11..<22:
        print("Between 11 and 22")
    default:
        print("Invalid")
    }

    Output:

    Between 11 and 22
    Tuple Matching

    Switch statements can evaluate tuples to test multiple values simultaneously. An underscore (_) serves as a wildcard.

    Example:

    var myTuple = (4, 10)
    
    switch myTuple {
    case (2, 3):
        print("First case gets executed")
    case (1...3, 5...11):
        print("Second case gets executed")
    case (1...5, 8...13):
        print("Third case gets executed")
    case (11...13, 15...18):
        print("Fourth case gets executed")
    default:
        print("Invalid")
    }

    Output:

    Third case gets executed
    Value Binding

    Switch statements allow temporary variables to be declared in case conditions. These variables are accessible only within their respective case blocks.

    Example:

    var myTuple = (2, 4, 6)
    
    switch myTuple {
    case (let myElement1, 3, 6):
        print("myElement1 is \(myElement1)")
    case (let myElement1, let myElement2, 6):
        print("myElement1 is \(myElement1), myElement2 is \(myElement2)")
    case (let myElement1, let myElement2, let myElement3):
        print("myElement1 is \(myElement1), myElement2 is \(myElement2), myElement3 is \(myElement3)")
    }

    Output:

    myElement1 is 2, myElement2 is 4
    Using where Clause in a Switch Statement

    The where clause adds additional conditions to a case.

    Example:

    var myTuple = (20, 10)
    
    switch myTuple {
    case let (myElement1, myElement2) where myElement1 == 3 * myElement2:
        print("myElement1 is thrice of myElement2")
    case let (myElement1, myElement2) where myElement1 == 2 * myElement2:
        print("myElement1 is twice of myElement2")
    case let (myElement1, myElement2) where myElement1 == myElement2:
        print("myElement1 is equal to myElement2")
    default:
        print("Invalid")
    }

    Output:

    myElement1 is twice of myElement2
    Compound Cases

    Swift allows multiple conditions to share a single case body by separating them with commas.

    Example:

    var myCharacter = "B"
    
    switch myCharacter {
    case "A", "B":
        print("Either Apple or Boy")
    case "C":
        print("Cat")
    case "D":
        print("Dog")
    default:
        print("Invalid")
    }

    Output:

    Either Apple or Boy

    Loops

    In general, loops are used for iteration, which means repeating a set of instructions. With loops, we can execute tasks multiple times, as required. A loop can run indefinitely until its specified condition is no longer satisfied. Typically, we define a condition to terminate the loop. For instance, if we want to display “Hello World” 100 times, we use a loop and specify a condition such as n <= 100. The loop will terminate when the condition is no longer met.

    Types of Loops in Swift

    1. for-in loop
    2. while loop
    3. repeat-while loop

    1. for-in Loop: The for-in loop is used for iterating over a sequence, such as arrays, dictionaries, ranges, or sets. Unlike an if statement, which executes a block of code only once if a condition is met, the for-in loop repeatedly executes the block as long as the condition is satisfied.

    Syntax:

    for item in range {
        // Statements
    }

    Example:

    // Swift program to find if a number is a perfect number
    
    // Declaring variables
    var number = 6
    var sum = 0
    
    // Iterating to find divisors of the number
    for i in 1...(number - 1) {
        if number % i == 0 {
            sum += i
        }
    }
    
    // Checking if the sum equals the number
    if sum == number {
        print("The given number is a perfect number")
    } else {
        print("The given number is not a perfect number")
    }

    Output:

    The given number is a perfect number

    2. While Loopwhile loop repeatedly executes a block of code as long as the specified condition evaluates to true. Unlike the for-in loop, the while loop requires explicit initialization, condition checking, and variable updates.

    Syntax:

    while condition {
        // Statements
        increment/decrement
    }

    Example:

    // Swift program to calculate the factorial of a number
    
    // Declaring variables
    var num = 5
    var factorial = 1
    var i = 1
    
    // Iterating to calculate factorial
    while i <= num {
        factorial *= i
        i += 1
    }
    
    print("The factorial of the given number is \(factorial)")

    Output:

    The factorial of the given number is 120

    3. Repeat-While Loop: The repeat-while loop, similar to the do-while loop in C, executes the block of code at least once before evaluating the condition. It is also referred to as an exit-controlled loop.

    Syntax:

    repeat {
        // Statements
    } while condition

    Example:

    // Swift program to demonstrate repeat-while loop
    
    // Declaring variables
    var start = 1
    let limit = 10
    
    // Iterating using repeat-while loop
    repeat {
        print(start)
        start += 1
    } while start <= limit

    Output:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    Break Statement

    The break statement is a control statement used to immediately terminate the execution of the current loop or switch statement. When the break condition is met, the loop stops its iterations, and control passes to the first statement following the loop. In simpler terms, the break statement halts the current program flow based on specified conditions. It can be used within loops or switch statements to terminate execution earlier, especially when the number of iterations isn’t predetermined or depends on dynamic conditions.

    Syntax:

    break

    1. for-in Loop: The for-in loop in Swift iterates over a sequence, such as a range, array, or string. The break statement can terminate the loop when a specific condition is satisfied.

    Syntax:

    for item in sequence {
        // Code block
        if condition {
            break
        }
    }

    Example:

    // Swift program demonstrating the use of break in a for-in loop
    
    print("Numbers in the range:")
    
    // Loop from 1 to 10
    for number in 1...10 {
        if number == 7 {
            break
        }
        print(number)
    }

    Output:

    Numbers in the range:
    1
    2
    3
    4
    5
    6

    2. While Loop: The while loop runs as long as the given condition is true. The break statement can be used to terminate the loop based on an additional condition.

    Syntax:

    while condition {
        // Code block
        if condition {
            break
        }
    }

    Example:

    // Swift program demonstrating the use of break in a while loop
    
    var count = 1
    
    // Loop to print the first 4 multiples of 3
    while count <= 10 {
        print("3 x \(count) = \(3 * count)")
        if count == 4 {
            break
        }
        count += 1
    }

    Output:

    3 x 1 = 3
    3 x 2 = 6
    3 x 3 = 9
    3 x 4 = 12

    3. Nested Loops: The break statement can also be used in nested loops to terminate only the inner loop or control the flow of nested structures.

    Syntax:

    for item1 in sequence1 {
        for item2 in sequence2 {
            if condition {
                break
            }
        }
    }

    Example:

    // Swift program demonstrating break in nested loops
    
    for outer in 1...3 {
        for inner in 1...3 {
            if outer == 2 {
                break
            }
            print("Outer: \(outer), Inner: \(inner)")
        }
    }

    Output:

    Outer: 1, Inner: 1
    Outer: 1, Inner: 2
    Outer: 1, Inner: 3
    Outer: 3, Inner: 1
    Outer: 3, Inner: 2
    Outer: 3, Inner: 3
  • Swift Data Types

    In any programming language, data types are crucial for determining the type of data stored in a variable. In Swift, variables are used to store data, and their type determines what kind of data they hold. This helps the operating system allocate appropriate memory and ensures that only the correct type of data is stored. For instance, an integer variable can only hold integer values, not strings. Swift supports the following primary data types:
    Int, String, Float, Double, Bool, and Character.

    Integer and Floating-Point Numbers

    Integers are whole numbers that can be positive, negative, or zero. They do not contain fractional components. In programming, integers are classified as:

    • Unsigned integers (UInt): Zero or positive numbers.
    • Signed integers (Int): Can include negative numbers.

    Swift provides integers in various bit sizes: 8-bit, 16-bit, 32-bit, and 64-bit. The naming convention follows a pattern similar to C. For example:

    • A signed 16-bit integer is represented as Int16.
    • An unsigned 8-bit integer is represented as UInt8.
    Integer Bounds

    The following table shows the minimum and maximum values for each integer type:

    Integer TypeMinMax
    UInt80255
    UInt16065,535
    UInt3204,294,967,295
    UInt64018,446,744,073,709,551,615
    Int8-128127
    Int16-32,76832,767
    Int32-2,147,483,6482,147,483,647
    Int64-9,223,372,036,854,775,8089,223,372,036,854,775,807

    Swift also provides two additional types:

    • Int: Platform-native integer, equivalent to Int32 on 32-bit platforms and Int64 on 64-bit platforms.
    • UInt: Platform-native unsigned integer, similar to UInt32 or UInt64 depending on the platform.
    Finding Minimum and Maximum Values

    Swift allows us to find the bounds of integer types using the .min and .max properties. Here’s an example:

    Example:

    print("Integer Type        Min                    Max")
    print("UInt8           \(UInt8.min)         \(UInt8.max)")
    print("UInt16          \(UInt16.min)        \(UInt16.max)")
    print("UInt32          \(UInt32.min)        \(UInt32.max)")
    print("UInt64          \(UInt64.min)        \(UInt64.max)")
    print("Int8            \(Int8.min)          \(Int8.max)")
    print("Int16           \(Int16.min)         \(Int16.max)")
    print("Int32           \(Int32.min)         \(Int32.max)")
    print("Int64           \(Int64.min)         \(Int64.max)")

    Output:

    Integer Type        Min                    Max
    UInt8               0                     255
    UInt16              0                     65535
    UInt32              0                     4294967295
    UInt64              0                     18446744073709551615
    Int8               -128                   127
    Int16              -32768                 32767
    Int32              -2147483648            2147483647
    Int64              -9223372036854775808   9223372036854775807
    Floating-Point Numbers

    Floating-point numbers can represent both integers and numbers with fractional components. Swift provides two types of floating-point numbers:

    TypeBit SizeDecimal Precision
    Double64-bitUp to 15 decimal places
    Float32-bitUp to 6 decimal places

    These numbers can represent values like: 5.670.012345.6789, etc.

    Examples

    // Using Double
    var largeDecimal: Double = 12345.6789012345
    print("Double value is:", largeDecimal)
    
    // Using Float
    var smallDecimal: Float = 5.6789
    print("Float value is:", smallDecimal)

    Output:

    Double value is: 12345.6789012345
    Float value is: 5.6789

    Strings in Swift

    Strings in Swift are collections of characters. For example, "Hello, World!" is a string. Swift strings are Unicode-compliant and case-insensitive. They are encoded using UTF-8, which defines how Unicode data is stored in bytes.

    In Swift, strings are a fundamental data type and are often implemented as an array of characters. They can also represent more general collections or sequences of data elements.

    Creating a String

    Strings in Swift can be created using string literals, the String keyword, or the String initializer.

    Syntax:

    var str: String = "Hello, Swift!"
    var str2 = "Swift Programming"

    Examples:

    // String creation using string literal
    var greeting = "Welcome to Swift Programming"
    print(greeting)
    
    // String creation using String instance
    var message = String("This is an example")
    print(message)
    
    // String creation using String keyword
    var note: String = "Enjoy Coding"
    print(note)

    Output:

    Welcome to Swift Programming
    This is an example
    Enjoy Coding
    Multi-Line String

    A multi-line string spans multiple lines and is enclosed in triple double quotes.

    Syntax:

    """
    This is a
    multi-line string
    example in Swift.
    """

    Example:

    let multilineString = """
    Swift strings are versatile.
    You can create multi-line strings
    easily with triple quotes.
    """
    
    print(multilineString)

    Output:

    Swift strings are versatile.
    You can create multi-line strings
    easily with triple quotes.
    Empty String

    An empty string is a string with no characters. You can create an empty string using either "" or the String() initializer.

    Syntax:

    var emptyStr = ""
    var anotherEmptyStr = String()

    Example:

    var empty = ""
    if empty.isEmpty {
        print("The string is empty.")
    }
    
    let anotherEmpty = String()
    if anotherEmpty.isEmpty {
        print("This is also an empty string.")
    }

    Output:

    The string is empty.
    This is also an empty string.
    String Concatenation

    Strings can be concatenated using various methods:

    1. Addition Operator (+):

    var result = str1 + str2

    2. Addition Assignment Operator (+=):

    result += str2

    3. Append Method:

    str1.append(str2)

    Example:

    let part1 = "Swift "
    let part2 = "is powerful."
    
    // Using +
    var sentence = part1 + part2
    print(sentence)
    
    // Using +=
    var phrase = "Learning "
    phrase += "Swift is fun."
    print(phrase)
    
    // Using append()
    var text = "Hello"
    text.append(", Swift!")
    print(text)

    Output:

    Swift is powerful.
    Learning Swift is fun.
    Hello, Swift!
    String Comparison

    Strings can be compared using the == (equal to) or != (not equal to) operators.

    Syntax:

    string1 == string2
    string1 != string2

    Example:

    let stringA = "Swift Programming"
    let stringB = "Learning Swift"
    
    if stringA == stringB {
        print("The strings are equal.")
    } else {
        print("The strings are not equal.")
    }

    Output:

    The strings are not equal.
    String Length

    The length of a string can be determined using the count property.

    Example:

    let text = "Swift Language"
    print("The length of '\(text)' is \(text.count).")

    Output:

    The length of 'Swift Language' is 14.
    String Interpolation

    String interpolation allows mixing variables, constants, and expressions into a string.

    Syntax:

    let str = "Swift \(version)"

    Example:

    let value = 42
    let multiplier = 2
    print("The result of \(value) times \(multiplier) is \(value * multiplier).")

    Output:

    The result of 42 times 2 is 84.
    String Iteration

    Strings can be iterated over using a for-in loop.

    Example:

    for char in "Swift" {
        print(char, terminator: " ")
    }

    Output:

    S w i f t
    Common String Functions and Properties
    NameDescription
    isEmptyChecks if the string is empty.
    hasPrefixChecks if the string starts with the specified prefix.
    hasSuffixChecks if the string ends with the specified suffix.
    countReturns the length of the string.
    utf8Returns the UTF-8 representation of the string.
    insert(_:at:)Inserts a value at a specified position.
    remove(at:)Removes a value at a specified position.
    reversed()Returns the reversed string.

    Example:

    let sample = "Swift Language"
    
    // Using isEmpty
    print("Is string empty? \(sample.isEmpty)")
    
    // Using hasPrefix
    print("Does string start with 'Swift'? \(sample.hasPrefix("Swift"))")
    
    // Using reversed()
    print("Reversed string: \(String(sample.reversed()))")

    Output:

    Is string empty? false
    Does string start with 'Swift'? true
    Reversed string: egaugnaL tfiwS

    String Functions and Operators in Swift

    A string is a sequence of characters that can either be a literal constant or a variable. For example, "Hello World" is a string of characters. Swift strings are Unicode-correct and locale-insensitive. They support various operations like comparison, concatenation, iteration, and more.

    Creating Strings in Swift

    You can create strings in Swift using either a String instance or a string literal.

    1. Using String() Initializer: The String() initializer creates a string instance.

    Syntax:

    var str = String("Example String")

    Example:

    // Using String() initializer
    var greeting = String("Welcome to Swift!")
    print(greeting)

    Output:

    Welcome to Swift!

    2. Using String Literal: Double quotes are used to create string literals, similar to other programming languages.

    Syntax:

    var str = "Hello"
    var strTyped: String = "World"

    Examples:

    // Creating strings using literals
    var message = "Learning Swift is fun!"
    print(message)
    
    var typedMessage: String = "Swift Programming"
    print(typedMessage)

    Output:

    Learning Swift is fun!
    Swift Programming
    Multi-line Strings 

    Swift supports multi-line strings using triple double quotes (""").

    Syntax:

    Learning Swift is fun!
    Swift Programming

    Examples:

    // Multi-line string example
    let paragraph = """
    Welcome to Swift!
    This example demonstrates
    multi-line strings.
    """
    print(paragraph)

    Output:

    Welcome to Swift!
    This example demonstrates
    multi-line strings.
    String Properties and Functions

    Empty String: An empty string has no characters and a length of zero.

    Example:

    // Creating an empty string
    var emptyLiteral = ""
    var emptyInstance = String()
    
    print("Is emptyLiteral empty? \(emptyLiteral.isEmpty)")
    print("Is emptyInstance empty? \(emptyInstance.isEmpty)")

    Output:

    Is emptyLiteral empty? true
    Is emptyInstance empty? true

    String Length: The count property returns the total number of characters in a string.

    Example:

    // Counting characters in a string
    let text = "Swift Programming"
    let length = text.count
    print("The length of the text is \(length)")

    Output:

    The length of the text is 18
    String Operations

    1. Concatenation: The + operator concatenates strings.

    Example:

    // Concatenating strings
    let firstName = "John"
    let lastName = "Doe"
    let fullName = firstName + " " + lastName
    print("Full Name: \(fullName)")

    Output:

    Full Name: John Doe

    2. Comparison: Use == to check equality and != to check inequality.

    Example:

    let a = "Swift"
    let b = "Swift"
    let c = "Python"
    
    // Equality
    print(a == b)  // true
    
    // Inequality
    print(a != c)  // true

    Output:

    true
    true
    String Functions:

    1. hasPrefix and hasSuffix: Check if a string starts or ends with a specific substring.

    Example:

    let sentence = "Learning Swift"
    print(sentence.hasPrefix("Learn"))  // true
    print(sentence.hasSuffix("Swift")) // true

    Output:

    true
    true

    2. lowercased() and uppercased(): Convert all characters in a string to lowercase or uppercase.

    Example:

    let text = "Swift Programming"
    print(text.lowercased())  // "swift programming"
    print(text.uppercased())  // "SWIFT PROGRAMMING"

    Output:

    swift programming
    SWIFT PROGRAMMING

    3. reversed(): Reverse the characters of a string.

    Example:

    let word = "Swift"
    let reversedWord = String(word.reversed())
    print("Original: \(word)")
    print("Reversed: \(reversedWord)")

    Output:

    Original: Swift
    Reversed: tfiwS

    4. insert(_:at:): Insert a character at a specified position.

    Example:

    var phrase = "Swift Programming"
    phrase.insert("!", at: phrase.endIndex)
    print(phrase)

    Output:

    Swift Programming!

    5. remove(at:): Remove a character at a specific index.

    Example:

    var text = "Hello Swift!"
    let index = text.index(text.startIndex, offsetBy: 6)
    let removedCharacter = text.remove(at: index)
    print("Modified text: \(text)")
    print("Removed character: \(removedCharacter)")

    Output:

    Modified text: Hello Sift!
    Removed character: w

    Data Type Conversions in Swift

    Type Conversion in Swift

    Type Conversion refers to the process of converting a variable or value from one data type to another. This allows instances to be checked and cast to specific class types. In Swift, type conversion is often performed during compile-time by the XCode compiler, provided the data types are compatible (e.g., integer, string, float, double).

    Type conversion involves explicitly casting a value to a different data type using the desired type as a function. For instance:

    var stringToFloatNum: Float = Float(67891)!

    Syntax:

    var <variable_name>: <data_type> = <convert_data_type>(<value>)

    1. Convert Integer to String: This program converts an integer value to a string and displays the result with its type.

    Example:

    import Swift
    
    var intNumber: Int = 789
    
    var intNumberToString: String = String(intNumber)
    
    print("Integer Value = \(intNumber) of type \(type(of: intNumber))")
    print("String Value = \(intNumberToString) of type \(type(of: intNumberToString))")

    Output:

    Integer Value = 789 of type Int
    String Value = 789 of type String

    2. Convert String to Integer: This program converts a string value to an integer and displays the result with its type.

    Example:

    import Swift
    
    var intNum: Int = 789
    
    var intNumToFloat: Float = Float(intNum)
    
    print("Integer Value = \(intNum) of type \(type(of: intNum))")
    print("Float Value = \(intNumToFloat) of type \(type(of: intNumToFloat))")

    Output:

    String Value = 789 of type String
    Integer Value = 789 of type Int

    3. Convert String to Integer: This program converts a string value to an integer and displays the result with its type.

    Example:

    import Swift
    
    var string: String = "789"
    
    var stringToInt: Int = Int(string)!
    
    print("String Value = \(string) of type \(type(of: string))")
    print("Integer Value = \(stringToInt) of type \(type(of: stringToInt))")

    Output:

    String Value = 789 of type String
    Integer Value = 789 of type Int

    4. Convert Integer to Float: This program converts an integer value to a float and displays the result with its type.

    Example:

    import Swift
    
    var intNum: Int = 789
    
    var intNumToFloat: Float = Float(intNum)
    
    print("Integer Value = \(intNum) of type \(type(of: intNum))")
    print("Float Value = \(intNumToFloat) of type \(type(of: intNumToFloat))")

    Output:

    Integer Value = 789 of type Int
    Float Value = 789.0 of type Float

    5. Convert Float to Integer: This program converts a float value to an integer and displays the result with its type.

    Example:

    import Swift
    
    var floatNum: Float = 789.0089
    
    var floatNumToInt: Int = Int(floatNum)
    
    print("Float Value = \(floatNum) of type \(type(of: floatNum))")
    print("Integer Value = \(floatNumToInt) of type \(type(of: floatNumToInt))")

    Output:

    Float Value = 789.0089 of type Float
    Integer Value = 789 of type Int

    6. Convert String to Float: This program converts a string value to a float and displays the result with its type.

    Example:

    import Swift
    
    var string: String = "789"
    
    var stringToFloatNum: Float = Float(string)!
    
    print("String Value = \(string) of type \(type(of: string))")
    print("Float Value = \(stringToFloatNum) of type \(type(of: stringToFloatNum))")

    Output:

    String Value = 789 of type String
    Float Value = 789.0 of type Float

    Convert String to Int Swift

    In Swift, you can convert a string into an integer using various methods. However, only numeric strings can be successfully converted to integers. Below are two common methods to perform this conversion.

    Declaring a String Variable

    A string in Swift is a collection of characters and can be declared using the String data type.

    Syntax:

    let myVariable: String

    Example:

    let myVariable = "Hello"
    Methods for String to Integer Conversion

    1. Using Int Initializer Swift provides an initializer function for integers that can convert a string to an Int. This initializer returns an optional integer. To handle non-numeric strings, you can use the nil-coalescing operator to provide a default value, such as 0.

    Example:

    // Converting a numeric string
    let myStringVariable = "25"
    let myIntegerVariable = Int(myStringVariable) ?? 0
    print("Integer Value:", myIntegerVariable)
    
    // Converting a non-numeric string
    let myStringVariable2 = "Hello"
    let myIntegerVariable2 = Int(myStringVariable2) ?? 0
    print("Integer Value:", myIntegerVariable2)

    Output:

    Integer Value: 25
    Integer Value: 0

    2. Using NSString:The NSString class in Swift allows strings to be passed by reference and includes the integerValue property, which can be used to convert an NSString to an integer.

    Example:

    import Foundation
    
    let myString = "25"
    
    // Converting a string to NSString and then to an integer using integerValue
    let myIntegerVariable = (myString as NSString).integerValue
    print("Integer Value:", myIntegerVariable)

    Output:

    Integer Value: 25
  • Swift Overview

    Swift is a powerful and intuitive programming language developed by Apple Inc. for iOS, macOS, watchOS, and tvOS app development. It was first released in 2014 and has quickly gained popularity due to its simplicity, safety, and performance.

    Key Features
    • Open Source: Swift is open-source, allowing developers to contribute and use it on a wide range of platforms.
    • Safety: Swift is designed with safety in mind, with features like optionals that help prevent null pointer errors.
    • Performance: Swift is optimized for performance, often outperforming Objective-C in various tasks.
    • Modern Syntax: Swift’s syntax is clean and concise, making it easier to read and write.

    Swift Basic Syntax

    Swift’s syntax is designed to be concise and expressive. Below are some basic syntax elements of Swift:

    Hello, World! Example

    Here’s a simple “Hello, World!” program in Swift:

    print("Hello, World!")

    Identifiers

    Identifiers are names used to identify variables, functions, or other user-defined items. They must begin with an alphabet (A-Z or a-z) or an underscore (_) and may contain letters, underscores, and digits. Swift is case-sensitive, so A and a are treated as distinct. Special characters like @#, and % are not allowed in identifiers.

    Examples:

    a, _temp, Temp, a_bc, a29b, temp12, temp_11

    To use a reserved word as an identifier, enclose it in backticks (`).
    Example:

    `exampleIdentifier`

    Keywords

    Keywords are reserved words with predefined meanings in Swift. They cannot be used as variable names or identifiers.

    Examples of Keywords:

    class, func, let, public, struct, return, for, while, if, else, true, false

    Every programming language has a set of reserved words used for internal processes or predefined actions. These reserved words, known as keywords, cannot be used as variable names, constant names, or other identifiers. To use a keyword as an identifier, enclose it in backticks (). For instance, while structis a keyword, “struct“ is a valid identifier. Note that backticks do not change the case sensitivity of identifiers (e.g.,aanda` are treated the same).

    In Swift, keywords are categorized into the following groups:

    1. Keywords in declaration
    2. Keywords in statements
    3. Keywords in expressions and types
    4. Keywords in specific contexts
    5. Keywords starting with the number sign (#)
    6. Keywords used in patterns (_)

    1. Keywords Used in Declarations: Keywords used in declarations include:

    associatedtype, class, deinit, enum, extension, fileprivate, func, import,
    init, inout, internal, let, open, operator, private, precedencegroup,
    protocol, public, rethrows, static, struct, subscript, typealias, var

    Example:

    import Swift
    
    // Creating a structure using the `struct` keyword
    struct Employee {
        var name = "John"
        var id = 1234
    }
    
    // Creating an instance of the structure
    var instance = Employee()
    
    // Accessing properties of the structure
    print("Employee Name:", instance.name)
    print("Employee ID:", instance.id)

    Output:

    Employee Name: John
    Employee ID: 1234

    2. Keywords Used in Statements: Keywords used in statements include:

    break, case, catch, continue, default, defer, do, else, fallthrough, for,
    guard, if, in, repeat, return, throw, switch, where, while

    Example:

    import Swift
    
    // Finding the age group
    let age = 70
    
    if age >= 60 {
        print("Senior Citizen")
    } else if age >= 40 {
        print("Middle-aged")
    } else {
        print("Young")
    }

    Output:

    Senior Citizen

    3. Keywords Used in Expressions and Types: Keywords in expressions and types include:

    Any, as, catch, false, is, nil, rethrows, self, Self, super, throw, throws,
    true, try

    Example:

    import Swift
    
    // Creating a class
    class Person {
        func name() {
            print("Hello! My name is Alice")
        }
    }
    
    // Creating a subclass
    class Employee: Person {
        override func name() {
            // Accessing the parent class method using `super`
            super.name()
            print("I work in the IT department")
        }
    }
    
    // Creating an instance of the subclass
    let employee = Employee()
    employee.name()

    Output:

    Hello! My name is Alice
    I work in the IT department

    4. Keywords Used in Specific Contexts: Keywords used in specific contexts include:

    associativity, convenience, didSet, dynamic, final, get, indirect, infix,
    lazy, left, mutating, none, nonmutating, optional, override, postfix,
    precedence, prefix, Protocol, required, right, set, some, Type, unowned,
    weak, willSet

    Example:

    import Swift
    
    // Creating a structure
    struct Data {
        var value: String
    
        // Using the `mutating` keyword
        mutating func updateValue() {
            self.value = "Updated Value"
        }
    }
    
    var data = Data(value: "Initial Value")
    data.updateValue()
    print(data.value)

    Output:

    Updated Value

    5. Keywords Starting with the Number Sign (#): Keywords starting with # include:

    #available, #colorLiteral, #column, #dsohandle, #elseif, #else, #endif,
    #error, #fileID, #fileLiteral, #filePath, #file, #function, #if,
    #imageLiteral, #keyPath, #line, #selector, #sourceLocation, #warning

    Example:

    import Swift
    
    // Using `#function` to display the function name
    func displayFunctionName() {
        print("You are in the function: \(#function)")
    }
    
    displayFunctionName()

    Output:

    You are in the function: displayFunctionName

    6. Keywords Used in Patterns (_): The underscore (_) is used as a wildcard in patterns.

    Example:

    import Swift
    
    // Printing a message 10 times
    for _ in 1...10 {
        print("Swift Programming")
    }

    Output:

    Swift Programming
    Swift Programming
    Swift Programming
    Swift Programming
    Swift Programming
    Swift Programming
    Swift Programming
    Swift Programming
    Swift Programming
    Swift Programming

    Semicolons (;)

    Semicolons are optional in Swift but are required when writing multiple statements on the same line.

    Examples:

    var a = 1; var b = 2
    print(a); print(b)

    Output:

    1
    2

    Whitespaces

    Whitespace separates elements in code and improves readability. It is mandatory between keywords and identifiers but optional elsewhere.

    Examples:

    var a = 1
    var b=2 // Valid but less readable
    var c = a + b // Preferred for readability

    Literals

    Literals represent fixed values in a program, such as integers, decimals, strings, or booleans. These values can be directly used without computation. By default, literals in Swift don’t have a type. Primitive type variables can be assigned literals. For instance:

    • 12 is an integer literal.
    • 3.123 is a floating-point literal.
    • “Swift” is a string literal.
    • true is a boolean literal.
    Swift Protocols for Literals

    Literals must conform to specific protocols depending on their type:

    • ExpressibleByIntegerLiteral: For integer literals.
    • ExpressibleByFloatLiteral: For floating-point literals.
    • ExpressibleByStringLiteral: For string literals.
    • ExpressibleByBooleanLiteral: For boolean literals.

    Example:

    let number: Int32 = 25
    Types of Literals in Swift

    1. Integer Literals: Used to represent whole numbers. If no alternate base is specified, they default to base-10 (decimal). Negative integers are represented using a minus sign (-).

    Examples:

    let positiveNumber = 25
    let negativeNumber = -25
    let numberWithUnderscore = 2_500 // Same as 2500

    Alternate Representations:

    • Binary: Prefix with 0b
    • Octal: Prefix with 0o
    • Hexadecimal: Prefix with 0x

    Example

    let decimalNumber = 10
    let binaryNumber = 0b1010
    let octalNumber = 0o12
    let hexadecimalNumber = 0xA
    
    print("Decimal Number:", decimalNumber)
    print("Binary Number:", binaryNumber)
    print("Octal Number:", octalNumber)
    print("Hexadecimal Number:", hexadecimalNumber)

    Comments make code more understandable. They are ignored by the compiler.

    Single-line comment syntax:

    Decimal Number: 10
    Binary Number: 10
    Octal Number: 10
    Hexadecimal Number: 10

    2. Floating-Point Literals: Represent numbers with a decimal point. The default type is Double. You can explicitly specify the type as Float.

    Examples:

    let number: Int32 = 25

    Exponential Representation:

    let scientificNumber1 = 1.25e3 // 1250
    let scientificNumber2 = 1.25e-3 // 0.00125

    Hexadecimal Floating-Point:

    • Prefix: 0x
    • Exponent: p
    let hexFloat1 = 0xFp1 // 30.0
    let hexFloat2 = 0xFp-1 // 7.5

    Example:

    let decimalFloatingNumber = 0.123
    let scientificNotation = 1.23e2
    let hexFloat = 0xFp1
    
    print("Decimal Floating-Point:", decimalFloatingNumber)
    print("Scientific Notation:", scientificNotation)
    print("Hexadecimal Floating-Point:", hexFloat)

    Output:

    \Decimal Floating-Point: 0.123
    Scientific Notation: 123.0
    Hexadecimal Floating-Point: 30.0

    3. String Literals: Represent a sequence of characters enclosed in double quotes. Strings can be multi-line, escape special characters, or use interpolation.

    • Single-Line String:
    let message = "Hello, World!"
    • Multi-Line String:
    vlet multilineString = """
    This is a
    multi-line string.
    """
    • Escape Sequences:
    let escapedString = "Hello,\nWorld!"
    • String Interpolation:
    let name = "Swift"
    let greeting = "Welcome to \(name) programming!"

    Example:

    let singleLine = "Learning Swift"
    let multiLine = """
    Multi-line
    String Example
    """
    let interpolated = "The value is \(42)"
    
    print(singleLine)
    print(multiLine)
    print(interpolated)

    Output:

    Learning Swift
    Multi-line
    String Example
    The value is 42

    4. Boolean Literals: Boolean literals can be true or false. The default type is Bool.

    Examples:

    let value1 = true
    let value2 = false
    
    print("Value 1:", value1)
    print("Value 2:", value2)

    Output:

    Value 1: true
    Value 2: false

    Comments

    Comments make code more understandable. They are ignored by the compiler.

    Single-line comment syntax:

    // This is a single-line comment

    Multi-line comment syntax:

    /*
    This is a
    multi-line comment
    */

    Print Statement

    The print function displays text, variables, or expressions on the screen.

    Examples:

    print("Hello Swift") // Prints: Hello Swift
    
    var x = 10
    print(x) // Prints: 10
    
    print(5 + 3) // Prints: 8

    Output:

    Hello Swift
    10
    8

    Import Class

    The import keyword brings definitions from another module into the current program.

    Syntax:

    import module

    Examples:

    import Swift
    print("Welcome to Swift Programming")

    Output:

    Welcome to Swift Programming

    Variables and Constants

    Constants and variables in Swift are used to store data with specific types, such as numbers, characters, or strings. A constant cannot be changed after it has been assigned a value, whereas a variable can be modified during the program execution.

    Declaring Constants & Variables in Swift

    Constants and variables must be declared within their scope before being used. The let keyword is used for constants, and the var keyword is used for variables. Swift does not require semicolons (;) to terminate statements.

    let temperature = 20 // Declaring a constant
    var count = 0        // Declaring a variable

    You can declare multiple constants or variables in a single line, separated by commas:

    let x = 10, y = 20, z = 30 // Multiple constants
    var p = 1, q = 2, r = 3    // Multiple variables
    Type Annotation

    Type annotation explicitly specifies the type of a constant or variable at the time of declaration. For example:

    var name: String // Declares a string variable without initialization
    var age: Int     // Declares an integer variable

    If multiple constants or variables are of the same type, you can declare them in one line:

    var firstName, lastName, city: String
    Naming Constants & Variables

    Names can contain any character except mathematical symbols, private Unicode scalar values, arrows, or whitespace characters:

    let pi = 3.14159
    print("Value of pi: \(pi)")
    Printing Constants & Variables

    print() Function

    The print(_:separator:terminator:) function is used to display the value of constants or variables.

    Example:

    let country = "India"
    print(country)

    Output:

    India

    String Interpolation: String interpolation allows embedding constants or variables directly within a string, replacing placeholders with actual values.

    Example:

    var city = "Delhi"
    print("I live in \(city)")

    Output:

    I live in Delhi
  • Swift Tutorial Roadmap

    Introduction to Swift

    Overview of Swift

    Swift is a powerful, modern, and safe programming language developed by Apple for building applications on iOS, macOS, watchOS, and tvOS. It is designed to be fast, expressive, and beginner-friendly while offering high performance for production apps.


    Swift Basic Syntax

    Identifiers and Keywords

    • Swift identifiers and naming conventions
    • Reserved keywords and their usage

    Semicolons and Whitespaces

    • Use of semicolons (;) in Swift
    • Importance of whitespaces in Swift syntax

    Literals

    • Integer literals
    • Floating-point literals
    • String literals
    • Boolean literals

    Comments

    • Single-line comments
    • Multi-line comments

    Print Statement

    • Using print() to display output

    Import Statements

    • Importing frameworks and modules

    Variables and Constants

    Declaring Variables and Constants

    • Using var for variables
    • Using let for constants

    Data Types

    • Type inference in Swift
    • Explicit type annotations

    Numeric Types

    • Integers
    • Floating-point numbers

    Strings in Swift

    String Basics

    • Creating and initializing strings

    String Functions

    • Common string manipulation methods

    Operators in Swift

    Types of Operators

    • Arithmetic operators
    • Comparison operators
    • Logical operators
    • Assignment operators

    Data Type Conversions in Swift

    Type Conversion

    • Converting between different data types

    String to Int Conversion

    • Safely converting String to Int

    Control Flow

    Decision-Making Statements

    • if statement
    • if-else statement
    • if-else-if ladder
    • Nested if-else
    • switch statement

    Loops

    Looping Statements

    • for-in loop
    • while loop
    • repeat-while loop

    Break Statement

    • Using break to exit loops

    Swift Functions

    Functions Overview

    • Defining and calling functions

    Parameters and Return Values

    • Function parameters
    • Returning values from functions

    In-Out Parameters

    • Modifying parameters using inout

    Nested Functions

    • Functions defined inside other functions

    Function Overloading

    • Multiple functions with the same name

    Closures in Swift

    Closures Overview

    • Introduction to closures and their syntax

    Escaping and Non-Escaping Closures

    • Understanding closure lifetimes and memory behavior

    Swift Sets

    Introduction to Sets

    • Creating and using sets

    Set Operations

    • Removing the first element
    • Using removeAll()
    • Checking if an element exists
    • Counting elements
    • Sorting a set
    • Checking if a set is empty
    • Shuffling set elements

    Sets vs Arrays

    • Differences and appropriate use cases

    Swift Arrays

    Arrays Overview

    • Creating and initializing arrays

    Array Properties

    • Count and capacity

    Common Array Operations

    • Removing the first element
    • Counting elements
    • Reversing arrays
    • Using joined()
    • Checking if an array contains an element
    • Sorting arrays
    • Swapping elements
    • Checking if an array is empty

    Swift Dictionary

    Dictionary Basics

    • Creating dictionaries
    • Creating empty dictionaries

    Dictionary Operations

    • Changing values
    • Accessing elements
    • Iterating over dictionaries

    Advanced Dictionary Usage

    • Creating a dictionary from two arrays
    • Removing items
    • Converting dictionaries to arrays

    Dictionary Properties

    • Common dictionary properties

    Swift Tuples

    Tuples in Swift

    • Creating and using tuples

    Swift Object-Oriented Programming (OOP)

    Structures in Swift

    • Creating and using structures

    Properties

    • Stored properties
    • Computed properties

    Methods

    • Instance methods
    • Type methods

    Function vs Method

    • Key differences

    Deinitialization

    • Understanding deinitializers

    Typecasting in Swift

    Typecasting

    • Checking types at runtime
    • Upcasting and downcasting

    Timers in Swift

    Repeating Timers

    • Implementing repeating timers

    Non-Repeating Timers

    • Creating one-time timers

    Swift Error Handling

    Errors in Swift

    • Error handling fundamentals

    try, try?, and try!

    • Differences and when to use each

    Additional Swift Topics

    Typealias

    • Creating and using type aliases

    Swift Structures vs C Structures

    • Key differences

    Swift Interview Preparation

    Interview Questions

    • Theoretical questions
    • Advanced coding challenges
    • Scenario-based questions

    Swift Project Ideas

    Project Ideas

    • Beginner-level Swift projects
    • Intermediate and advanced Swift project ideas

  • Miscellaneous

    Ruby Types of Iterators

    Iterators in Ruby allow you to loop through collections like arrays, hashes, and ranges. Ruby provides several built-in methods to iterate over these collections.

    Common Iterators:

    1. each: Iterates over each element in a collection.
    2. map: Creates a new array by applying a block to each element.
    3. select: Returns an array of elements for which the block returns true.
    4. reject: Returns an array of elements for which the block returns false.
    5. reduce (also known as inject): Combines all elements of an enumerable by applying a binary operation.
    6. times: Iterates a given number of times.
    7. upto: Iterates from the current number up to the specified number.
    8. downto: Iterates from the current number down to the specified number.
    9. step: Iterates from the current number to the specified number, incrementing by the step value.

    Examples:

    1. each: The each iterator returns all elements of an array or hash, one by one.
    Syntax:

    collection.each do |variable_name|
      # code to iterate
    end

    Example:

    # Using each iterator with a range
    (0..5).each do |i|
      puts i
    end
    
    # Using each iterator with an array
    letters = ['A', 'B', 'C']
    letters.each do |letter|
      puts letter
    end

    2. Collect Iterator: The collect iterator returns all elements of a collection, either an array or hash, and can be used to transform elements.
    Syntax:

    result = collection.collect { |element| block }

    Example:

    # Using collect iterator to multiply each element
    numbers = [1, 2, 3, 4]
    result = numbers.collect { |x| x * 2 }
    puts result

    3. Times Iterator: The times iterator repeats a block of code a specified number of times, starting from 0 up to one less than the specified number.
    Syntax:

    t.times do |i|
      # code to execute
    end

    Example:

    # Using times iterator
    3.times do |i|
      puts i
    end

    4. Upto Iterator: The upto iterator starts from a number and continues up to the specified upper limit.
    Syntax:

    start.upto(limit) do |i|
      # code to execute
    end

    Example:

    # Using upto iterator
    1.upto(3) do |i|
      puts i
    end

    5. Downto Iterator: The downto iterator starts from a number and goes down to a specified lower limit.
    Syntax:

    start.downto(limit) do |i|
      # code to execute
    end

    Example:

    # Using downto iterator
    5.downto(2) do |i|
      puts i
    end

    6. Step Iterator: The step iterator is used when you want to skip a specified number of elements in a range during iteration.
    Syntax:

    range.step(step_value) do |i|
      # code to execute
    end

    Example:

    # Using step iterator to skip by 2
    (0..10).step(2) do |i|
      puts i
    end

    7. Each_Line Iterator: The each_line iterator iterates through each line in a string, often used when working with multi-line text.
    Syntax:

    string.each_line do |line|
      # code to execute
    end

    Example:

    # Using each_line iterator
    "Hello\nWorld\nRuby".each_line do |line|
      puts line
    end

    Ruby getters and setters Method

    In Ruby, instance variables (denoted by @) are private by default and are not accessible directly outside the class. To expose these variables for encapsulation purposes, Ruby provides getter and setter methods. A getter method retrieves the value of an instance variable, while a setter method assigns a value to it.

    Example 1: Simple Getter Method

    # Ruby program using getter method
    class Website
      # Constructor to initialize the class with a name instance variable
      def initialize(name)
        @name = name
      end
    
      # Classical getter method
      def name
        @name
      end
    end
    
    # Creating an object of the class
    site = Website.new("www.example.com")
    puts site.name

    Output:

    www.example.com

    In this example, the getter method allows access to the @name variable outside the class.

    Example 2: Simple Setter Method

    # Ruby program using setter method
    class Website
      # Constructor to initialize the class with a name instance variable
      def initialize(name)
        @name = name
      end
    
      # Classical getter method
      def name
        @name
      end
    
      # Classical setter method
      def name=(name)
        @name = name
      end
    end
    
    # Creating an object of the class
    site = Website.new("www.example.com")
    puts site.name
    
    # Changing the instance variable from outside the class
    site.name = "www.updated.com"
    puts site.name

    Output:

    www.example.com
    www.updated.com

    Here, the setter method allows modification of the @name variable from outside the class.

    Accessor Methods in Ruby

    Writing multiple getter and setter methods manually can make the code verbose, especially as the class grows. Ruby provides a convenient way to create these methods using accessor methods.

    • attr_reader: Automatically generates a getter method.
    • attr_writer: Automatically generates a setter method.
    • attr_accessor: Generates both getter and setter methods.

    Example : 

    # Ruby Program using accessor for getter and setter
    class Website
      # attr_accessor combines both getter and setter
      attr_accessor :url
    
      # Constructor to initialize the website URL
      def initialize(url)
        @url = url
      end
    end
    
    # Creating an object of the class
    my_site = Website.new("www.example.com")
    
    # Accessing the website using the getter
    puts my_site.url # Output: www.example.com
    
    # Changing the website using the setter
    my_site.url = "www.updatedsite.com"
    
    # Accessing the updated website
    puts my_site.url # Output: www.updatedsite.com

    Output:

    www.example.com
    www.updatedsite.com
  • Ruby Threading

    Ruby Introduction to Multi-threading

    Multi-threading in Ruby is a powerful feature that enables the concurrent execution of different parts of a program, optimizing CPU usage. Each part of the program is called a thread, and threads are essentially lightweight processes within a larger process. A single-threaded program executes instructions sequentially, while a multi-threaded program can run multiple threads concurrently, utilizing multiple cores of a processor. This leads to reduced memory usage and improved performance compared to single-threaded programs.

    Before Ruby version 1.9, Ruby used green threads, meaning thread scheduling was handled by Ruby’s interpreter. However, from Ruby 1.9 onwards, threading is handled by the operating system, making thread execution more efficient. Despite this improvement, two threads in the same Ruby application still cannot run truly concurrently.

    In Ruby, a multi-threaded program can be created using the Thread class. A new thread is usually created by calling a block using Thread.new.

    Creating Threads in Ruby

    To create a new thread in Ruby, you can use any of three blocks: Thread.newThread.start, or Thread.fork. The most commonly used is Thread.new. Once a thread is created, the main (original) thread resumes execution after the thread creation block, running in parallel with the new thread.

    Syntax:

    # Main thread runs here
    
    # Creating a new thread
    Thread.new {
        # Code to run inside the new thread
    }
    
    # Main thread continues executing

    Example:

    # Ruby program to demonstrate thread creation
    
    def task1
      count = 0
      while count <= 2
        puts "Task 1 - Count: #{count}"
        sleep(1) # Pauses execution for 1 second
        count += 1
      end
    end
    
    def task2
      count = 0
      while count <= 2
        puts "Task 2 - Count: #{count}"
        sleep(0.5) # Pauses execution for 0.5 seconds
        count += 1
      end
    end
    
    # Creating threads for each task
    thread1 = Thread.new { task1() }
    thread2 = Thread.new { task2() }
    
    # Ensuring the main program waits for threads to finish
    thread1.join
    thread2.join
    
    puts "All tasks completed"

    Output:

    Task 1 - Count: 0
    Task 2 - Count: 0
    Task 2 - Count: 1
    Task 1 - Count: 1
    Task 2 - Count: 2
    Task 1 - Count: 2
    All tasks completed

    Note: The exact output may vary depending on how the operating system allocates resources to the threads.

    Terminating Threads

    When the Ruby program finishes, all associated threads are also terminated. However, you can manually kill a thread using the Thread.kill method.

    Syntax:

    Thread.kill(thread)
    Thread Variables and Scope

    Each thread has access to local, global, and instance variables within the scope of the block where it is defined. However, variables defined within a thread block are local to that thread and cannot be accessed by other threads. If multiple threads need to access the same variable concurrently, proper synchronization is required.

    Example:

    # Ruby program to demonstrate thread variables
    
    # Global variable
    $message = "Hello from Ruby!"
    
    def task1
      counter = 0
      while counter <= 2
        puts "Task 1 - Counter: #{counter}"
        sleep(1)
        counter += 1
      end
      puts "Global message: #{$message}"
    end
    
    def task2
      counter = 0
      while counter <= 2
        puts "Task 2 - Counter: #{counter}"
        sleep(0.5)
        counter += 1
      end
      puts "Global message: #{$message}"
    end
    
    # Creating threads for each task
    thread1 = Thread.new { task1() }
    thread2 = Thread.new { task2() }
    
    # Waiting for both threads to finish
    thread1.join
    thread2.join
    
    puts "Program finished"

    Output:

    Task 1 - Counter: 0
    Task 2 - Counter: 0
    Task 2 - Counter: 1
    Task 1 - Counter: 1
    Task 2 - Counter: 2
    Task 1 - Counter: 2
    Global message: Hello from Ruby!
    Global message: Hello from Ruby!
    Program finished

    In Ruby, threads are utilized to implement concurrent programming. Programs that require multiple threads use the Thread class, which provides a variety of methods to handle thread-based operations.

    Public Class Methods

    1. abort_on_exception: This method returns the status of the global “abort on exception” setting. By default, its value is false. If set to true, all threads are aborted when an exception is raised in any of them.

    Thread.abort_on_exception -> true or false

    2. abort_on_exception=: This method sets the new state of the global “abort on exception” flag. When set to true, threads are aborted when an exception arises. The return value is a boolean.

    Thread.abort_on_exception= bool -> true or false

    Example:

    # Ruby program to demonstrate abort_on_exception
    
    Thread.abort_on_exception = true
    
    thread = Thread.new do
      puts "Starting new thread"
      raise "An error occurred in the thread"
    end
    
    sleep(0.5)
    puts "Execution complete"

    Output:

    Starting new thread
    RuntimeError: An error occurred in the thread

    3. critical: Returns the current “thread critical” status. This flag indicates whether Ruby’s thread scheduler is in a critical section.

    Thread.critical -> true or false

    4. critical=: This method sets the global “thread critical” condition. When set to true, it blocks scheduling of any thread but allows new threads to be created and run. Thread-critical sections are used mainly in threading libraries.

    Thread.critical= bool -> true or false

    5. current: This method returns the currently running thread.

    Thread.current -> thread

    6. exit: Terminates the current thread and schedules another thread to run. If the thread is marked to be killed, it will return the thread. If it’s the main thread or the last thread, the program will exit.

    Thread.exit

    7. fork: Similar to start, this method initiates a new thread.

    Thread.fork { block } -> thread

    8. kill: This method terminates a specified thread.

    Thread.kill(thread)

    Example:

    # Ruby program to demonstrate the kill method
    
    counter = 0
    
    # Create a new thread
    thread = Thread.new { loop { counter += 1 } }
    
    # Sleep for a short time
    sleep(0.4)
    
    # Kill the thread
    Thread.kill(thread)
    
    # Check if the thread is alive
    puts thread.alive?  # Output: false

    9. list: This method returns an array of all the thread objects, whether they are runnable or stopped.

    Thread.list -> array

    Example:

    # Ruby program to demonstrate list method
    
    # First thread
    Thread.new { sleep(100) }
    
    # Second thread
    Thread.new { 1000.times { |i| i*i } }
    
    # Third thread
    Thread.new { Thread.stop }
    
    # List all threads
    Thread.list.each { |thr| p thr }

    Output:

    #<Thread:0x00007fc6fa0a8b98 sleep>
    #<Thread:0x00007fc6fa0a8cf8 run>
    #<Thread:0x00007fc6fa0a8d88 sleep>

    10. main: This method returns the main thread of the process. Each run of the program will generate a unique thread ID.

    Thread.main -> thread

    Example:

    # Ruby program to print the main thread's ID
    
    puts Thread.main

    Output:

    #<Thread:0x00007fcd92834b50>

    11. new: Creates and runs a new thread. Any arguments passed are given to the block.

    Thread.new([arguments]*) { |arguments| block } -> thread

    12. pass: Attempts to pass execution to another thread. The actual switching depends on the operating system.

    Thread.pass

    13. start: Similar to new. If the Thread class is subclassed, calling start from the subclass will not invoke the subclass’s initialize method.

    Thread.start([arguments]*) { |arguments| block } -> thread

    14. stop: Stops the current thread, putting it to sleep and allowing another thread to be scheduled. It also resets the critical condition to false.

    Thread.stop

    Example:

    # Ruby program to demonstrate stop and pass methods
    
    thread = Thread.new { print "Start"; Thread.stop; print "End" }
    
    # Pass control to another thread
    Thread.pass
    
    print "Main"
    thread.run
    thread.join

    Output:

    StartMainEnd

    Ruby Thread Class-Public Class Methods

    In Ruby, threads are utilized to enable concurrent programming. Programs requiring multiple threads rely on the Thread class to create and manage threads. The Thread class offers a variety of methods to perform specific tasks.

    Public Class Methods

    1. abort_on_exception:
    This method checks the global “abort on exception” setting and returns its current status. By default, this setting is false. When set to true, it ensures that all threads are terminated if an exception occurs in any thread.

    Thread.abort_on_exception -> true or false

    2. Thread.abort_on_exception=: This method determines if threads will abort when an exception occurs. If set to true, any exception in a thread will terminate the program.

    Example:

    Thread.abort_on_exception = true
    
    x = Thread.new do
      puts "Hello from the thread!"
      raise "Error raised in the thread"
    end
    
    sleep(0.5)
    puts "This won't be printed"

    Output:

    Hello from the thread!
    test.rb:6: Error raised in the thread (RuntimeError)
            from test.rb:4:in `initialize'
            from test.rb:4:in `new'
            from test.rb:4

    3. Thread.critical: This method retrieves or sets the global “thread critical” condition. When set to true, thread scheduling is prohibited, but new threads can still be created and run.

    Example:

    Thread.critical = true
    puts Thread.critical # Output: true
    Thread.critical = false
    puts Thread.critical # Output: false

    Output:

    true
    false

    4. Thread.current: Returns the currently executing thread.

    Example:

    puts Thread.current

    Output:

    #<Thread:0x00005623 @status="run">

    5. Thread.exit

    Terminates the currently running thread and schedules another thread to run.

    Example:

    x = Thread.new do
      puts "Running thread"
      Thread.exit
      puts "This won't be executed"
    end
    
    x.join
    puts "Thread exited"

    Output:

    Running thread
    Thread exited

    6. Thread.kill: Terminates the specified thread.

    Example:

    counter = 0
    
    x = Thread.new { loop { counter += 1 } }
    
    sleep(0.4)
    Thread.kill(x)
    sleep(0.5)
    
    puts x.alive?

    Output:

    false

    7. Thread.list: Returns an array of all threads (either runnable or stopped).

    Example:

    Thread.new { sleep(100) }
    Thread.new { 10000.times {|i| i * i } }
    Thread.new { Thread.stop }
    
    Thread.list.each { |thr| p thr }

    Output:

    #<Thread:0x00005633 sleep>
    #<Thread:0x00005634 run>
    #<Thread:0x00005635 sleep>
    #<Thread:0x00005636 run>

    8. Thread.main: Returns the main thread of the process.

    Example:

    puts Thread.main

    Output:

    #<Thread:0x0000123 main>

    9. Thread.new: Creates and starts a new thread to execute a block of code.

    Example:

    t = Thread.new do
      puts "Executing in a new thread"
    end
    
    t.join

    Output:

    Executing in a new thread

    10. Thread.passAttempts to pass execution to another thread, depending on the operating system’s thread scheduler.

    Example:

    Thread.new { 3.times { puts "Running task"; Thread.pass } }
    
    puts "Main thread"

    Output:

    Main thread
    Running task
    Running task
    Running task

    11. Thread.stop: Stops the currently running thread and schedules another thread.

    Example:

    x = Thread.new { print "Start"; Thread.stop; print "End" }
    
    Thread.pass
    print "Running Main Thread"
    
    x.run
    x.join

    Output:

    StartRunning Main ThreadEnd

    Ruby Thread Life Cycle & Its States

    The thread life cycle explains the progression of a thread from its creation to termination. A new thread can be created using Thread.newThread.start, or Thread.fork. There’s no need to start a thread explicitly—it starts running automatically when CPU resources are available. The value returned by Thread.new is a Thread object. The Thread class provides several methods to query and control thread behavior.

    A thread executes the block of code passed to Thread.new and terminates once the block finishes execution. The result of the last expression in the block is the thread’s value, which can be accessed using the value method of the Thread object. The value method returns the result only if the thread has completed execution; otherwise, it does not return any value. If an exception is raised within a thread (other than the main thread), the thread terminates.

    Thread States

    Ruby provides five thread states, representing the thread’s current status. You can check a thread’s status using the alive? and status methods.

    StateReturn Value
    Runnable“run”
    Sleeping“sleep”
    Aborting“aborting”
    Terminated normallyfalse
    Terminated with exceptionnil

    Example: Checking Thread Status:

    counter = 0
    
    # Create a new thread
    x = Thread.new { loop { counter += 1 } }
    
    # Check if the thread is alive
    puts x.alive?

    Output:

    true
    Main Thread

    In Ruby, the main thread is the top-level thread under which all other threads are spawned. The Ruby interpreter runs until both the main thread and all child threads complete their execution. The Thread.main method returns the main thread object. If an exception occurs in the main thread and is not caught, the interpreter will print an error message and exit. If an exception occurs in a non-main thread, the interpreter will terminate that particular thread.

    Example:

    # Ruby program to demonstrate the main thread
    
    # Display the main thread
    puts Thread.main
    
    # Create a new thread
    thread1 = Thread.new { sleep 100 }
    
    # List all threads
    Thread.list.each { |t| p t }
    
    # Print the current thread
    puts "Current thread: " + Thread.current.to_s
    
    # Create another thread
    thread2 = Thread.new { sleep 100 }
    
    # List all threads again
    Thread.list.each { |t| p t }
    
    # Print the current thread
    puts "Current thread: " + Thread.current.to_s
    
    # Kill the first thread
    Thread.kill(thread1)
    
    # Pass execution to the next thread
    Thread.pass
    
    # Kill the second thread
    Thread.kill(thread2)
    
    # List all threads after killing
    Thread.list.each { |t| p t }
    
    # Exit the main thread
    Thread.exit

    Outputs:

    #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2034d08 sleep>
    Current thread: #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2034d08 sleep>
    #<Thread:0x00007fcda2035b48 sleep>
    Current thread: #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2035b48 dead>
    Alternate Thread States: Pausing, Waking, and Killing

    Threads are created in the runnable state and are ready to execute. A thread can pause itself by entering the sleeping state using methods like Thread.stop or Kernel.sleep. If Kernel.sleep is called without an argument, the thread pauses indefinitely. When a time argument is provided, the thread resumes after the specified time expires and reenters the runnable state.

    Paused threads can be resumed using methods like wakeup and run. These methods change the thread’s state from sleeping to runnable. The run method also triggers the thread scheduler, potentially allocating CPU resources to the resumed thread. The wakeup method resumes a specific thread without invoking the thread scheduler.

    To terminate a thread, you can use the killterminate, or exit methods. These methods place the thread into the “terminated normally” state.

    Example: Pausing and Resuming Threads

    t = Thread.new do
      puts "Thread started"
      sleep(1)
      puts "Thread resumed"
    end
    
    puts "Pausing main thread"
    Kernel.sleep(2)
    
    t.run
    t.join

    Outputs:

    Thread started
    Pausing main thread
    Thread resumed
  • Collections

    Ruby Arrays

    An array is a collection of elements, which can be of the same or different types, stored in contiguous memory locations. The concept behind arrays is to group multiple elements together under a single variable name. In Ruby, arrays can store various types of objects like numbers, strings, hashes, symbols, or even other arrays. Arrays in Ruby are indexed, meaning each element is associated with an index number, starting from 0 for positive indices. Negative indices start from -1, representing elements from the end of the array.

    Example of an Array:

    ["Ruby", 42, 88.5, "Programming"]

    In this example, the array contains four elements: a string ("Ruby"), an integer (42), a float (88.5), and another string ("Programming").

    • Positive indices start from 0.
    • Negative indices start from -1 and represent elements from the end of the array.

    Ruby also supports multidimensional arrays (arrays within arrays), but here we will focus on one-dimensional arrays.

    Creating a 1-D Array in Ruby

    There are various ways to create an array in Ruby, but two of the most common methods are:

    1. Using the new method: The new method can be used to create arrays by specifying its size and optional default values. The Array.new method can take zero, one, or more arguments.

    Syntax:

    array_name = Array.new

    Example:

    arr = Array.new

    Here, arr is an empty array. To create an array with a specific size, pass a number to the new method.

    Example:

    arr = Array.new(10) # Creates an array of size 10
    puts arr.size       # Output: 10
    puts arr.length     # Output: 10

    You can also specify a default value for each element.

    Example:

    arr = Array.new(3, "Hello")
    puts "#{arr}"  # Output: ["Hello", "Hello", "Hello"]

    Program Example:

    # Ruby program demonstrating array creation using the new method
    arr1 = Array.new()         # Empty array
    arr2 = Array.new(5)        # Array with 5 nil elements
    arr3 = Array.new(4, "Hi")  # Array of size 4 with default element "Hi"
    
    puts arr1.size             # Output: 0
    puts arr2.length           # Output: 5
    puts arr3.size             # Output: 4
    puts "#{arr3}"             # Output: ["Hi", "Hi", "Hi", "Hi"]

    2. Using the literal constructor []The literal constructor [] can be used to quickly define an array with elements inside square brackets.

    Example:

    arr = ['x', 'y', 'z', 'a', 'b']
    puts "#{arr}"              # Output: ["x", "y", "z", "a", "b"]
    puts "Size of array: #{arr.size}"   # Output: 5

    3. Accessing Elements from an Array: In Ruby, you can access elements of an array using indices. The most common way is to use the element’s index number in square brackets []. If you try to access an element that doesn’t exist, Ruby will return nil.

    Example:

    # Ruby program to demonstrate element access
    arr = ["Hello", "World", "Ruby", "Array"]
    puts arr[1]     # Output: World
    puts arr[-1]    # Output: Array (last element)

    4. Accessing Multiple Elements: To retrieve multiple elements from an array, pass two arguments to the [] method, specifying the starting index and the number of elements to retrieve.

    Example:

    # Ruby program to demonstrate accessing multiple elements
    arr = ["Hello", "World", "Ruby", "Array"]
    puts arr[1, 2]  # Output: ["World", "Ruby"]

    Ruby String

    A string in Ruby is a sequence of characters that may include letters, numbers, or symbols. Ruby treats strings as objects, allowing them to be manipulated directly. Unlike some other languages, Ruby strings are mutable, meaning their content can be changed without creating new instances.

    Creating Strings:

    Strings in Ruby can be created by enclosing characters in either single or double quotes. You can also assign them to variables without specifying a type, as Ruby is dynamically typed.

    Example:

    # Creating strings using single and double quotes
    
    puts 'String created using single quotes'
    puts "String created using double quotes"
    
    # Storing strings in variables
    str1 = "Hello"
    str2 = 'World'
    
    # Displaying stored strings
    puts str1
    puts str2

    Output:

    String created using single quotes
    String created using double quotes
    Hello
    World

    Note: The main difference between using single and double quotes is that double quotes allow variable interpolation, while single quotes do not.

    Example:

    # Difference between single and double quotes
    name = "Ruby"
    
    puts 'This is #{name}'   # No interpolation
    puts "This is #{name}"   # Interpolation happens

    Output:

    This is #{name}
    This is Ruby
    Strings as Objects:

    Ruby is an object-oriented language, so strings are objects with associated data and methods.

    Example:

    # Ruby strings as objects
    
    str1 = "Programming"
    str2 = String.new("Language")
    
    puts str1  # Output: Programming
    puts str2  # Output: Language
    Accessing String Elements:

    You can access individual characters or substrings in a string using square brackets []. The index of the character or range can be specified.

    Example:

    # Accessing string elements
    
    str = "Learning Ruby"
    
    # Accessing substring using a string
    puts str["Learning"]   # Output: Learning
    
    # Accessing character using index
    puts str[1]            # Output: e
    
    # Accessing character using negative index
    puts str[-1]           # Output: y
    
    # Accessing substring using range of indices
    puts str[9, 4]         # Output: Ruby
    
    # Using range operators
    puts str[9..12]        # Output: Ruby
    Creating Multiline Strings:

    Ruby offers multiple ways to create multiline strings, which can be achieved using double quotes, %/ /, or heredoc syntax.

    Example:

    # Ruby program demonstrating multiline strings
    
    # Using double quotes with newline characters
    puts "This is a multiline string.\nIt spans multiple lines.\n"
    
    # Using %/ /
    puts %/This is another way to create a multiline string
    which spans multiple lines./
    
    # Using heredoc syntax
    puts <<TEXT
    This is created using heredoc syntax.
    It also spans multiple lines.
    TEXT
    String Replication:

    In Ruby, the * operator allows you to replicate strings multiple times.

    Example:

    # Replicating strings
    
    str = "Hello, Ruby!\n"
    
    # Replicating the string 5 times
    puts str * 5

    Output:

    Hello, Ruby!
    Hello, Ruby!
    Hello, Ruby!
    Hello, Ruby!
    Hello, Ruby!

    Ruby String Interpolation

    String interpolation in Ruby allows combining strings in a dynamic way without needing to concatenate them using the + operator. It works only with double quotes ("") and provides a straightforward way to include variables or expressions within strings. When using interpolation, Ruby evaluates the variables or expressions inside #{} and replaces them with their values in the string.

    Syntax:

    "#{variable}"

    In the syntax above, everything inside the curly braces {} is an executable expression or variable.

    Example 1:

    # Ruby program demonstrating string interpolation
    
    x = 10
    y = 25
    puts "The number #{x} is less than #{y}"

    Output:

    The number 10 is less than 25

    In this example, the values of x and y are substituted directly into the string at the locations where the interpolation occurs.

    Example 2:

    # Ruby program demonstrating string interpolation with variables
    
    name = 'Rocket'
    age = 5
    
    # Using interpolation
    puts "#{name} is #{age} years old"
    
    # Concatenation without interpolation
    puts name + " is " + age.to_s + " years old"

    Output:

    Rocket is 5 years old
    Rocket is 5 years old

    In this example:

    • The first puts statement uses string interpolation, automatically converting the variables into a string format.
    • The second puts statement uses the + operator, requiring age to be converted to a string using to_s to avoid a type error.

    Note: String interpolation is preferable because it’s cleaner, avoids explicit conversions, and can handle complex expressions inside the curly braces.

    How String Interpolation Works:

    When Ruby encounters #{} inside a string, it evaluates the expression or variable inside and inserts the resulting value into the string. This works for numbers, strings, or even more complex expressions.

    For example:

    # Ruby program demonstrating interpolation of an expression
    
    num = 20
    puts "In five years, the number will be #{num + 5}"

    Output:

    In five years, the number will be 25

    Ruby Hashes Basics

    hash in Ruby is a data structure that holds a collection of unique keys, each associated with a corresponding value. Hashes are also called associative arrays or maps because they map keys to values. Unlike arrays, which are indexed by integers, hashes can be indexed by objects (such as strings or symbols).

    Creating Hashes

    There are several ways to create a hash in Ruby:

    1. Using the new Method: The new method creates an empty hash with no default value unless specified.

    Syntax:

    hash_variable = Hash.new

    Example:

    my_hash = Hash.new

    This will create an empty hash called my_hash. You can also specify a default value for the hash, which will be returned when trying to access a key that does not exist.

    my_hash = Hash.new("default_value")

    Now, if a key is not found in my_hash, it will return "default_value".

    2. Using {} Braces: This is the most common way to create a hash, using curly braces {} to define key-value pairs.

    Syntax:

    hash_variable = { "key1" => value1, "key2" => value2 }

    Example:

    languages = { "Ruby" => 1, "Python" => 2 }

    This hash associates "Ruby" with 1 and "Python" with 2.

    Fetching Hash Values

    To retrieve a value from a hash, use square brackets [] and pass the key as an argument.

    Example:

    # Define a hash with some key-value pairs
    student_scores = {
      "Alice" => 85,
      "Bob" => 92,
      "Charlie" => 78
    }
    
    # Fetch values using their keys
    puts student_scores["Alice"]   # Output the score of Alice
    puts student_scores["Bob"]     # Output the score of Bob
    puts student_scores["Charlie"] # Output the score of Charlie

    Output:

    85
    92
    78

    2. Numbers

    Ruby supports different types of numbers, including integers and floating-point numbers. You can write numbers of any size, using underscores (_) for readability. Ruby allows various numerical formats, including decimal, hexadecimal, octal, and binary.

    Example:

    # Ruby program to demonstrate hash creation and fetching values
    
    my_hash = { "Apple" => 3, "Banana" => 5 }
    
    # Fetching values using keys
    puts my_hash["Apple"]    # Output: 3
    puts my_hash["Banana"]   # Output: 5
    Modifying Hashes in Ruby

    You can modify a hash by adding, removing, or changing key-value pairs. Modifying the value of an existing key is done by reassigning the value.

    Example:

    # Ruby program to demonstrate modifying a hash
    
    fruits = { "Apple" => 3, "Banana" => 5 }
    
    puts "Before Modification:"
    puts fruits["Apple"]    # Output: 3
    puts fruits["Banana"]   # Output: 5
    
    # Modifying the values
    fruits["Apple"] = 10
    fruits["Banana"] = 8
    
    puts "\nAfter Modification:"
    puts fruits["Apple"]    # Output: 10
    puts fruits["Banana"]   # Output: 8
    Overwriting Key Values

    If you assign multiple values to the same key in a hash, the last assignment overwrites the previous ones. This does not raise an error but may give a warning.

    Example:

    # Ruby program to demonstrate overwriting keys
    
    my_hash = { "Apple" => 3, "Apple" => 7, "Banana" => 5 }
    
    puts "Before Modifying:"
    puts my_hash["Apple"]    # Output: 7 (last assigned value)
    puts my_hash["Banana"]   # Output: 5
    
    # Modifying the hash
    my_hash["Apple"] = 12
    my_hash["Banana"] = 10
    
    puts "\nAfter Modifying:"
    puts my_hash["Apple"]    # Output: 12
    puts my_hash["Banana"]   # Output: 10

    Ruby Hash Class

    In Ruby, a Hash is a collection of unique keys and their associated values. Unlike arrays, hashes allow indexing with arbitrary object types as keys. Iterating through a hash may return key-value pairs in an arbitrary order, not necessarily in the insertion order. By default, hashes return nil when trying to access keys that do not exist.

    Class Methods

    1. []: Creates a new hash populated with the given key-value pairs.

    # Example:
    p Hash["a", 1, "b", 2]
    p Hash["a" => 1, "b" => 2]

    Output:

    {"a"=>1, "b"=>2}
    {"a"=>1, "b"=>2}

    2. new: Returns an empty hash. If a key that doesn’t exist is accessed, the return value depends on the form of new. By default, nil is returned, but a custom object or block can be specified for default values.

    # Example:
    h = Hash.new("default_value")
    p h["key1"] = 10
    p h["key2"] = 20
    p h["key3"]       # key3 doesn't exist, returns default

    Output:

    10
    20
    "default_value"

    3. try_convert: Converts an object into a hash, if possible, and returns the hash or nil if conversion fails.

    # Example:
    p Hash.try_convert({1 => 2})
    p Hash.try_convert("1 => 2")  # Not a hash

    Output:

    {1=>2}
    nil
    Instance Methods

    1. ==: Checks if two hashes are equal, meaning both contain the same keys and corresponding values.

    # Example:
    h1 = {"a" => 1, "b" => 2}
    h2 = {"b" => 2, "a" => 1}
    h3 = {"a" => 3, "b" => 2}
    p h1 == h2  # true
    p h1 == h3  # false

    2. [] (Element Reference): Retrieves the value associated with the specified key, or returns nil if the key is not found.

    # Example:
    h = {"a" => 10, "b" => 20}
    p h["a"]  # 10
    p h["c"]  # nil

    3. []= (Element Assignment): Assigns a value to a key in the hash.

    # Example:
    h = {"a" => 10, "b" => 20}
    h["a"] = 50
    h["c"] = 30
    p h  # {"a"=>50, "b"=>20, "c"=>30}

    4. clear: Removes all key-value pairs from the hash.

    # Example:
    h = {"a" => 10, "b" => 20}
    h.clear
    p h  # {}

    5. default: Returns the default value for missing keys.

    # Example:
    h = Hash.new("default_value")
    p h.default  # "default_value"

    6. delete: Deletes a key-value pair from the hash by key, returning the value or nil if the key is not found.

    # Example:
    h = {"a" => 10, "b" => 20}
    p h.delete("a")  # 10
    p h.delete("z")  # nil

    7. each: Iterates over each key-value pair in the hash.

    # Example:
    h = {"a" => 10, "b" => 20}
    h.each { |key, value| puts "#{key}: #{value}" }

    Output:

    a: 10
    b: 20

    8. has_key?: Returns true if the specified key is present in the hash, otherwise false.

    # Example:
    h = {"a" => 10, "b" => 20}
    p h.has_key?("a")  # true
    p h.has_key?("z")  # false

    9. invert: Swaps keys and values, returning a new hash.

    # Example:
    h = {"a" => 1, "b" => 2}
    p h.invert  # {1 => "a", 2 => "b"}

    10. merge: Combines two hashes, giving precedence to the second hash’s values for duplicate keys.

    # Example:
    h1 = {"a" => 1, "b" => 2}
    h2 = {"b" => 3, "c" => 4}
    p h1.merge(h2)  # {"a" => 1, "b" => 3, "c" => 4}

    11. reject: Returns a new hash with key-value pairs removed where the block evaluates to true.

    # Example:
    h = {"a" => 1, "b" => 2, "c" => 3}
    p h.reject { |k, v| v > 1 }  # {"a" => 1}
  • Ruby Classes

    Ruby Float Class

    In Ruby, the Float class is a subclass of the Numeric class. Objects of the Float class represent real numbers using the system’s native double-precision floating-point representation.

    Public Instance Methods

    Arithmetic Operations

    The Float class supports various arithmetic operations, including addition, subtraction, multiplication, division, modulo, and exponentiation.

    • Addition: Returns the sum of a float and a numeric value as a floating-point number.
    float + numeric
    • Subtraction: Returns the difference between a float and a numeric value as a floating-point number.
    float - numeric
    • Multiplication: Returns the product of a float and a numeric value as a floating-point number.
    float * numeric
    • Division: Returns the quotient of a float and a numeric value as a floating-point number.
    float / numeric
    • Modulo: Returns the remainder when a float is divided by a numeric value.
    float % numeric
    • Exponentiation: Raises the float to the power of a numeric value.
    float ** numeric
    • Unary Minus: Returns the negative of the float.
    -float

    Example:

    a = 5.5
    b = 2
    
    # Addition
    puts a + b  # Output: 7.5
    
    # Subtraction
    puts a - b  # Output: 3.5
    
    # Multiplication
    puts a * b  # Output: 11.0
    
    # Division
    puts a / b  # Output: 2.75
    
    # Modulo
    puts a % b  # Output: 1.5
    
    # Exponentiation
    puts a ** b  # Output: 30.25
    
    # Unary Minus
    puts -a  # Output: -5.5
    Comparison Operators
    • Spaceship operator (<=>): Returns -1 if the float is less than the numeric value, 0 if they are equal, and 1 if the float is greater.
    float <=> numeric

    Example:

    puts 3.2 <=> 5   # Output: -1
    puts 5.0 <=> 5   # Output: 0
    puts 7.4 <=> 5   # Output: 1
    • Equality (==): Returns true if the float is equal to another object.
    float == obj

    Example:

    p 45.5.divmod(6)  # Output: [7, 3.5]
    Other Useful Methods
    • abs: Returns the absolute value of the float.
    float.abs
    • eql?: Returns true if the float is equal to another object, with the same value and type.
    float.eql?(obj)
    • ceil: Returns the smallest integer greater than or equal to the float.
    float.ceil
    • divmod: Returns an array containing the quotient and remainder when divided by a numeric value.
    float.divmod(numeric)

    Example:

    p 45.5.divmod(6)  # Output: [7, 3.5]
    • eql?: Returns true if the float is equal to another object, with the same value and type.
    float.eql?(obj)

    Example:

    puts 5.5.eql?(5.5)  # Output: true

    Output:

    Title: Ruby Programming
    Author: John Doe
    Title: Learning Ruby
    Author: Jane Doe
    • finite?: Returns true if the float is a valid IEEE floating-point number.
    float.finite?

    Example:

    puts 10.0.finite?  # Output: true
    • floor: Returns the largest integer less than or equal to the float.
    float.floor

    Output:

    Global count in FirstClass: 5
    Global count in SecondClass: 5
    • floor: Returns the largest integer less than or equal to the float.
    float.floor
    • infinite?: Returns nil-1, or 1 depending on whether the float is finite, negative infinity, or positive infinity.
    float.infinite?

    Example:

    puts (1.0/0.0).infinite?  # Output: 1
    • round: Rounds the float to the nearest integer or to the specified number of decimal places.
    float.round(digits=0)

    Example:

    puts 5.67.round   # Output: 6
    puts 5.67.round(1) # Output: 5.7
    • to_f: Returns the float itself.
    float.to_f
    • to_i: Truncates the float to return its integer value.
    float.to_i
    • zero?: Returns true if the float is 0.0.
    float.zero?

    Example:

    puts 0.0.zero?  # Output: true
    Constants in the Float Class
    • EPSILON: Smallest floating-point number greater than 1 (2.2204460492503131e-16).
    • MANT_DIG: Number of mantissa digits (53 by default).
    • MAX: Largest double-precision floating-point number (1.7976931348623157e+308).
    • MIN: Smallest positive normalized number (2.2250738585072014e-308).
    • \INFINITY: Represents positive infinity.
    • NAN: Represents “Not a Number.”

    Ruby Integer Class

    The Integer class in Ruby is the foundation for two subclasses, Bignum and Fixnum, which store whole numbers. Fixnum holds integer values within the machine’s native word size, while Bignum handles values outside the range of Fixnum. The Integer class itself inherits from the Numeric class and offers a variety of methods for performing operations on integers.

    Methods in the Integer Class

    1. to_i: This method returns the integer value. Its synonym is to_int.

    int.to_i

    2. chr: This method returns the ASCII character that corresponds to the integer’s value as a string.

    int.chr

    Example:

    puts 97.chr  # Output: "a"
    puts 66.chr  # Output: "B"

    3. downto: This method passes decreasing integer values from the receiver down to (and including) the argument, yielding each value to the block.

    int.downto(integer) {|i| block}

    Example:

    5.downto(1) { |i| print "#{i} " }
    # Output: 5 4 3 2 1

    4. floor: This method returns the largest integer less than or equal to the receiver. It behaves similarly to to_i.

    int.floor

    Example:

    puts 3.floor  # Output: 3
    puts (-3.4).floor  # Output: -4

    5. integer?: This method checks if the object is an integer, returning true if it is and false otherwise.

    int.integer?

    Example:

    puts 5.integer?  # Output: true
    puts 3.7.integer?  # Output: false

    6. next and succ: These methods return the next integer, which is the current integer plus one. Both methods are synonymous.

    int.next
    int.succ

    Example:

    puts 7.next  # Output: 8
    puts (-3).succ  # Output: -2

    7. times: This method executes the block a specified number of times, passing values from 0 to int - 1.

    int.times { |i| block }

    8. upto: This method iterates over integers from the receiver up to (and including) the specified value, yielding each number to the block.

    4.times { |i| print "#{i} " }
    # Output: 0 1 2 3

    Examples:

    2.upto(5) { |i| print "#{i} " }
    # Output: 2 3 4 5

    9. round: This method rounds the integer or float to the nearest integer. If no argument is provided, it rounds to zero decimal places.

    puts 10.round  # Output: 10
    puts (15.67).round  # Output: 16
    Additional Methods
    • to_int: Same as to_i, returns the integer value.
    • truncate: Similar to floor, it truncates any decimal part and returns an integer.
    • zero?: Returns true if the integer is zero, otherwise false.
    • odd?: Returns true if the integer is odd, otherwise false.
    • even?: Returns true if the integer is even, otherwise false.

    Ruby Symbol Class

    The Struct class in Ruby provides a concise way to bundle multiple attributes together, using accessor methods, without the need to define an explicit class. Each structure creates a new class with accessor methods for a predefined set of variables. A subclass of Struct is Struct::Tms.

    Example:

    # Ruby program demonstrating Symbol objects
    
    # A symbol representing a class
    module ModuleA
      class MyClass
      end
      $sym1 = :MyClass
    end
    
    # A symbol representing a constant
    module ModuleB
      MyConstant = 1
      $sym2 = :MyClass
    end
    
    # A symbol representing a method
    def MyClassMethod
    end
    $sym3 = :MyClass
    
    puts $sym1.object_id
    puts $sym2.object_id
    puts $sym3.object_id

    Output:

    1675428
    1675428
    1675428

    In this example, the symbol :MyClass refers to the same object, regardless of whether it’s used as a class name, constant, or method.

    Class Method

    • all_symbols: Returns an array of all symbols currently available in Ruby’s symbol table.
    Symbol.all_symbols

    Example:

    # Ruby program demonstrating the all_symbols method
    puts Symbol.all_symbols.size
    puts Symbol.all_symbols[1, 10]

    Output:

    3250
    [:a_symbol, :another_symbol, ...]  # An example of some symbols
    Instance Methods
    • id2name: Returns the string representation of a symbol.
    sym.id2name

    Example:

    # Ruby program demonstrating the id2name method
    p :Ruby.id2name
    p :"Hello World".id2name

    Output:

    "Ruby"
    "Hello World"
    • inspect: Returns a string representation of the symbol, prefixed with a colon.
    sym.inspect

    Example:

    # Ruby program demonstrating the inspect method
    p :ruby.inspect
    p :"sample text".inspect

    Output:

    ":ruby"
    ":\"sample text\""
    • to_s: Converts the symbol to its string equivalent.
    sym.to_s

    Example:

    # Ruby program demonstrating the to_s method
    p :language.to_s
    p :"hello world".to_s

    Output:

    "language"
    "hello world"
    • <=>: Compares two symbols after converting them to strings. Returns -1 if the first symbol is less, 0 if they are equal, and 1 if it’s greater.
    sym <=> other_sym
    • ==: Returns true if two symbols are the same object.
    # Ruby program demonstrating the <=> method
    a = :ruby
    b = :"programming language"
    puts a <=> b  # Output: -1
    puts a <=> :ruby  # Output: 0
    puts b <=> a  # Output: 1
    • ==: Returns true if two symbols are the same object.
    # Ruby program demonstrating the == method
    a = :ruby
    b = :"programming language"
    puts a == b  # Output: false
    puts a == :ruby  # Output: true

    Example:

    # Ruby program demonstrating the == method
    a = :ruby
    b = :"programming language"
    puts a == b  # Output: false
    puts a == :ruby  # Output: true
    • downcase: Converts all uppercase letters in the symbol to lowercase.
    sym.downcase

    Example:

    # Ruby program demonstrating the downcase method
    puts :"RUBY LANGUAGE".downcase
    • The entries method lists all files and folders in a directory.

    Syntax:

    Dir.entries("directory")

    Output:

    :"ruby language"
    • length: Returns the number of characters in the symbol.
    sym.length

    Example:

    # Ruby program demonstrating the length method
    puts :RubySymbol.length

    Output:

    10
    • slice: Returns a substring or character at a given index from the symbol.
    sym.slice(index)
    sym.slice(start, length)

    Example:

    # Ruby program demonstrating the slice method
    p :Programming.slice(2)  # Output: "o"
    p :Programming.slice(0, 6)  # Output: "Progra"
    • swapcase: Swaps the case of the symbol’s characters, converting uppercase to lowercase and vice versa.
    swapcase: Swaps the case of the symbol’s characters, converting uppercase to lowercase and vice versa.

    Example:

    # Ruby program demonstrating the swapcase method
    p :"RubyLanguage".swapcase

    Output:

    :"rUBYlANGUAGE"
    • upcase: Converts all lowercase letters in the symbol to uppercase.
    sym.upcase

    Example:

    # Ruby program demonstrating the upcase method
    p :"ruby language".upcase

    Output:

    :"RUBY LANGUAGE"
    • to_proc: Converts the symbol into a Proc object that invokes the method represented by the symbol.
    sym.to_proc

    Example:

    # Example using an array of strings
    words = ["apple", "banana", "cherry"]
    
    # Using &:symbol to convert symbol to Proc
    capitalized_words = words.map(&:capitalize)
    
    puts capitalized_words

    Output:

    Apple
    Banana
    Cherry
    • to_sym: Returns the symbol itself (as it is already a symbol).
    sym.to_sym

    Example:

    # Example: String to Symbol
    str = "hello"
    symbol = str.to_sym
    puts symbol  # Output: :hello
    puts symbol.class  # Output: Symbol

    Output:

    :world
    Symbol

    Ruby Struct Class

    The Struct class in Ruby provides a concise way to bundle multiple attributes together, using accessor methods, without the need to define an explicit class. Each structure creates a new class with accessor methods for a predefined set of variables. A subclass of Struct is Struct::Tms.

    Example:

    # Ruby program demonstrating the use of Struct
    
    # Creating a Struct with custom behavior
    Course = Struct.new(:name, :category) do
      def details
        "This is a #{category} course on #{name}."
      end
    end
    
    # Creating an instance of the struct
    course = Course.new("Ruby", "Programming")
    puts course.details

    Output:

    This is a Programming course on Ruby.

    Class Method

    • new: This method creates a new class with accessor methods for the provided symbols. If the name string is omitted, an anonymous structure class is created. If a name is provided, it appears as a constant in the Struct class and must be unique, starting with a capital letter.
    Struct.new([name], symbol1, symbol2)
    Struct.new([name], symbol1, symbol2){block}

    Example:

    # Ruby program demonstrating Struct creation
    
    # Creating a structure with a name in Struct
    Struct.new("Course", :subject, :type)
    Struct::Course.new("Ruby", "Programming")
    
    # Creating a structure using a constant name
    Course = Struct.new(:subject, :type)
    p Course.new("Ruby", "Programming")

    Output:

    #<struct Course subject="Ruby", type="Programming">
    Instance Methods
    • ==: Checks for equality between two instances of the same Struct class, comparing the values of their instance variables.
    instance1 == instance2

    Example:

    • []=: Assigns a new value to an instance variable using a symbol or index.
    # Ruby program demonstrating equality in Structs
    
    Course = Struct.new(:subject, :type)
    course1 = Course.new("Ruby", "Programming")
    course2 = Course.new("Python", "Data Science")
    course3 = Course.new("Ruby", "Programming")
    
    puts course1 == course2  # Output: false
    puts course1 == course3  # Output: true
    • each: Iterates over each instance variable’s value, passing it to the given block.
    instance.each { |value| block }

    Example:

    # Ruby program demonstrating each method
    
    Course = Struct.new(:subject, :type)
    course = Course.new("Ruby", "Programming")
    
    course.each { |value| puts value }
    • each: Iterates over each instance variable’s value, passing it to the given block.
    # Ruby program demonstrating each method
    
    Course = Struct.new(:subject, :type)
    course = Course.new("Ruby", "Programming")
    
    course.each { |value| puts value }

    Output:

    Ruby
    Programming
    • each_pair: Iterates over each instance variable, passing both the name and the value to the given block.
    • each_pair: Iterates over each instance variable, passing both the name and the value to the given block.
    instance.each_pair { |name, value| block }

    Example:

    # Ruby program demonstrating each_pair method
    
    Course = Struct.new(:subject, :type)
    course = Course.new("Ruby", "Programming")
    
    course.each_pair { |name, value| puts "#{name} => #{value}" }

    Output:

    # Ruby program demonstrating attribute assignment
    
    Course = Struct.new(:subject, :type)
    course = Course.new("Ruby", "Programming")
    
    course[:subject] = "JavaScript"
    course[1] = "Web Development"
    
    puts course.subject  # Output: JavaScript
    puts course.type     # Output: Web Development
  • Ruby Regex

    Ruby Regular Expressions

    A regular expression, or regex, is a sequence of characters that forms a search pattern. It’s mainly used for pattern matching within strings. In Ruby, regular expressions (regex) allow us to find specific patterns within a string. Two common uses of Ruby regex are validation (such as checking email addresses) and parsing text. Regular expressions in Ruby are enclosed between two forward slashes (/).

    Syntax:

    # Finding the word 'hi'
    "Hi there, I am using Ruby" =~ /hi/

    This will return the index of the first occurrence of ‘hi’ in the string if found, otherwise, it will return nil.

    Checking if a String Contains a Pattern

    You can check if a string contains a regex pattern using the match method.

    Example:

    Match found

    Checking for Specific Characters in a String

    A character class lets you define a range of characters for matching. For instance, you can use [aeiou] to search for any vowel.

    Example:

    # Ruby program using regular expressions
    
    # Function to check if the string contains a vowel
    def contains_vowel(str)
      str =~ /[aeiou]/
    end
    
    # Driver code
    puts contains_vowel("Hello")  # 'Hello' has a vowel, so it returns index
    puts contains_vowel("bcd")    # 'bcd' has no vowels, returns nil, so nothing is printed

    Output:

    1
    Common Regular Expressions

    Here are some shorthand character classes for specifying ranges:

    • \w is equivalent to [0-9a-zA-Z_]
    • \d is the same as [0-9]
    • \s matches any whitespace
    • \W matches anything not in [0-9a-zA-Z_]
    • \D matches anything that’s not a number
    • \S matches anything that’s not a whitespace

    The dot character . matches any character except a newline. If you want to search for the literal . character, you need to escape it with a backslash (\.).

    Example:

    # Ruby program using regular expressions
    
    str1 = "2m3"
    str2 = "2.5"
    
    # . matches any character
    if str1.match(/\d.\d/)
      puts "Match found"
    else
      puts "Not found"
    end
    
    # Escaping the dot to match only a literal '.'
    if str1.match(/\d\.\d/)
      puts "Match found"
    else
      puts "Not found"
    end
    
    # This will match because str2 contains a dot between digits
    if str2.match(/\d\.\d/)
      puts "Match found"
    else
      puts "Not found"
    end

    Output:

    Match found
    Not found
    Match found

    Explanation:

    1. First condition: Checks if str1 contains a digit followed by any character and then another digit (\d.\d). Since str1 contains “2m3,” it matches.
    2. Second condition: Escapes the dot (\.) to match a literal .. Since str1 doesn’t contain a literal dot, it does not match.
    3. Third condition: Matches because str2 (“2.5”) contains a digit, followed by a literal dot, and then another digit.

    In summary, Ruby regular expressions provide a powerful way to search and match patterns within strings, allowing for complex validations and parsing tasks.

    Ruby Search and Replace

    sub and gsub are Ruby string methods that utilize regular expressions for searching and replacing content in a string. Their in-place variants, sub! and gsub!, modify the original string directly.

    • sub and sub!: Replace only the first occurrence of the pattern.
    • gsub and gsub!: Replace all occurrences of the pattern.

    The difference between sub/gsub and their in-place variants (sub!/gsub!) is that the in-place methods (sub! and gsub!) modify the string on which they are called, while sub and gsub return a new string, leaving the original one unchanged.

    Example:

    # Ruby program demonstrating sub and gsub methods in a string
    
    number = "1234-567-890 # This is a number"
    
    # Remove the comment section using sub (replaces the first occurrence)
    number = number.sub!(/#.*$/, "")
    puts "Number: #{number}"
    
    # Remove all non-digit characters using gsub (replaces all occurrences)
    number = number.gsub!(/\D/, "")
    puts "Number: #{number}"

    Output:

    Number: 1234-567-890
    Number: 1234567890
  • Exceptions

    Ruby Exceptions

    In programming, predicting errors and handling them effectively is crucial. Exceptions are errors that occur during runtime and disrupt the normal flow of a program. Handling exceptions properly ensures a program continues to operate even in unexpected situations.

    What is an Exception?

    An exception is an unexpected event that occurs during the execution of a program, disrupting its normal flow. It provides a way to handle error scenarios without stopping the entire program.

    Errors vs. Exceptions

    Errors:

    • Unexpected issues that arise during program execution.
    • Cannot be handled directly.
    • All errors are considered exceptions.

    Exceptions:

    • Unexpected events during runtime.
    • Can be managed using begin-rescue blocks.
    • Not all exceptions are errors.
    Traditional Exception Handling Approach

    In the traditional approach, errors were managed using return codes. Methods would return specific values indicating failure, which would be propagated through calling routines until a function took responsibility. This made error management complex and hard to maintain.

    Ruby addresses this issue by using an exception class that packages information about errors into an object. This object is passed through the calling stack until appropriate code is found to handle the error.

    Exception Class & Its Hierarchy

    Ruby has a built-in hierarchy of exception classes. Most exceptions are subclasses of StandardError, which represents general errors in Ruby programs. More serious exceptions fall under other classes. Users can also create their own exceptions by subclassing StandardError or its descendants. Every exception object contains a message and a stack trace.

    Example of an Exception

    # Ruby program to illustrate an exception
    
    # defining two integer values
    num1 = 14
    num2 = 0
    
    # attempting to divide by zero
    result = num1 / num2
    
    puts "The result is: #{result}"

    Runtime Error:

    source_file.rb:6:in `/': divided by 0 (ZeroDivisionError)
    from source_file.rb:6:in `<main>'

    Explanation

    In the example above, dividing 14 by 0 triggers a ZeroDivisionError.

    Creating User-Defined Exceptions

    Ruby uses the raise method to create exceptions, which become instances of the Exception class or its subclasses. The rescue clause is used to handle these exceptions.

    Example of User-Defined Exception

    # Ruby program to create a user-defined exception
    
    # defining a method
    def raise_exception
      puts 'This is before the exception is raised!'
    
      # using raise to create an exception
      raise 'Custom Exception Raised'
    
      puts 'This line will not be executed.'
    end
    
    # calling the method
    raise_exception

    Output:

    This is before the exception is raised!
    source_file.rb:6:in `raise_exception': Custom Exception Raised (RuntimeError)
        from source_file.rb:10:in `<main>'

    Handling Exceptions with rescue

    You can handle exceptions in Ruby using begin-rescue blocks. This structure allows the program to continue running after handling an exception.

    Example of Handling Exception

    # Ruby program to create and handle a user-defined exception
    
    # defining a method
    def raise_and_rescue
      begin
        puts 'This is before the exception is raised!'
    
        # using raise to create an exception
        raise 'Custom Exception Raised!'
    
        puts 'This line will not be executed.'
    
      # using rescue to handle the exception
      rescue
        puts 'Exception handled!'
      end
    
      puts 'Outside of the begin block!'
    end
    
    # calling the method
    raise_and_rescue

    Output:

    This is before the exception is raised!
    Exception handled!
    Outside of the begin block!

    Ruby Exception Handling

    Exception handling in Ruby provides a way to manage unexpected events or errors that occur during the execution of a program. These errors can disrupt the normal flow of a program and are managed using various statements like beginrescueraiseensure, and more. Ruby also provides an Exception class, which has different methods to handle these errors.

    Syntax of Exception Handling in Ruby

    The code block where an exception might be raised is enclosed in a begin and end block. The rescue clause is used to manage the exception.

    Basic Syntax:

    begin
      # Code where an exception might be raised
    rescue
      # Code to handle the exception
    end

    Example of Exception Handling

    # Ruby program to handle an exception
    
    # defining a method
    def raise_and_rescue
      begin
        puts 'This is before an exception occurs!'
    
        # raising an exception
        raise 'An Exception Occurred!'
    
        puts 'This will not be printed'
      rescue
        puts 'Exception handled successfully!'
      end
    
      puts 'Outside the begin block!'
    end
    
    # calling the method
    raise_and_rescue

    Output:

    This is before an exception occurs!
    Exception handled successfully!
    Outside the begin block!

    Explanation: In this example, an exception is raised in the begin block using raise, which interrupts the program’s flow. The rescue block then catches and handles the exception, allowing the program to continue executing.

    Note

    • You can use multiple rescue clauses to handle different exceptions. If an exception is not handled by the first rescue, the next one is tried. If no rescue matches, or if an exception occurs outside the begin block, Ruby searches up the call stack for an exception handler.
    Statements Used in Exception Handling

    1. retry Statement: The retry statement re-executes the code from the beginning of the begin block after catching an exception.

    Syntax:

    begin
      # Code where an exception might be raised
    rescue
      # Code to handle the exception
      retry
    end

    Note: Be cautious while using retry as it can lead to an infinite loop if not properly managed.

    2. raise Statement: The raise statement is used to raise an exception in Ruby.

    Syntax:

    • raise: Re-raises the current exception.
    • raise "Error Message": Creates a RuntimeError with the given message.
    • raise ExceptionType, "Error Message": Creates an exception of the specified type with the given message.

    Example:

    # Ruby program demonstrating the use of raise statement
    
    begin
      puts 'This is before an exception occurs!'
    
      # Raising an exception
      raise 'An Exception Occurred!'
    
      puts 'This will not be printed'
    end

    Output:

    This is before an exception occurs!
    An Exception Occurred!

    3. ensure Statement: The ensure block always executes regardless of whether an exception is raised or rescued. It is placed after the rescue block.

    Syntax:

    begin
      # Code where an exception might be raised
    rescue
      # Code to handle the exception
    ensure
      # Code that always executes
    end

    Output:

    # Ruby program demonstrating the use of the ensure statement
    
    begin
      # Raising an exception
      raise 'An Exception Occurred!'
      puts 'This will not be printed'
    rescue
      puts 'Exception handled!'
    ensure
      puts 'Ensure block executed'
    end

    Output:

    Exception handled!
    Ensure block executed

    4. else Statement: The else block executes only if no exception is raised in the begin block. It is placed between the rescue and ensure blocks.

    Syntax:

    begin
      # Code where an exception might be raised
    rescue
      # Code to handle the exception
    else
      # Code that executes if no exception is raised
    ensure
      # Code that always executes
    end

    Example:

    # Ruby program demonstrating the use of the else statement
    
    begin
      puts 'No exception is raised here'
    rescue
      puts 'Exception handled!'
    else
      puts 'Else block executed because no exception was raised'
    ensure
      puts 'Ensure block executed'
    end

    Output:

    No exception is raised here
    Else block executed because no exception was raised
    Ensure block executed

    5. catch and throw Statements:catch and throw provide a lightweight mechanism for managing errors and jumps in Ruby. The catch block works normally until it encounters a throw. When a throw is encountered, Ruby looks for the matching catch block with the same label.

    Syntax:

    catch :label_name do
      # Code block
      throw :label_name if condition
    end

    Example:

    # Ruby program to illustrate Class Variables
    
    class Library
      # Class variable
      @@book_count = 0
    
      def initialize(title)
        # Instance variable
        @title = title
      end
    
      def display_title
        puts "Book Title: #{@title}"
      end
    
      def add_book
        # Increment the class variable
        @@book_count += 1
        puts "Total books: #@@book_count"
      end
    end
    
    # Creating objects
    book1 = Library.new("The Art of Ruby")
    book2 = Library.new("Ruby on Rails Guide")
    
    # Calling methods
    book1.display_title
    book1.add_book
    
    book2.display_title
    book2.add_book

    Input:

    Enter a number: 1

    Output:

    1

    Input:

    Enter a number: !

    Output:

    nil

    Catch and Throw Exception In Ruby

    In Ruby, an exception is an object that belongs to the Exception class or one of its subclasses. An exception occurs when a program reaches an undefined or unexpected state. At this point, Ruby doesn’t know how to proceed, so it raises an exception. This can happen automatically or be done manually by the programmer.

    The catch and throw keywords in Ruby provide a way to handle exceptions, similar to how raise and rescue work. The throw keyword generates an exception, and when it is encountered, the program’s control flow jumps to the corresponding catch statement.

    Syntax

    catch :label_name do
      # Code block that runs until a throw is encountered
      throw :label_name if condition
      # This block will not execute if throw is encountered
    end

    The catch block is used to jump out of nested code, and it will continue executing normally until a throw statement is encountered.

    Example: Basic catch and throw

    # Ruby Program using Catch and Throw for Exception Handling
    result = catch(:divide) do
      # Code block similar to 'begin'
      number = rand(2)
      throw :divide if number == 0
      number # set result = number if no exception is thrown
    end
    
    puts result

    Output: If the random number is 0, the output will be empty since the throw :divide statement will execute and jump to the catch block. If the random number is 1, the output will be:

    1

    Explanation: If the random number is 0throw :divide is executed, and nothing is returned to the catch block, resulting in an empty output. If the number is 1, the exception is not thrown, and the result variable is set to 1.

    Example:

    In the previous example, when the exception was thrown, the variable result was set to nil. We can change this behavior by providing a default value to the throw keyword.

    # Ruby Program using Catch and Throw with a Default Value
    result = catch(:divide) do
      # Code block similar to 'begin'
      number = rand(2)
      throw :divide, 10 if number == 0
      number # set result = number if no exception is thrown
    end
    
    puts result

    Output:

    1

    Exception Handling in Ruby

    In Ruby, a literal is any constant value that can be assigned to a variable. We use literals whenever we type an object directly into the code. Ruby literals are similar to those in other programming languages, with some differences in syntax and functionality.

    Types of Ruby Literals

    Ruby supports various types of literals:

    1. Booleans and nil
    2. Numbers
    3. Strings
    4. Symbols
    5. Ranges
    6. Arrays
    7. Hashes
    8. Regular Expressions

    1. Booleans and nil: Booleans are constants that represent the truth values true and falsenil is another constant that represents an “unknown” or “empty” value, and it behaves similarly to false in conditional expressions.

    Example:

    raise exception_type, "exception message" if condition

    Output:

    true
    false
    false

    When a raise statement is executed, it invokes the rescue block. By default, raise raises a RuntimeError.

    Example 1: Using raise Statement

    # Ruby program to demonstrate the use of raise statement
    begin
      puts 'This is Before Exception Arises!'
    
      # Using raise to create an exception
      raise 'Exception Created!'
    
      puts 'After Exception'
    end

    Output:

    This is Before Exception Arises!
    Exception Created!

    Ruby Exception Handling in Threads

    Threads in Ruby can also encounter exceptions. By default, only exceptions that arise in the main thread are handled, while exceptions in other threads cause those threads to terminate. The behavior of threads when an exception arises is controlled by the abort_on_exception method, which is set to false by default.

    If abort_on_exception is false, an unhandled exception will abort the current thread but allow other threads to continue running. The setting can be changed by using Thread.abort_on_exception = true or by setting $DEBUG to true. Additionally, Ruby provides the ::handle_interrupt method for handling exceptions asynchronously within threads.

    Example 1: Exception in Threads

    # Ruby program to demonstrate exception handling in threads
    
    #!/usr/bin/ruby
    
    threads = []
    4.times do |value|
      threads << Thread.new(value) do |i|
        # Raise an error when i equals 2
        raise "An error occurred!" if i == 2
        print "#{i}\n"
      end
    end
    
    threads.each { |t| t.join }

    Output:

    0
    3
    1
    main.rb:11:in `block (2 levels) in <main>': An error occurred! (RuntimeError)

    Note: The Thread.join method waits for a specific thread to finish. When a Ruby program ends, all threads are terminated, regardless of their states.

    Saving Exceptions in Threads

    In the following example, we handle exceptions within threads to prevent program termination and continue processing other threads.

    Example 2: Handling Exceptions in Threads

    # Ruby program to handle exceptions in threads
    
    #!/usr/bin/ruby
    
    threads = []
    
    5.times do |value|
      threads << Thread.new(value) do |i|
        raise "An error occurred!" if i == 3
        print "#{i}\n"
      end
    end
    
    threads.each do |t|
      begin
        t.join
      rescue RuntimeError => e
        puts "Exception caught: #{e.message}"
      end
    end

    Output:

    0
    1
    4
    2
    Exception caught: An error occurred!
    Using abort_on_exception

    Setting abort_on_exception to true will terminate the entire program as soon as an exception occurs in any thread. Once the thread with the exception is terminated, no further output will be generated.

    Example 3: Using abort_on_exception

    # Ruby program to demonstrate thread termination on exception
    
    #!/usr/bin/ruby
    
    # Set abort_on_exception to true
    Thread.abort_on_exception = true
    
    threads = []
    
    5.times do |value|
      threads << Thread.new(value) do |i|
        raise "An error occurred!" if i == 3
        print "#{i}\n"
      end
    end
    
    # Use Thread.join to wait for threads to finish
    threads.each { |t| t.join }

    Output:

    0
    1
    2
    main.rb:12:in `block (2 levels) in <main>': An error occurred! (RuntimeError)