Defining a custom PURE Swift Character Set - string

So, using Foundation you can use NSCharacterSet to define character sets and test character membership in Strings. I would like to do so without Cocoa classes, but in a purely Swift manner.
Ideally, code could be used like so:
struct ReservedCharacters: CharacterSet {
characters "!", "#", "$", "&", ... etc.
func isMember(character: Character) -> Bool
func encodeCharacter(parameters) { accepts a closure }
func decodeCharacter(parameters) { accepts a closure }
}
This is probably a very loaded question. But I'd like to see what you Swifters think.

You can already test for membership in a character set by initializing a String and using the contains global function:
let vowels = "aeiou"
let isVowel = contains(vowels, "i") // isVowel == true
As far as your encode and decode functions go, are you just trying to get the 8-bit or 16-bit encodings for the Character? If that is the case then just convert them to a String and access there utf8 or utf16 properties:
let char = Character("c")
let a = Array(String(char).utf8)
println() // This prints [99]
Decode would take a little more work, but I know there's a function for it...
Edit: This will replace a character from a characterSet with '%' followed by the character's hex value:
let encode: String -> String = { s in
reduce(String(s).unicodeScalars, "") { x, y in
switch contains(charSet, Character(y)) {
case true:
return x + "%" + String(y.value, radix: 16)
default:
return x + String(y)
}
}
}
let badURL = "http://why won't this work.com"
let encoded = encode(badURL)
println(encoded) // prints "http://why%20won%27t%20this%20work.com"
Decoding, again, is a bit more challenging, but I'm sure it can be done...

Related

Replace some characters in a string with the next unicode character

I have an input text as following:
inputtext = "This is a test";
I need to replace some of the character (based on a certain criteria) to next unicode character
let i = 0;
for c in inputtext.chars() {
if (somecondition){
// Replace char here
inputtext.replace_range(i..i+1, newchar);
// println!("{}", c);
}
What is the best way to do this?
You can't easily update a string in-place because a Rust string is not just an array of characters, it's an array of bytes (in UTF-8 encoding), and different characters may use different numbers of bytes. For example, the character ߿ (U+07FF "Nko Taman Sign") uses two bytes, whereas the next Unicode character ࠀ (U+0800 "Samaritan Letter Alaf") uses three.
It's therefore simplest to turn the string into an iterator of characters (using .chars()), manipulate that iterator as appropriate, and then construct a new string using .collect().
For example:
let old = "abcdef";
let new = old.chars()
// note: there's an edge case if ch == char::MAX which we must decide
// how to handle. in this case I chose to not change the
// character, but this may be different from what you need.
.map(|ch| {
if somecondition {
char::from_u32(ch as u32 + 1).unwrap_or(ch)
} else {
ch
}
})
.collect::<String>();

Rascal: Convert define to camelcase

I want to convert c-style define names to camelcase.
Example: BLA_BLA -> blaBla
I created the following function:
private str camelCaseName(n) {
rVal = n;
if (contains(rVal, "_")) {
rVal = toLowerCase(rVal);
for (pos <- findAll(rVal, "_")) {
up = toUpperCase(stringChar(charAt(rVal, pos + 1)));
charList = chars(rVal);
charList = delete(charList, pos + 1);
charList = insertAt(charList, pos + 1, charAt(up, 0));
rVal = stringChars(charList);
}
// rVal = replaceAll(rVal, "_", "");
}
return rVal;
}
This works well for changing the case. However if I uncomment the replaceAll then a call that uses the string reports "Expected str, but got tuple[value,value,value]".
I guess there is an easier way to get this functionality.
I'm not sure what goes wrong with the replaceAll function, but I'd try the static checker on this code and read its error message.
Another way of writing the function:
private str camelCaseName(str n) {
words = split("_", toLowerCase(n));
return "<words[0]><for (w <- words[1..]) {><capitalize(w)><}>";
}
I added a type to the parameter n, that's obligatory.
String templates are a nice way of printing out stuff in a string.
The split function is handy; it splits a string into list elements.
The capitalize function changes the first letter of a word to a capital

Swift remove ONLY trailing spaces from string

many examples in SO are fixing both sides, the leading and trailing. My request is only about the trailing.
My input text is: " keep my left side "
Desired output: " keep my left side"
Of course this command will remove both ends:
let cleansed = messageText.trimmingCharacters(in: .whitespacesAndNewlines)
Which won't work for me.
How can I do it?
A quite simple solution is regular expression, the pattern is one or more(+) whitespace characters(\s) at the end of the string($)
let string = " keep my left side "
let cleansed = string.replacingOccurrences(of: "\\s+$",
with: "",
options: .regularExpression)
You can use the rangeOfCharacter function on string with a characterSet. This extension then uses recursion of there are multiple spaces to trim. This will be efficient if you only usually have a small number of spaces.
extension String {
func trailingTrim(_ characterSet : CharacterSet) -> String {
if let range = rangeOfCharacter(from: characterSet, options: [.anchored, .backwards]) {
return self.substring(to: range.lowerBound).trailingTrim(characterSet)
}
return self
}
}
"1234 ".trailingTrim(.whitespaces)
returns
"1234"
Building on vadian's answer I found for Swift 3 at the time of writing that I had to include a range parameter. So:
func trailingTrim(with string : String) -> String {
let start = string.startIndex
let end = string.endIndex
let range: Range<String.Index> = Range<String.Index>(start: start, end: end)
let cleansed:String = string.stringByReplacingOccurrencesOfString("\\s+$",
withString: "",
options: .RegularExpressionSearch,
range: range)
return cleansed
}
Simple. No regular expressions needed.
extension String {
func trimRight() -> String {
let c = reversed().drop(while: { $0.isWhitespace }).reversed()
return String(c)
}
}

How to make a function that compares strings?

I want to make a function which compares strings.
I don't want to use equal operators (==), I want it worked only with Swift language.
First I made a function which takes 2 strings, and returns bool type.
then I looped these strings with for in syntax.
And want to compare these characters, if strings have equal value, it should return true, if not, then false. Is there any better way?
func isEqual(str1:String, str2:String) -> Bool {
var result = false
for char in str1 {
}
for char2 in str2 {
}
//Compare characters.
return result
}
== works fine with Strings in Swift. For educational purposes
(as I conclude from your comment "because I'm practicing...")
you can implement it as:
func myStringCompare(str1 : String, str2 : String) -> Bool {
if count(str1) != count(str2) {
return false
}
for (c1, c2) in zip(str1, str2) {
if c1 != c2 {
return false
}
}
return true
}
zip(str1, str2) returns a sequence of pairs from the given
sequences, this is a convenient way to enumerate the strings
"in parallel".
Once you have understood how it works, you can shorten it,
for example to:
func myStringCompare(str1 : String, str2 : String) -> Bool {
return count(str1) == count(str2) && !contains(zip(str1, str2), { $0 != $1 })
}
Comparing the string length is necessary because the zip() sequence
terminates as soon as one of the strings is exhausted. Have a look at
#drewag's answer to In Swift I would like to "join" two sequences in to a sequence of tuples
for an alternative Zip2WithNilPadding sequence.
If you don't want to use the built-in zip() function (again for
educational/self-learning purposes!) then you can use the fact
that Strings are sequences, and enumerate them in parallel using
the sequence generator. This would work not only for strings but
for arbitrary sequences, as long as the underlying elements can
be tested for equality, so let's make it a generic function:
func mySequenceCompare<S : SequenceType where S.Generator.Element : Equatable>(lseq : S, rseq : S) -> Bool {
var lgen = lseq.generate()
var rgen = rseq.generate()
// First elements (or `nil`):
var lnext = lgen.next()
var rnext = rgen.next()
while let lelem = lnext, relem = rnext {
if lelem != relem {
return false
}
// Next elements (or `nil`):
lnext = lgen.next()
rnext = rgen.next()
}
// Are both sequences exhausted?
return lnext == nil && rnext == nil
}
Tests:
mySequenceCompare("xa", "xb") // false
mySequenceCompare("xa", "xa") // true
mySequenceCompare("a", "aa") // false
mySequenceCompare("aa", "a") // false
My solution differ a little as I didn't know about the zip operator, I guess is not as efficient as the one post by Martin great use of tuple.
Great question alphonse
func isEqual(str1:String, str2:String) -> Bool {
if count(str1) != count(str2){
return false
}
for var i = 0; i < count(str1); ++i {
let idx1 = advance(str1.startIndex,i)
let idx2 = advance(str2.startIndex,i)
if str1[idx1] != str2[idx2]{
return false
}
}
return true
}
As pointed by Martin each string needs its own index, as explained by him:
"The "trick" is that "🇩🇪" is an "extended grapheme cluster" and consists of two Unicode code points, but counts as one Swift character."
Link for more details about extended grapheme cluster

String interpolation in Swift

A function in swift takes any numeric type in Swift (Int, Double, Float, UInt, etc).
the function converts the number to a string
the function signature is as follows :
func swiftNumbers <T : NumericType> (number : T) -> String {
//body
}
NumericType is a custom protocol that has been added to numeric types in Swift.
inside the body of the function, the number should be converted to a string:
I use the following
var stringFromNumber = "\(number)"
which is not so elegant, PLUS : if the absolute value of the number is strictly inferior to 0.0001 it gives this:
"\(0.000099)" //"9.9e-05"
or if the number is a big number :
"\(999999999999999999.9999)" //"1e+18"
is there a way to work around this string interpolation limitation? (without using Objective-C)
P.S :
NumberFormater doesn't work either
import Foundation
let number : NSNumber = 9_999_999_999_999_997
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 20
formatter.minimumIntegerDigits = 20
formatter.minimumSignificantDigits = 40
formatter.string(from: number) // "9999999999999996.000000000000000000000000"
let stringFromNumber = String(format: "%20.20f", number) // "0.00000000000000000000"
Swift String Interpolation
1) Adding different types to a string
2) Means the string is created from a mix of constants, variables, literals or expressions.
Example:
let length:Float = 3.14
var breadth = 10
var myString = "Area of a rectangle is length*breadth"
myString = "\(myString) i.e. = \(length)*\(breadth)"
Output:
3.14
10
Area of a rectangle is length*breadth
Area of a rectangle is length*breadth i.e. = 3.14*10
Use the Swift String initializer: String(format: <#String#>, arguments: <#[CVarArgType]#>)
For example:
let stringFromNumber = String(format: "%.2f", number)
String and Characters conforms to StringInterpolationProtocol protocol which provide more power to the strings.
StringInterpolationProtocol - "Represents the contents of a string literal with interpolations while it’s being built up."
String interpolation has been around since the earliest days of Swift, but in Swift 5.0 it’s getting a massive overhaul to make it faster and more powerful.
let name = "Ashwinee Dhakde"
print("Hello, I'm \(name)")
Using the new string interpolation system in Swift 5.0 we can extend String.StringInterpolation to add our own custom interpolations, like this:
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Date) {
let formatter = DateFormatter()
formatter.dateStyle = .full
let dateString = formatter.string(from: value)
appendLiteral(dateString)
}
}
Usage: print("Today's date is \(Date()).")
We can even provide user-defined names to use String-Interpolation, let's understand with an example.
extension String.StringInterpolation {
mutating func appendInterpolation(JSON JSONData: Data) {
guard
let JSONObject = try? JSONSerialization.jsonObject(with: JSONData, options: []),
let jsonData = try? JSONSerialization.data(withJSONObject: JSONObject, options: .prettyPrinted) else {
appendInterpolation("Invalid JSON data")
return
}
appendInterpolation("\n\(String(decoding: jsonData, as: UTF8.self))")
}
}
print("The JSON is \(JSON: jsonData)")
Whenever we want to provide "JSON" in the string interpolation statement, it will print the .prettyPrinted
Isn't it cool!!

Resources