0

I would like to remove specific characters from a string - instead of reentering the following across various string in my application, I would like to create an extension to easily reference across the code.

How can this be turned into an extension?

var string = "11224B"
let removeCharacters: Set<Character> = [" ", ";", ".", "!", "/"]
string.removeAll(where: { removeCharacters.contains($0) })
print(string)
pkamb
  • 33,281
  • 23
  • 160
  • 191
John
  • 965
  • 8
  • 16

2 Answers2

2

What you need is to extend the protocol which requires you to implement removeAll(where:) method, in this case RangeReplaceableCollection and constrain Self to StringProtocol:


extension RangeReplaceableCollection where Self: StringProtocol {
    mutating func remove(characters: Set<Element>) {
        removeAll(where: characters.contains)
    }
}

var string = "1/1!2.2;4 B"
string.remove(characters: [" ", ";", ".", "!", "/"])
print(string)  // "11224B\n"

And the non mutating method as well but using filter instead. You just need to return Self:

extension RangeReplaceableCollection where Self: StringProtocol {
    func removing(characters: Set<Element>) -> Self {
        filter { !characters.contains($0) }
    }
}

let string = "1/1!2.2;4 B"
string.removing(characters: [" ", ";", ".", "!", "/"]) // "11224B\n"

You can also make your method generic and allow any sequence type which its element is equal to the collection element type:


extension RangeReplaceableCollection where Self: StringProtocol {
    mutating func remove<S: Sequence>(characters: S) where S.Element == Element {
        removeAll(where: characters.contains)
    }
    func removing<S: Sequence>(characters: S) -> Self where S.Element == Element {
        filter { !characters.contains($0) }
    }
}

var string = "1/1!2.2;4 B"
let characters: Set<Character> = [" ", ";", ".", "!", "/"]
string.remove(characters: characters) 
string // "11224B\n"

let string = "1/1!2.2;4 B"
let charactersString = " ;.!/"
string.removing(characters: charactersString) // "11224B\n"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Leo, is it possible to remove all emoji as well? How can isEmojiPresentation be added into the array of characters to remove? – John Aug 19 '21 at 17:50
  • 2
    @MarkLyson just filter them `filter { $0.unicodeScalars.first?.properties.isEmojiPresentation == false }` if you want to remove the characters at the sequence as well as any character that isEmojiPresentation `filter { !characters.contains($0) && $0.unicodeScalars.first?.properties.isEmojiPresentation == false }` – Leo Dabus Aug 19 '21 at 18:15
1

Extension:

extension String {
    func removeCharacters(from characterSet: CharacterSet) -> String {
        let filteredString = self.unicodeScalars.filter { !characterSet.contains($0) }
        return String(String.UnicodeScalarView(filteredString))
    }
}

Usage:

var string1 = "1122 ;4B"
print(string1.removeCharacters(from: [" ", ";", ".", "!", "/"]))

Use removeCharacters as default parameter:

extension String {
    func removeCharacters(from characterSet: CharacterSet = [" ", ";", ".", "!", "/"]) -> String {
        let filteredString = self.unicodeScalars.filter { !characterSet.contains($0) }
        return String(String.UnicodeScalarView(filteredString))
    }
}

var string1 = "1122 ;4B"
print(string1.removeCharacters())
RTXGamer
  • 3,215
  • 6
  • 20
  • 29
  • Can `removeCharacters` be inside the extension? It would be best if the characters to remove were not reinstated each time – John Aug 19 '21 at 16:14
  • Yes, put `removeCharacters` inside the `func`, if that's what you want. – RTXGamer Aug 19 '21 at 16:21
  • Can you please show an example of how that would look - having an issue understanding how to provide it in the function itself. I appreciate your help – John Aug 19 '21 at 16:26
  • 1
    Better yet, have the function take a CharacterSet parameter with a default value of the "normal" set the OP wants. That way you don't have to specify a CharacterSet unless it's different from the normal set. – Duncan C Aug 19 '21 at 17:01
  • @MarkLyson Have default value for the `characterSet` parameters, as @Duncan suggested. – RTXGamer Aug 19 '21 at 17:09
  • @DuncanC you can make the method generic and take any sequence which element is equal to the original collection in this case a String or a Substring as well. – Leo Dabus Aug 19 '21 at 17:33
  • Is it possible to also exclude all emojis as part of the array using something like this? `{!$0.properties.isEmojiPresentation}` included in the extension? – John Aug 19 '21 at 18:07