Passing Swift strings as C strings in variable argument lists - c

I am trying to use the old printf-style string formatters to define a new Swift string from an old Swift string.
I notice this works fine as long as I am starting with a Swift string literal, but not a string variable.
// OK!
String(format:"%s", "hello world".cStringUsingEncoding(NSUTF8StringEncoding))
// ERROR: argument type '[CChar]?' does not conform to expected type 'CVarArgType'
let s = "hello world"
String(format:"%s", s.cStringUsingEncoding(NSUTF8StringEncoding))
Why does this happen?
And what's the best workaround?
(Please note that I am aware of but do not want to use the Cocoa string formatter %#. I want the printf-style formatting code because what I'm actually trying to do is get quick and dirty tabular alignment with codes like %-10s.)
This question concerns Swift 2.2.

Don't create a C string just for alignment. There is a method stringByPaddingToLength for that.
// Swift 2
s.stringByPaddingToLength(10, withString: " ", startingAtIndex: 0)
// Swift 3
s.padding(toLength: 10, withPad: " ", startingAt: 0)
Note that it will truncate the string if it is longer than 10 UTF-16 code units.
The problem here is, there are two methods in Swift named cStringUsingEncoding:
func cStringUsingEncoding(encoding: UInt) -> UnsafePointer<Int8>, as a method of NSString
func cStringUsingEncoding(encoding: NSStringEncoding) -> [CChar]?, as an extension of String
If we want a pointer, we need to ensure we are using an NSString, not a String.
In the first example, a string literal can be a String or NSString, so the compiler chooses NSString since the other one won't work.
But in the second example, the type of s is already set to String, so the method that returns [CChar]? is chosen.
This could be worked-around by forcing s to be an NSString:
let s: NSString = "hello world"
String(format:"%s", s.cStringUsingEncoding(NSUTF8StringEncoding))

kennytm's answer is clearly the best way.
But for anyone else who is still wondering about how to do it the wrong way, and get access to a c string based on a Swift String without going through NSString, this also seems to work (Swift 2.2):
var s:String = "dynamic string"
s.nulTerminatedUTF8.withUnsafeBufferPointer { (buffptr) -> String in
let retval:String = String(format:"%s",buffptr.baseAddress)
return retval
}

Related

Beginner Question: Func return String and Array

I'm experimenting manipulating basic Swift data types and tried to add a (basic) layer of complexity to a simple function examples I was practising from Swifts Documentation. I was able to return as a simple array but when I tried to return an array within a String I got errors. I've read all the Swift Documentation for Arrays to try and solve this first with no luck.
Here is my code for the successful Array return:
func namesList(person: String) -> [(String)] {
let register = ["RoboCop", person, "Terminator"]
return register.sorted()
}
... and my unsuccessful code:
func namesList(person: String) -> [(String)] {
let register = "The alphabetic order of names are \(["RoboCop", person, "Terminator"])"
return register.sorted()
}
I think the problem is in my return parameters but couldn't find a way to Return string and array?
Many thanks
.sorted() operates on Sequence, and both Array and String are Sequences. [String].sorted (your first example) return a [String]. String.sorted (your second example) returns a [Character], since a String is made up of Characters.
Your second example is not "an array nested in a String." It's just a String. \(...) performs string interpolation. It doesn't nest anything.
If you want to return the String, you'll need to return String.
func namesList(person: String) -> String {
let register = ["RoboCop", person, "Terminator"].sorted()
return "The alphabetic order of names are \(register)"
}
As a very minor point, [(String)] is unusual syntax and can be confusing to experienced Swift devs. The correct way to write this is just [String] with no extra parentheses.
register variable is actually a String in the second case.
You should change the return type to just string.

Creating A Substring | Swift 4 [duplicate]

This question already has answers here:
How does String substring work in Swift
(25 answers)
How do you use String.substringWithRange? (or, how do Ranges work in Swift?)
(33 answers)
Closed 4 years ago.
How do I substring a string in swift 4.
I would like to convert:
"Hello, World!"
Into:
"Hello"
Swift has no built in function to make this easy so I wrote a simple extension to get the same functionality as other languages such as python which have really easy substring functions.
Extension
Outside your class, add this extension for strings.
extension String {
func substring(start: Int, range: Int) -> String {
let characterArray = Array(self)
var tempArray:[Character] = []
let starting = start
let ending = start + range
for i in (starting...ending) {
tempArray.append(characterArray[i])
}
let finalString = String(tempArray)
return finalString
}
}
Usage
let myString = "Hello, World!"
print(myString.substring(start: 0, range: 4))
This prints:
"Hello"
How It Works
The extension works by turning the string into an array of separate characters then creates a loop which appends the desired characters into a new string which is determined by the function's parameters.
Thanks for stopping by. I hope apple add this basic functionality to their language soon! The above code is a bit messy so if anyone wants to clean it up then feel free!

Faster way of converting image hex dump to binary array in Swift

I need to edit the pixels' raw binary values of an image. For this, I did this steps:
I obtained the CFData containing the hex dump of the image.
I converted the CFData obtained to an array of characters (using convertToArray function)
After that, I used convertToBinaryString function to obtain a string representing the base 2 value of the hex dump.
It does work and does the job for small files but when it comes to bigger ones it takes forever to finish. I failed in the struggle of finding a faster way. Could you help me?
Down here you can take a look of the functions I need to optimize:
func convertToArray(imageData : CFData) -> Array<Character>{
let arrayData : Array<Character> = Array(String(NSData(data: imageData)).characters)
print("Array : ")
Swift.print(arrayData)
return arrayData
}
func convertToBinaryString(array : Array<Character>) -> String{
let numberOfChars = array.count
var binaryString = convertHexToBinary(array[1])
for character in 2...numberOfChars - 2{
binaryString = binaryString + convertHexToBinary(array[character])
}
// print("BINARRY ARRAY : ")
// print(binaryString)
return binaryString
}
I would try this extension to NSData that provides a CollectionType interface. It seems like it would make it much easier for you to do your modifications without having to much around with UnsafeMutablePointer:
Bytes collection for NSData
Since BytesView and MutableBytesView are a CollectionType it gives you subscripting, indices, and more so you can iterate through your data and make the changes you want.
The article which is relevant to this question and introduces that linked code:
NSData, My Old Friend

How to convert array of strings to string in D?

I have got array of strings like:
string [] foo = ["zxc", "asd", "qwe"];
I need to create string from them. Like:
"zxc", "asd", "qwe" (yes every elements need be quoted and separate with comma from another, but it should be string, but not array of strings.
How can I do it's in functional style?
import std.algorithm, std.array, std.string;
string[] foo = ["zxc", "asd", "qwe"];
string str = foo.map!(a => format(`"%s"`, a)).join(", ");
assert(str == `"zxc", "asd", "qwe"`);
std.algorithm.map takes a lambda which converts an element in the range into something else, and it returns a lazy range where each element is the result of passing an element from the original range to the lambda function. In this case, the lambda takes the input string and creates a new string which has quotes around it, so the result of passing foo to it is a range of strings which have quotes around them.
std.array.join then takes a range and eagerly concatenates each of its elements with the given separator separating each one and returns a new array, and since it's given a range of strings it returns a string. So, by giving it the result of the call to map and ", " for the separator, you get your string made up of the original strings quoted and separated by commas.
std.algorithm.joiner would do the same thing as join, except that it results in a lazy range instead of an array.
Alternatively, use std.string.format
Your format specifier should be something like this:
auto joinedstring = format("%(\“%s\",%)", stringarray)
this is untested as I'm on mobile, but it's something similar to it!

Swift nil coalescing when using array contents

I'm just wondering if there's a more readable/less verbose way to write the following:
let rankMap: Dictionary<Int,String> = [1:"Ace", 11:"Jack", 12:"Queen", 13:"King"]
func getCardImage(suit: String, rank: Int) -> NSImage {
var imgRank: String
if rankMap[rank] == nil {
imgRank = rank.description
} else {
imgRank = (rankMap[rank]! as NSString).substringToIndex(1)
}
let imgSuit = (suit as NSString).substringToIndex(1)
//let imgRank: String = rankMap[rank] ?? rank.description
return NSImage(named: imgRank + imgSuit)
}
In particular, I'm interested in the part where rankMap is checked vs nil. Whatever is returned from the array needs to have every character except the first chopped off. I'm unable to write something like this:
let imgRank: String = (rankMap[rank] as NSString).substringToIndex(1) ?? rank.description
..due to the semantics around nil coalescing. Is there anything else I can do aside from the "hard way" of just doing an "if nil" check?
I removed the NSImage part, just so it's easier to test as a filename, but this should behave similarly.
func cardImage(suit: String, rank: Int) -> String {
let imgRankFull = rankMap[rank] ?? rank.description
let imgRank = first(imgRankFull) ?? " "
let imgSuit = first(suit) ?? " "
return imgRank + imgSuit
}
This shows how to use ??, but it still has lots of little problems IMO. I would recommend replacing suit and rank with enums. The current scheme has lots of ways it can lead to an error, and you don't do any good error checking. I've dodged by returning " ", but really these should be either fatalError or an optional, or a Result (an object that carries a value or an error).
You don't need substringToIndex to get the first character of a Swift string. String is a CollectionType, to which this generic function from the standard library applies:
func first<C : CollectionType>(x: C) -> C.Generator.Element?
So you might start by trying something like this:
let imgRank = first(rankMap[rank]) ?? rank.description // error
That doesn't work, for two reasons:
String.Generator.Element is a Character, not a String, so if you're going to put it in a ?? expression, the right operand of ?? needs to also be a Character. You can construct a Character from a String (with Character(rank.description)), but that'll crash if that string has more than one character in it.
rankMap[rank] is an optional, so you can't pass it directly to first. But if you try to force-unwrap it immediately (with first(rankMap[rank]!), you'll crash upon looking up something not in the dictionary.
You could try solving these problems together by putting the ?? inside the first call, but you probably want a two-character string for rank 10.
It'd be nice if we could treat String like an Array, which provides var first for getting its first element. (And have a version of first for strings that returns a String.) Then we could take advantage of optional chaining to put a whole expression to the left of the ??, and just write:
let imgRank = rankMap[rank]?.first ?? rank.description
Well, let's write it in an extension!
extension String {
var first : String? {
// we're overloading the name of the global first() function,
// so access it with its module name
switch Swift.first(self) {
case let .Some(char):
return String(char)
case .None:
return nil
}
}
}
Now you can be nice and concise about it:
let imgRank = rankMap[rank]?.first ?? String(rank)
(Notice I'm also changing rank.description to String(rank). It's good to be clear -- description gives you a string representation of a value that's appropriate for printing in the debugger; String() converts a value to a string. The former is not guaranteed to always match the latter, so use the latter if that's what you really want.)
Anyhow, #RobNapier's advice stands: you're probably better off using enums for these. That gets you a lot less uncertainty about optionals and invalid lookups. But seeing what you can do with the standard library is always a useful exercise.

Resources