Swift: Storing Array of Optionals to NSUserDefaults? - arrays

I am trying to save an array of optionals Strings to NSUserDefaults, but unfortunately it doesn't work:
var textFieldEntries: [String?]
...
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(textFieldEntries, forKey: "textFieldEntries")
// prints error: Cannot convert value of type '[String?]'
// to expected argument type 'AnyObject?'
}

[String?] is a Swift type that cannot be represented as a Foundation type. Normally, Swift Array bridges to NSArray, but an NSArray cannot contain nil. An Array of Optionals can contain nil, so it doesn't bridge automatically.
You could work around this by using a sparse array representation. (And since your content is strings — a property list type and therefore legal for use in NSUserDefaults — you don't even need to use NSCoding to encode the array.) A dictionary makes a pretty good sparse array:
var textFieldEntries: [String?] = ["foo", nil, "bar"]
func saveToDefaults() {
var sparseArray: [String: String] = [:] // plists need string keys
for (index, entry) in textFieldEntries.enumerate() {
if let e = entry {
sparseArray["\(index)"] = e
}
}
// sparseArray = ["0": "foo", "2": "bar"]
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(sparseArray, forKey: "textFieldEntries")
}
Then, when you go to read in your data from defaults, convert from the sparse-array dictionary form to the array-of-optionals form. That's a little bit more fun because you need to figure out from the sparse representation how many nils you need the array to store.
func readFromDefaults() {
let defaults = NSUserDefaults.standardUserDefaults()
guard let sparseArray = defaults.objectForKey("textFieldEntries") as? [String: String]
else { fatalError("unepxected defaults key format") }
// count of the sparse array = index of highest key + 1
let count = sparseArray.keys.flatMap({Int($0)}).maxElement()! + 1
// wipe the old array to put nils in all the right places
textFieldEntries = [String?](count: count, repeatedValue: nil)
// fill in the values
for (strindex, entry) in sparseArray {
guard let index = Int(strindex)
else { fatalError("non-numeric key") }
textFieldEntries[index] = entry
}
}
(Alternately, you might know that count is constant because it's, say, the number of text fields in your UI.)

Let's say this is the original array
let textFieldEntries: [String?]
First of all let's turn the array of String? into an array of String.
let entries = textFieldEntries.flatMap { $0 }
Now we can save it
NSUserDefaults.standardUserDefaults().setObject(entries, forKey: "entries")
And retrieve it
if let retrieved = NSUserDefaults.standardUserDefaults().objectForKey("entries") as? [String] {
// here your have your array
print(retrieved)
}

Related

Convert [MLMultiArray] to Float?

I have an MLMultiArray which is a result of an ML Model.
I need to convert it to Float so that I can further store it in Realm.
Below is an example of one of the MLMultiArray. The result from the ML Model contains 120 of the same vectors so its an array of MLMultiArrays i.e Array of Float32 1 x 128 matrices.
Float32 1 x 128 matrix
[4.476562,1.179688,0.07141113,6.976562,-0.2858887,-7.378906,0.6445312,3.695312,1.399414,2.486328,-3.988281,-0.2636719,1.000977,-4.480469,-7.832031,1.59082,0.8515625,-1.296875,-1.435547,7.839844,5.851562,0.3701172,-2.492188,7.273438,2.404297,-3.3125,-5.699219,-0.6816406,0.2807617,-3.882812,-3.982422,5.339844,4.125,-3.871094,0.6225586,1.712891,-10.02344,0.7119141,4.472656,3.566406,-0.559082,-1.049805,-4.679688,10.07812,-1.459961,4.707031,-6.078125,1.675781,-0.6259766,2.519531,3.472656,-3.400391,-6.714844,-4.933594,-1.733398,1.095703,-6.15625,9.234375,3.693359,-9.492188,0.8637695,0.8203125,-2.814453,-4.4375,-1.092773,3.332031,0.1623535,3.583984,-11.25781,-0.9941406,-0.3491211,1.464844,-1.579102,4.558594,2.703125,4.601562,5.914062,-2.402344,-5.46875,-0.355957,11.39062,2.070312,-7.289062,-0.4470215,-0.1595459,9.148438,1.833008,-2.097656,-3.9375,6.699219,-4.347656,-6.835938,-1.179688,3.910156,-13.07812,-1.947266,-0.9238281,-0.949707,-4.398438,2.363281,4.421875,4.632812,2.607422,8.773438,0.9106445,9.21875,-14.0625,-1.301758,-4.875,0.6054688,6.496094,-2.021484,3.898438,-4.644531,0.9853516,7.253906,3.066406,-1.051758,-8.09375,-6.527344,3.890625,5.175781,0.3701172,-0.5683594,-1.341797,0.1497803,4.074219,0.5932617]
Is there any way I can convert an array of MLMultiArray to Float32?
Any help would be appreciated <3
You can first convert the MLMultiArray to an UnsafeBufferPointer and then to a regular Array.
import CoreML
var a: [Float] = [ 1, 2, 3 ]
var m = try! MLMultiArray(a)
if let b = try? UnsafeBufferPointer<Float>(m) {
let c = Array(b)
print(c)
}
This is old question but this can help someone:
To convert from an MLMultiArray To An Array of primitive, You can use this function you will need just to change the output type
func convertToArray(from mlMultiArray: MLMultiArray) -> [Double] {
// Init our output array
var array: [Double] = []
// Get length
let length = mlMultiArray.count
// Set content of multi array to our out put array
for i in 0...length - 1 {
array.append(Double(truncating: mlMultiArray[[0,NSNumber(value: i)]]))
}
return array
}
To convert from an Array to MLMultiArray Use this, you may need to change the shape accordingly
func convertToMLMultiArray(from array: [Double]) -> MLMultiArray {
let length = NSNumber(value: array.count)
// Define shape of array
guard let mlMultiArray = try? MLMultiArray(shape:[1, length], dataType:MLMultiArrayDataType.double) else {
fatalError("Unexpected runtime error. MLMultiArray")
}
// Insert elements
for (index, element) in array.enumerated() {
mlMultiArray[index] = NSNumber(floatLiteral: element)
}
return mlMultiArray
}
I'm using this way, and subscript to access the element and it works fine for me.
const float *array = (float*)matrix.dataPointer

Array modification in loop

I'm working on a command-line application. The first array (which is called firstArray, yes) is a result of user input via readLine(), all of its elements is Double. Now I have to create a second array with results of calculations applied to my first array. Some of results is NaN, cause of trigonometric calculations. I need to change all the NaNs to string, but I get the error "Cannot assign value of type 'String' to type 'Double'". How to solve this problem?
func calcLn () -> [Double] {
var calculatedArray = [Double]()
for item in firstArray {
var result = log(Double((-100))/(cos(item)))
calculatedArray.append(result)
}
for index in 0..<calculatedArray.count {
if calculatedArray[index].isNaN {
calculatedArray[index] = String("can't calculate")
An array can only store one type of stuff. calculatedArray can only store Doubles, so you can't set its elements to Strings.
You can create a new array called outputArray that can store strings, and convert all the doubles to strings:
var outputArray = [String]()
for index in 0..<calculatedArray.count {
if calculatedArray[index].isNaN {
outputArray.append("can't calculate")
} else {
outputArray.append("\(calculatedArray[index])")
}
}
Your calcLn method returns a [Double], but it seems like you want to return outputArray. If that's the case, you need to change its signature to return [String].
Note that you can do array transformations with map. Here is a shorter version of your code:
func calcLn () -> [String] {
let calculatedArray = firstArray.map { log(100.0/cos($0)) }
let outputStringsArray = calculatedArray.map { $0.isNaN ? "can't calculate" : "\($0)" }
return outputStringsArray
}

String into array in Swift 3

I am trying to transform a string of the following format into an array (...of arrays, of floats!) in Swift 3:
"[173.0, 180.5],[173.0, 180.0],[174.0, 180.5],[174.0, 183.0]"
so that the output would be an array in this format:
[[173.0, 180.5, 173.0, 180.0],[174.0, 180.5, 174.0, 183.0]]
I am really new to Swift and struggling to find any String functions that will allow me to convert the data in this way. Any pointers on how I can do it would be awesome - thanks!
As Martin said, you first want to first convert this from a string to an array. In Swift 3:
let string = "[173.0, 180.5],[173.0, 180.0],[174.0, 180.5],[174.0, 183.0]"
let jsonString = "[" + string + "]"
guard let data = jsonString.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data),
let numberPairs = json as? [[Double]] else {
fatalError("string was not well-formed: \(string)")
}
You then want to combine these pairs of numbers together:
var combinedNumbers = [[Double]]()
var current: [Double]?
for numberPair in numberPairs {
if current != nil {
combinedNumbers.append(current! + numberPair)
current = nil
} else {
current = numberPair
}
}
// use `combinedNumbers` here
Clearly, you should use better variable names (perhaps something that suggests what these sets of numbers are), but hopefully this illustrates the idea.
Swift 4
You can use Decodable:
let str = "[173.0, 180.5],[173.0, 180.0],[174.0, 180.5],[174.0, 183.0]"
let json = "[\(str)]".data(using: .utf8)!
let numbers = try JSONDecoder().decode([[Double]].self, from: json).flatMap { $0 }
let result = stride(from: 0, to: numbers.count, by: 4).map { startIndex -> [Double] in
let endIndex = min(startIndex + 4, numbers.count)
return Array(numbers[startIndex..<endIndex])
}
Swift 3
One option is to use the old-school NSScanner to extract the numbers from the string to a flat array, then build a 2 dimensional array off that:
let str = "[173.0, 180.5],[173.0, 180.0],[174.0, 180.5],[174.0, 183.0]"
let scanner = Scanner(string: str)
scanner.charactersToBeSkipped = CharacterSet(charactersIn: "[], ")
// Build the flat array
var numbers = [Double]()
while !scanner.isAtEnd {
var d = 0.0
if scanner.scanDouble(&d) {
numbers.append(d)
}
}
// Now the 2 dimensional array
let result = stride(from: 0, to: numbers.count, by: 4).map { startIndex -> [Double] in
let endIndex = min(startIndex + 4, numbers.count)
return Array(numbers[startIndex..<endIndex])
}
One option to convert the data types would be to develop a simple algorithm that will iterate through the string and analyze elements and square bracket delimiters, returning the appropriate conversion.
Below is the skeleton of what the fundamental components of such a function could look like.
Included are some basic aspects of the conversion from string to array.
var str = "[173.0, 180.5], [173.0, 180.0],[174.0, 180.5],[174.0, 183.0]"
// Cast returns array ["[","1","7","3",".","0",",".......]
let array = Array(str.characters)
// Iterate through array
for char in array {
if char == "[" || char == "]" {
// Deal with array delimiters appropriately
}
...
}
It might help to check out this similar problem.
NOTE: As Martin R mentioned, JSON interpretation may be a good method as well.

How do I cast my array of arrays as a [[Double]] in Swift 3.0?

In Swift 2.3 I am able to take a [String:AnyObject] and cast it as! [[Double]]. However, using the same object in Swift 3.0, I am unable to cast it as a [[Double]]. When I cast it instead as a [[AnyObject]] or [[Any]], loop through and try and convert, I get the following error:
Could not cast value of type '__NSArrayI' (0x10783ec08) to 'NSNumber' (0x106e47320).
The following code works in my Swift 2.3 implementation, but NOT Swift 3.0
func extractCoordinatesForArea() {
// This first line is where it crashes
let theArraysOfCoordinates = myObject.geoArea!["geometry"]!["coordinates"] as! [[Double]]
var newArea:[CLLocationCoordinate2D] = []
for coordArray in theArraysOfCoordinates {
// x is LONG, LAT
let x = [coordArray[0], coordArray[1]]
// Reverse the coordinates because they are stored as LONG, LAT on the server
let y = CLLocationCoordinate2DMake(x[1],x[0])
print(y)
newArea.append(y)
}
}
While the error makes sense, I cannot seem to get this to work after having explicitly declared the type or converting in the for loop. Any help is greatly appreciated.
Try to break the first statement like this.
if let geometryDic = myObject.geoArea["geometry"] as? [String:Any],
let coordinatesArray = geometryDic["coordinates"] as? [Any] {
let theArraysOfCoordinates = coordinatesArray.first as? [[Double]] {
var newArea:[CLLocationCoordinate2D] = []
for coordArray in theArraysOfCoordinates {
//There is no need to create another array X you can directly use coordArray
// Reverse the coordinates because they are stored as LONG, LAT on the server
let coordinate = CLLocationCoordinate2DMake(coordArray[1],coordArray[0])
print(coordinate)
newArea.append(coordinate)
}
}

Swift Convert A String-As-An-Array, To An Array Of Strings

I have a string:
"["word1","word2"]"
And I want a simple way to convert it to an actual [String].
All the other questions I could dig up on there were about converting int strings to arrays.
I tried doing
Array(arrayLiteral: "["word1","word2"]")
But I get
["[\"word1\",\"word2\"]"]
Manually cleaning up the edges and removing the slashes seems like I'm doing something very wrong.
I'm curious if there's a simple way to convert a an array of strings as a string into an array of strings.
i.e. Convert "["word1","word2"]" to ["word1","word2"]
Solution (Thanks to #Eric D)
let data = stringArrayString.dataUsingEncoding(NSUTF8StringEncoding)
var stringsArray:[String]!
do
{
stringsArray = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String]
} catch
{
print()
}
print("Array is \(stringsArray)")
Encode your "string array" to data, then decode this data as JSON to a Swift Array.
Like this, for example:
let source = "[\"word1\",\"word2\"]"
guard let data = source.dataUsingEncoding(NSUTF8StringEncoding),
arrayOfStrings = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String] else {
fatalError()
}
print(arrayOfStrings) // ["word1", "word2"]
print(arrayOfStrings[1]) // "word2"
Agree with comments above, I'd probably use a JSON parser. Failing that (or if you can't for some reason), I do not know of any built-in way; you'd have to do it manually. I'd do something like:
extension String {
func stringByTrimmingFirstAndLast() -> String {
let startIndex = self.startIndex.successor()
let endIndex = self.endIndex.predecessor()
return self.substringWithRange( startIndex..<endIndex )
}
}
let str = "[\"word1\",\"word2\"]"
let array = str
.stringByTrimmingFirstAndLast()
.componentsSeparatedByString(",")
.map { string in
return string.stringByTrimmingFirstAndLast()
}

Resources