How to I get the first or last few characters of a string in a method chain?
Asked Answered
D

1

0

If I use functional-style method chains for string manipulation, I can not use the usual machinery for getting the first or last few characters: I do not have access to a reference to the current string, so I can not compute indices.

Example:

[some, nasty, objects]
    .map( { $0.asHex } )
    .joined()
    .<first 100>
    .uppercased()
    + "..."

for a truncated debug output.

So how to I implement <first 100>, or do I have to break the chain?

Dillingham answered 7/4, 2017 at 12:16 Comment(10)
Why can't you use the "usual machinery" in a map here?Coonskin
@RobNapier Map on which value? (Let's not get hung up with this toy example, if that's the issue.) Maybe I'm overthinking things; in that case I'd appreciate an answer.Dillingham
If SE-0163 gets accepted, then String will (once again) conform to Collection, meaning that you'll be able to just get the prefix(_:) :)Sleazy
@Sleazy Nice, thanks! That said, the Collection API confuses me deeply. It almost never seems to work as I want it to (subcollections have different types; the whole business with indices and ranges is cumbersome). Maybe I should do some reading ... can you recommend a resource? The code documentation is definitely no help. :/Dillingham
@Dillingham I do agree the Collection API isn't as sleek as it could be (really it's just missing a few convenience methods for common indexing operations, e.g index(atOffset:), which would just offset the startIndex by a given offset – but these can always be added as extensions). Usually, I don't find sub-collections to be cumbersome – they generally implement the same interface as the parent collection, and can always be easily converted back with an initialiser call (although that being said, they're annoying to work with in extensions due to....Sleazy
the lack of associatedtype where clauses, so the compiler doesn't know various things about them, e.g they should be Collections themselves, with elements and indices of the same type as parent collection – but thankfully this will all change soon with SE-0157 & SE-0142). Although, in general, I don't think the Collection API is too bad – it's fundamentally a simple concept, just a..Sleazy
collection of elements that are accessible by an index, and can be non-destructively iterated over. The main difficulty only arises because it has such a broad range of instance members (and lots of 'subprotocols', e.g RangeReplaceableCollection). I'm afraid I can't recommend any resources for you though, I actually don't find the official documentation too bad (it's certainly better than it used to be!). You can always ask a question about a specific part of Collection that you feel the documentation has missed – and I'll be happy to (attempt to) answer :)Sleazy
@Sleazy Yea... to I have a startIndex which I can offset using an integer to get the index I actually want to take an element from. So why on earth don't I get the good old get(i), or maybe even an array-style subscript? And why can I sometimes use only one of open and closed ranges? Maybe my complaints are just incompleteness of the API as opposed to conceptual issues, but so far I have not learned what the advantage of the very verbose constructs are. Scala, for instance, has a very strong Collection API -- but I found it easy to use as well. Anyway, thanks for your elaborate comments!Dillingham
@Dillingham I'm not aware of any cases where you can't subscript a Collection with a ClosedRange – there's a subscript overload defined on _Indexable (which Collection internally derives from). Regarding not having a convenient get(i)-style method – I don't know the full rationale, but I'm aware that the concern is partly performance related, for example for i in 0..<string.characters.count { print(string.get(i)) } feels like it should be a linear time operation...Sleazy
... but would in fact be quadratic as String.CharacterView isn't a RandomAccessCollection – having to say str.index(str.startIndex, offsetBy: i), while cumbersome, at least makes you more aware of this fact. Although as I said earlier, it's still not as sleek as it could be – I'm not sure if adding a get(_:)-style method would be the optimal solution, due to the fact that it would confuse the Array API slightly (do I say array.get(i) or array[i]?) – but regardless, it's definitely lacking some shorter way of getting an element at a given index offset.Sleazy
D
1

I don't know of any API that does this. Fortunately, writing our own is an easy exercise:

extension String {
    func taking(first: Int) -> String {
        if first <= 0 {
            return ""
        } else if let to = self.index(self.startIndex, 
                                      offsetBy: first, 
                                      limitedBy: self.endIndex) {
            return self.substring(to: to)
        } else {
            return self
        }
    }
}

Taking from the end is similar.

Find full code (including variants) and tests here.

Dillingham answered 7/4, 2017 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.