Pitch: Add a function String to mimic BASIC's Mid$()

BASIC has 3 string functions to return a substring: Left$, Mid$ and Right$. Swift has Prefix() and Suffix() which are equivalent to Left$ and Right$ . String is missing an equivalent function to Mid$.

Example:

Extension String {

func infix(from position: Int, maxLength: Int = Int.max) -> Substring {

    let start = index(startIndex, offsetBy: numericCast(position), limitedBy: endIndex) ?? endIndex

    let end = index(start, offsetBy: numericCast(maxLength), limitedBy: endIndex) ?? endIndex

      return self[start..<end]

}

}

Confidentiality Notice: This email, including attachments, may include non-public, proprietary, confidential or legally privileged information. If you are not an intended recipient or an authorized agent of an intended recipient, you are hereby notified that
any dissemination, distribution or copying of the information contained in or transmitted with this email is unauthorized and strictly prohibited. If you have received this email in error, please notify the sender by replying to this message and permanently
delete this email, its attachments, and any copies of it immediately. You should not retain, copy or use this email or any attachment for any purpose, or disclose all or any part of the contents to any other person.

s.dropFirst(position).prefix(maxLen)

Can we please keep the forum free of this corporate spam?

2 Likes

Sorry for the corporate message I didn’t know my company added that to my email.

There are many ways to get the Mid string but there should be a single method where it can be optimised as well. One of Swifts goals is to make it easy for new developers to find what they need. Every developer I know would look ‘Mid’ or some method that’s spelled the same as Prefix and Suffix i.e. the ‘fix’ names.

Besides Mid do you have a name suggestion? These days the preferred way is using slices (index range subscripts). They reduce API surface area and support both extracting, removing and replacing a substring with a single consistent method.

In some situations, a function might be more readable. I think the only chance this pitch has of gaining acceptance is providing a compelling function name and example use case that show a clear readability advantage over range indexing.

I would keep the method name in the same vain as the two other String APIs: String.prefix() and String.suffix(). Therefor I would use String.infix() which means “a formative element inserted in a word”.

Current String APIs
    func prefix(_ maxLength: Int) -> Substring
func prefix(while predicate: (Character) throws -> Bool) rethrows -> Substring

func suffix(_ maxLength: Int) -> Substring

I propose adding the following APIs to String

func infix(from position: Int, maxLength: Int = Int.max) -> Substring

func infix(from position: Int, while predicate: (Element) throws -> Bool) rethrows -> Substring

Example implementation

/// Companion function to String.prefix() and String.suffix(). It is similar to

/// Basic's Mid() fuction.

///

/// Returns a subsequence, starting from position up to the specified

/// maximum length, containing the middle elements of the collection.

///

/// If the maximum length exceeds the remaing number of elements in the

/// collection, the result contains all the remaining elements in the collection.

///

///     let numbers = [1, 2, 3, 4, 5]

///     print(numbers.infix(from: 2, maxLength: 2))

///     // Prints "[3, 4]"

///     print(numbers.prefix(from: 2, maxLength: 10))

///     // Prints "[3, 4, 5]"

///     print(numbers.prefix(from: 10, maxLength: 2))

///     // Prints ""

///     print(numbers.infix(from: 0))

///     // Prints "[1, 2, 3, 4, 5]"

///     print(numbers.infix(from: 2))

///     // Prints "[3, 4, 5]"

///     print(numbers.infix(from: 10))

///     // Prints ""

///

/// - Parameters:

///   - position: The starting index in the collection. `position` must be

///     greater than or equal to zero.

///   - maxLength: The maximum number of elements to return. `maxLength`

///     must be greater than or equal to zero. The default for `maxLength`

///     is set so the remaining elements of the collection will be returned.

/// - Returns: A subsequence starting from `position` up to `maxLength`

///   elements in the collection.

func infix(from position: Int, maxLength: Int = Int.max) -> Substring {

    // if 'position' is beyond the end then set 'start' to 'endIndex'

    let start = index(startIndex, offsetBy: numericCast(position), limitedBy: endIndex) ?? endIndex

    // if 'start' + 'maxLength' is beyond the end then set end to 'endIndex'

    let end = index(start, offsetBy: numericCast(maxLength), limitedBy: endIndex) ?? endIndex

    return self[start..<end]

}

/// Returns a subsequence, starting from position and containing the elements

/// until `predicate` returns `false` and skipping the remaining elements.

///

/// - Parameters:

///   - position: The starting index in the collection. `position` must be

///     greater than or equal to zero.

///   - predicate: A closure that takes an element of the sequence as its

///     argument and returns `true` if the element should be included or

///     `false` if it should be excluded. Once the predicate

///   returns `false` it will not be called again.

///

/// - Complexity: O(*n*), where *n* is the length of the collection.

func infix(from position: Int, while predicate: (Element) throws -> Bool) rethrows -> Substring {

    // if 'position' is beyond the end then set 'start' to 'endIndex'

    let start = index(startIndex, offsetBy: numericCast(position), limitedBy: endIndex) ?? endIndex

    var end = start

    while try end != endIndex && predicate(self[end]) {

        formIndex(after: &end)

    }

    return self[start..<end]

}

Well these should be:

func infix(from index: (Sub)String.Index, maxLength: Int = Int.max) -> Substring

func infix(from index: (Sub)String.Index, while predicate: (Element) throws -> Bool) rethrows -> Substring

Not exactly the same, but a very similar proposal

1 Like