Turn Swift function into extension for reuse - swift

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)

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"

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())

Related

Get just the hash from SHA256.hash(data:)?

I'm trying to get just a hash from SHA256.hash(data:) but in order to do this I need to grab the description and then use .replacingOccurrences(of: "SHA256 digest: ", with: ""). Is there a way that I can just get the full SHA256 hash as a string?
func getId<T>(input: T) -> String {
let input = "\(input)".utf8
let data = Data(input)
let hash = SHA256.hash(data: data).description.replacingOccurrences(of: "SHA256 digest: ", with: "")
return hash
}
You can simply map SHA256Digest bytes into an hexa string:
import CryptoKit
extension UnsignedInteger where Self: CVarArg {
var hexa: String { .init(format: "%ll*0x", bitWidth / 4, self) }
}
extension DataProtocol {
var sha256Digest: SHA256Digest { SHA256.hash(data: self) }
var sha256Data: Data { .init(sha256Digest) }
var sha256Hexa: String { sha256Digest.map(\.hexa).joined() }
}
func getId<D: DataProtocol>(data: D) -> String {
data.sha256Hexa
}

swift string separation but include the

I need to separate a string to a array of substring but need to include ",.?!" as the substring in Swift.
var sentence = "What is your name?" into
var words = ["What", "is", "your", "name", "?"]
I know I can use this to separate the white space, but I need the ".,?!" to be separated into a word in the words array. How can I do that?
var words = sentence.components(separatedBy: " ")
I only get ["What", "is", "your", "name?"]
I need to separate the ? at the end word, and make words array like this:
var words = ["What", "is", "your", "name", "?"]
You can enumerate your substrings in range using .byWords options, append the substring to your words array, get the substring range upperBound and the enclosed range upperBound, remove the white spaces on the resulting substring and append it to the words array:
import Foundation
let sentence = "What is your name?"
var words: [String] = []
sentence.enumerateSubstrings(in: sentence.startIndex..., options: .byWords) { substring, range, enclosedRange, _ in
words.append(substring!)
let start = range.upperBound
let end = enclosedRange.upperBound
words += sentence[start..<end]
.split{$0.isWhitespace}
.map(String.init)
}
print(words) // "["What", "is", "your", "name", "?"]\n"
You can also use a regular expression to replace the punctuation by the same punctuation preceded by a space before splitting your words by whitespaces:
let sentence = "What is your name?"
let words = sentence
.replacingOccurrences(of: "[.,;:?!]",
with: " $0",
options: .regularExpression)
.split{$0.isWhitespace}
print(words) // "["What", "is", "your", "name", "?"]\n"
Swift native approach:
var sentence = "What is your name?"
for index in sentence
.indices
.filter({ sentence[$0].isPunctuation })
.reversed() {
sentence.insert(" ", at: index)
}
let words = sentence.split { $0.isWhitespace }
words.forEach { print($0) }
This will print:
What
is
your
name
?
This function will split on whitespace and also include each punctuation character as a separate string. Apostrophes are treated as part of a word, so "can't" and "it's" are kept together as a single string. This function will also handle double spaces and tabs.
func splitSentence(sentence: String) -> [String] {
var result : [String] = []
var word = ""
let si = sentence.startIndex
for i in 0..<sentence.count {
let c = sentence[sentence.index(si, offsetBy: i)]
if c.isWhitespace {
if word.count > 0 {
result.append(word)
word = ""
}
} else if (c.isLetter || (String(c) == "'")) {
word = word + String(c)
} else {
if word.count > 0 {
result.append(word)
word = ""
}
result.append(String(c))
}
}
if word.count > 0 {
result.append(word)
}
return result
}
Here is some testing code:
func test(_ sentence: String, _ answer: [String]) {
print("--------------------------------")
print("sentence=" + sentence)
let result : [String] = splitSentence(sentence: sentence)
for s in result {
print("s={" + s + "}")
}
if answer.count != result.count {
print("#### Answer count mismatch")
}
for i in 0..<answer.count {
if answer[i] != result[i] {
print("### Mismatch: {" + answer[i] + "} != {" + result[i] + "}")
}
}
}
func runTests() {
test("", [])
test(" ", [])
test(" ", [])
test(" a", ["a"])
test("a ", ["a"])
test(" a", ["a"])
test(" a ", ["a"])
test("a ", ["a"])
test("aa", ["aa"])
test("a a", ["a", "a"])
test("?", ["?"])
test("a?", ["a", "?"])
test("???", ["?", "?", "?"])
test("What is your name?", [ "What", "is", "your", "name", "?" ])
test("What is your name? ", [ "What", "is", "your", "name", "?" ])
test("La niña es linda.", [ "La", "niña", "es", "linda", "."])
test("ñññ ñ ññ ñ", [ "ñññ", "ñ", "ññ", "ñ" ])
test("It's the 'best'.", [ "It's", "the", "'best'", "." ])
test("¿Cómo te llamas?", [ "¿", "Cómo", "te", "llamas", "?" ])
test("你好吗?", [ "你好吗", "?" ])
}
XCTAssertEqual(
"¿What is your name? My name is 🐱, and I am a cat!"
.split(separator: " ")
.flatMap { $0.split(includingSeparators: \.isPunctuation) }
.map(Array.init)
.map { String($0) },
[ "¿", "What", "is", "your", "name", "?",
"My", "name", "is", "🐱", ",", "and", "I", "am", "a", "cat", "!"
]
)
public enum Spliteration<Element> {
case separator(Element)
case subSequence([Element])
}
public extension Array {
init(_ spliteration: Spliteration<Element>) {
switch spliteration {
case .separator(let separator):
self = [separator]
case .subSequence(let array):
self = array
}
}
}
public extension Sequence {
/// The first element of the sequence.
/// - Note: `nil` if the sequence is empty.
var first: Element? {
var iterator = makeIterator()
return iterator.next()
}
func split(includingSeparators getIsSeparator: #escaping (Element) -> Bool)
-> AnySequence< Spliteration<Element> > {
var separatorFromPrefixIteration: Element?
func process(next: Element?) -> Void {
separatorFromPrefixIteration =
next.map(getIsSeparator) == true
? next
: nil
}
process(next: first)
let prefixIterator = AnyIterator(
dropFirst(
separatorFromPrefixIteration == nil
? 0
: 1
),
processNext: process
)
return .init {
if let separator = separatorFromPrefixIteration {
separatorFromPrefixIteration = nil
return .separator(separator)
}
return Optional(
prefixIterator.prefix { !getIsSeparator($0) },
nilWhen: \.isEmpty
).map(Spliteration.subSequence)
}
}
}
public extension AnyIterator {
/// Use when `AnyIterator` is required / `UnfoldSequence` can't be used.
init<State>(
state: State,
_ getNext: #escaping (inout State) -> Element?
) {
var state = state
self.init { getNext(&state) }
}
/// Process iterations with a closure.
/// - Parameters:
/// - processNext: Executes with every iteration.
init<Sequence: Swift.Sequence>(
_ sequence: Sequence,
processNext: #escaping (Element?) -> Void
) where Sequence.Element == Element {
self.init( state: sequence.makeIterator() ) { iterator -> Element? in
let next = iterator.next()
processNext(next)
return next
}
}
}
public extension AnySequence {
/// Use when `AnySequence` is required / `AnyIterator` can't be used.
/// - Parameter getNext: Executed as the `next` method of this sequence's iterator.
init(_ getNext: #escaping () -> Element?) {
self.init( Iterator(getNext) )
}
}
public extension Optional {
/// Wraps a value in an optional, based on a condition.
/// - Parameters:
/// - wrapped: A non-optional value.
/// - getIsNil: The condition that will result in `nil`.
init(
_ wrapped: Wrapped,
nilWhen getIsNil: (Wrapped) throws -> Bool
) rethrows {
self = try getIsNil(wrapped) ? nil : wrapped
}
}
Not answering using swift, but I believe the algorithm can be emulated with any language.
Done the implementation using Java. The code uses standard Java libraries and not external ones.
private void setSpecialCharsAsLastArrayItem() {
String name = "?what is your name?";
String regexCompilation = "[$&+,:;=?##|]";
Pattern regex = Pattern.compile(regexCompilation);
Matcher matcher = regex.matcher(name);
StringBuilder regexStr = new StringBuilder();
while (matcher.find()) {
regexStr.append(matcher.group());
}
String stringOfSpecialChars = regexStr.toString();
String stringWithoutSpecialChars = name.replaceAll(regexCompilation, "");
String finalString = stringWithoutSpecialChars + " "+stringOfSpecialChars;
String[] splitString = finalString.split(" ");
System.out.println(Arrays.toString(splitString));
}
will print [what, is, your, name, ??]
Here is the solution (Swift 5):
let sentence = "What is your name?".replacingOccurrences(of: "?", with: " ?")
let words = sentence.split(separator: " ")
print(words)
Output:
["What", "is", "your", "name", "?"]

Swift 5: Why Range<String.Index>.Bound.utf16Offset calculates offsets even if empty string provided as argument

In Swift 4 I had a function to dump Range to debugger output defined as below:
extension Range where Bound == String.Index {
public func dump() -> String {
return "[\(lowerBound.encodedOffset)..<\(upperBound.encodedOffset)] (\(length))"
}
public var length: Int {
return upperBound.encodedOffset - lowerBound.encodedOffset
}
}
Since in Swift 5 encodedOffset is deprecated I changed implementation as below:
extension Range where Bound == String.Index {
public func dump(string: String) -> String {
let lower = lowerBound.utf16Offset(in: string)
let upper = upperBound.utf16Offset(in: string)
let result = "[\(lower)..<\(upper)] (\(upper - lower))"
return result
}
}
Test working as expected after implementation update:
class RangeTests: LogicTestCase {
func test_dump() {
let string = "Hello!"
let range = string.range(of: "ello")
Assert.equals(range?.dump(string: string), "[1..<5] (4)")
}
}
But function dump works correctly even if empty string is passed:
class RangeTests: LogicTestCase {
func test_dump() {
let string = "Hello!"
let range = string.range(of: "ello")
Assert.equals(range?.dump(string: ""), "[1..<5] (4)")
}
}
Shouldn't, for instance, call to lowerBound.utf16Offset(in: "") throw exception because we passing empty string, while range itself not empty?
UPDATE 1:
With the trick, with lowerBound.utf16Offset(in: "") mentioned above, the following two versions of sample index shifting function works identically:
extension String.Index {
// Deprecated due `encodedOffset`
public func shifting(by offset: Int) -> String.Index {
return String.Index(encodedOffset: encodedOffset + offset)
}
// Possible Swift 5 replacement to achieve index manipulation
// without reference to string.
public func shifting(by offset: Int) -> String.Index {
let newOffset = utf16Offset(in: "") + offset
let referenceString = String(repeating: " ", count: newOffset)
let result = String.Index(utf16Offset: newOffset, in: referenceString)
return result
}
}

Sort a string to determine if it is a Anagram or Palindrome in Swift Xcode

I have a extension names String, with two functions names isAnagramOf and isPalindrome. The first function is supposed to take input as a String, then first it will replace whitespace with no space then sort and compare the string and return a Bool to determine if anagram or not.
The second function named isPalindrome and will also ignore whitespaces and capitalization, it will then reverse the String and compare to return if it is reversed.
I am new to swift and following a tutorial, but I kept getting these errors no matter how I tried to write it. I have gone through it at least 10 times now and cant get it to work
If anyone can help with this code that would be great, I would also be open to someone showing me another way to write. Perhaps as a array first then to sort the string, I am not sure though.
extension String {
func isAnagramOf(_ s: String) -> Bool {
let lowerSelf = self.lowercased().replacingOccurrences(of: " ", with: "")
let lowerOther = s.lowercased().replacingOccurrences(of: " ", with: "")
return lowerSelf.sorted() == lowerOther.sorted() // first error:Value of type 'String' has no member 'sorted
}
func isPalindrome() -> Bool {
let f = self.lowercased().replacingOccurrences(of: " ", with: "")
let s = String(describing: f.reversed()) //second error:Value of type 'String' has no member 'reversed'
return f == s
}
}
In Swift 3 a String itself is not a collection, so you have to
sort or reverse its characters view:
extension String {
func isAnagramOf(_ s: String) -> Bool {
let lowerSelf = self.lowercased().replacingOccurrences(of: " ", with: "")
let lowerOther = s.lowercased().replacingOccurrences(of: " ", with: "")
return lowerSelf.characters.sorted() == lowerOther.characters.sorted()
}
func isPalindrome() -> Bool {
let f = self.lowercased().replacingOccurrences(of: " ", with: "")
return f == String(f.characters.reversed())
}
}
A slightly more efficient method to check for a palindrome is
extension String {
func isPalindrome() -> Bool {
let f = self.lowercased().replacingOccurrences(of: " ", with: "")
return !zip(f.characters, f.characters.reversed()).contains(where: { $0 != $1 })
}
}
because no new String is created, and the function "short-circuits",
i.e. returns as soon as a non-match is found.
In Swift 4 a String is collection of its characters, and
the code simplifies to
extension String {
func isAnagramOf(_ s: String) -> Bool {
let lowerSelf = self.lowercased().replacingOccurrences(of: " ", with: "")
let lowerOther = s.lowercased().replacingOccurrences(of: " ", with: "")
return lowerSelf.sorted() == lowerOther.sorted()
}
func isPalindrome() -> Bool {
let f = self.lowercased().replacingOccurrences(of: " ", with: "")
return !zip(f, f.reversed()).contains(where: { $0 != $1 })
}
}
Note also that
let f = self.lowercased().replacingOccurrences(of: " ", with: "")
returns a string with all space characters removed. If you want
to remove all whitespace (spaces, tabulators, newlines, ...) then use
for example
let f = self.lowercased().replacingOccurrences(of: "\\s", with: "", options: .regularExpression)

Split a String without removing the delimiter in Swift

This might be a duplicate. I couldn't find the answer in Swift, so I am not sure.
componentsSeparatedByCharactersInSet removes the delimiter. If you separate by only one possible character it is easy to add it back. But what when you have a set?
Is there another method to split?
Swift 3 and 4 Versions
extension Collection {
func splitAt(isSplit: (Iterator.Element) throws -> Bool) rethrows -> [SubSequence] {
var p = self.startIndex
var result:[SubSequence] = try self.indices.flatMap {
i in
guard try isSplit(self[i]) else {
return nil
}
defer {
p = self.index(after: i)
}
return self[p...i]
}
if p != self.endIndex {
result.append(suffix(from: p))
}
return result
}
}
Thanks to Oisdk for getting me thinking.
This method works on CollectionTypes, rather than Strings, but it should be easy enough to adapt:
extension CollectionType {
func splitAt(#noescape isSplit: Generator.Element throws -> Bool) rethrows -> [SubSequence] {
var p = startIndex
return try indices
.filter { i in try isSplit(self[i]) }
.map { i in
defer { p = i }
return self[p..<i]
} + [suffixFrom(p)]
}
}
extension CollectionType where Generator.Element : Equatable {
func splitAt(splitter: Generator.Element) -> [SubSequence] {
return splitAt { el in el == splitter }
}
}
You could use it like this:
let sentence = "Hello, my name is oisdk. This should split: but only at punctuation!"
let puncSet = Set("!.,:".characters)
sentence
.characters
.splitAt(puncSet.contains)
.map(String.init)
// ["Hello", ", my name is oisdk", ". This should split", ": but only at punctuation", "!"]
Or, this version, which uses a for-loop, and splits after the delimiter:
extension CollectionType {
func splitAt(#noescape isSplit: Generator.Element throws -> Bool) rethrows -> [SubSequence] {
var p = startIndex
var result: [SubSequence] = []
for i in indices where try isSplit(self[i]) {
result.append(self[p...i])
p = i.successor()
}
if p != endIndex { result.append(suffixFrom(p)) }
return result
}
}
extension CollectionType where Generator.Element : Equatable {
func splitAt(splitter: Generator.Element) -> [SubSequence] {
return splitAt { el in el == splitter }
}
}
let sentence = "Hello, my name is oisdk. This should split: but only at punctuation!"
let puncSet = Set("!.,:".characters)
sentence
.characters
.splitAt(puncSet.contains)
.map(String.init)
// ["Hello,", " my name is oisdk.", " This should split:", " but only at punctuation!"]
Or, if you wanted to get the most Swift features into one function (defer, throws, a Protocol extension, an evil flatMap, guard, and Optionals):
extension CollectionType {
func splitAt(#noescape isSplit: Generator.Element throws -> Bool) rethrows -> [SubSequence] {
var p = startIndex
var result: [SubSequence] = try indices.flatMap { i in
guard try isSplit(self[i]) else { return nil }
defer { p = i.successor() }
return self[p...i]
}
if p != endIndex { result.append(suffixFrom(p)) }
return result
}
}
I came here looking for an answer to this question. Didn't find what I was looking for and ended up building this by repeated calls to .split(...) It isn't elegant but you can choose which delimiters are preserved and which aren't. There's probably a way to avoid the String <--> Substring conversions, anyone know?
var input = """
{All those moments will be (lost in time)},
like tears [in rain](. ([(Time to)] die))
"""
var separator: Character = "!"
var output: [String] = []
repeat {
let tokens = input.split(
maxSplits: 1,
omittingEmptySubsequences: false,
whereSeparator: {
switch $0 {
case "{", "}", "(", ")", "[", "]": // preserve
separator = $0; return true
case " ", "\n", ",", ".": // omit
separator = " "; return true
default:
return false
}
}
)
if tokens[0] != "" {
output.append(String(tokens[0]))
}
guard tokens.count == 2 else { break }
if separator != " " {
output.append(String(separator))
}
input = String(tokens[1])
} while true
for token in output { print("\(token)") }
In the case above, the selectors are not in actual sets. I didn't need that, but if you do, simply make these declarations,
let preservedDelimiters: Set<Character> = [ "{", "}", "(", ")", "[", "]" ]
let omittedDelimiters: Set<Character> = [ " ", "\n", ",", "." ]
and replace the whereSeparator function with:
whereSeparator: {
if preservedDelimiters.contains($0) {
separator = $0
return true
} else if omittedDelimiters.contains($0) {
separator = " "
return true
} else {
return false
}
}