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

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

Related

Swift: sort an array of strings...of more or less numbers

I have an array of strings, examples are as follows:
"0.125-0.25"
"1-2"
"50-100"
"100-200"
The result of sorting these is:
"0.125-0.25"
"1-2"
"100-200"
"50-100"
And if I append("1000-2000") to the array and then sort it will be:
"0.125-0.25"
"1-2"
"100-200"
"1000-2000"
"50-100"
But what I want is:
"0.125-0.25"
"1-2"
"50-100"
"100-200"
"1000-2000"
It's definitely an edge case, but I have been having luck on my own. Thanks everyone.
A working but not very efficient solution is to extract the first Double value in the string ranges and sort by them. It's very inefficient because in each call of the closure both Double values have to be recreated.
var array = ["1-2", "50-100", "0.125-0.25", "100-200"]
array.append("1000-2000")
let sortedArray = array.sorted { (str1, str2) -> Bool in
func firstDouble(of string: String) -> Double { return Double(string.components(separatedBy: "-").first!)! }
return firstDouble(of: str1) < firstDouble(of: str2)
}
print(sortedArray)
A more efficient solution is to map the array (once) to its first Double value, then zip both arrays, sort the combined array by the Double array and map the result back to the string-range array.
var array = ["1-2", "50-100", "0.125-0.25", "100-200"]
array.append("1000-2000")
let firstDoubleArray = array.map{Double($0.components(separatedBy: "-").first!)!}
let sortedArray = zip(array, firstDoubleArray).sorted {$0.1 < $1.1}.map{$0.0}
print(sortedArray)
What it appears you're sorting is ranges of Doubles, so the problem can be clarified by creating an intermediate object…
struct DoubleRange: Comparable {
let start: Double
let end: Double
init(string: String) {
let components = string.split(separator: "-")
start = Double(components[0])! // Be careful with `!` here, I'm assuming the format is always correct
end = Double(components[1])!
}
var stringValue: String {
return "\(start)-\(end)"
}
static func < (lhs: DoubleRange, rhs: DoubleRange) -> Bool {
return lhs.start < rhs.start
}
}
Then sorting is simple…
var array = ["1-2", "50-100", "0.125-0.25", "100-200"]
array.append("1000-2000")
array.map(DoubleRange.init).sorted().map{$0.stringValue}
// ["0.125-0.25", "1.0-2.0", "50.0-100.0", "100.0-200.0", "1000.0-2000.0"]
And if you always want to convert back to the string value, you could add…
extension Array where Element == String {
func sortedDoubleRange() -> [String] {
return array.map(DoubleRange.init).sorted().map{$0.stringValue}
}
}
array.sortedDoubleRange()
Be careful with this though… it will crash if any of strings are formatted incorrectly.

String Sorting Based on Last Character in Swift

I want to sort my string array based on last character. Here is my string array:
["c_572A267C-DAC8-487D-B1AF-719FE8E3A6AB_FF6E00_2",
"b_69E21DC6-431C-4373-B4F1-90BF7FB5462B_FFC000_1"]
Now I want to sort this array based on last value that is after last underscore(_).
Is it possible ?
Thanks
sorted can provide a custom sort condition for example (assuming that all strings are not empty)
let array = ["c_572A267C-DAC8-487D-B1AF-719FE8E3A6AB_FF6E00_2", "b_69E21DC6-431C-4373-B4F1-90BF7FB5462B_FFC000_1"]
let sortedArray = array.sorted { $0.substring(from: $0.index(before: $0.endIndex)) < $1.substring(from: $1.index(before: $1.endIndex)) }
Swift 3+ the syntax is much more convenient
let array = ["c_572A267C-DAC8-487D-B1AF-719FE8E3A6AB_FF6E00_2", "b_69E21DC6-431C-4373-B4F1-90BF7FB5462B_FFC000_1"]
let sortedArray = array.sorted { $0.suffix(1) < $1.suffix(1) }
No doubt it is. By using sorted(by:), you could do it like this:
let myArray = ["c_572A267C-DAC8-487D-B1AF-719FE8E3A6AB_FF6E00_2",
"b_69E21DC6-431C-4373-B4F1-90BF7FB5462B_FFC000_1"]
let sortedArray = myArray.sorted {
guard let str1LastChar = $0.characters.last, let str2LastChar = $1.characters.last else {
return false
}
return str1LastChar < str2LastChar
}
print(sortedArray)
Note that if myArray contains any empty string ("") the sort should be as is.
One more answer with Higher Order Function:
Reverse Each word in an Array
Then, Sort
let arr = ["c_572A267C-DAC8-487D-B1AF-719FE8E3A6AB_FF6E00_2",
"b_69E21DC6-431C-4373-B4F1-90BF7FB5462B_FFC000_1"]
One Line:
let returnValue = arr.map({String($0.reversed())}).sorted().map({String($0.reversed())})
Multi Line:
let reverseEachWordsArr = arr.map { value in
return String(value.reversed())
}
let finalCharSortArr = reverseEachWordsArr.sorted().map { word in
return String(word.reversed())
}
print(finalCharSortArr)
OUTPUT:
["b_69E21DC6-431C-4373-B4F1-90BF7FB5462B_FFC000_1",
"c_572A267C-DAC8-487D-B1AF-719FE8E3A6AB_FF6E00_2"]

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.

Simple way to convert an array of non-optionals to an array of optionals

I wish to assign an array [T] to an optional array [T?]. This seems like it should be straightforward but the only solution I came up with is to do it manually.
struct ArrayHelper<T> {
func toArrayOfOptionals(input: [T]) -> [T?] {
var result = [T?]()
for value in input {
result.append(value)
}
return result
}
}
I don't think there are any built-in ways to do this seamlessly, but map ought to be a simpler solution:
var input = [T]()
let output = input.map { Optional($0) }
Edit: Code modified based on suggestions in comments.

Swift: Storing Array of Optionals to NSUserDefaults?

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

Resources