In swift 3.0, I would like to concatenate strings (string1 & string2) and use the resulting string (string3) as the name of the array I want to access.
let fruitsArray = ["apple", "orange", "banana"]
let appleArray = ["red", "green", "yellow"]
let string1 = "apple"
let string2 = "Array"
let string3 = string1 + string2
let string4 = string3[1]
Of course there is an error message "subscript is unavailable ...)
What is the proper way to do this? Many thanks to you all.
What you want is possible using some form of introspection but it's a bad practice and extremely clumsy anyhow. You should remodel your data, for example, use a Dictionary:
let data = [
"fruitsArray" : ["apple", "orange", "banana"],
"appleArray": ["red", "green", "yellow"]
]
let string1 = "apple"
let string2 = "Array"
let string3 = string1 + string2
if let array = data[string3] {
let string4 = array[1]
print(string4)
} else {
print("Key not found")
}
As #CodeDifferent writes in his/her answer, you might want to consider remodelling your data as this type of runtime property accessing is not very "Swifty".
If you're only interesting in reading data for the sake of debugging, however, you could make use of the Mirror structure to perform runtime introspection on the data type that owns the array properties.
E.g.:
struct Foo {
let fruitsArray = ["apple", "orange", "banana"]
let appleArray = ["red", "green", "yellow"]
}
func attemptToReadStringArrayProperty(_ name: String, fromFoo foo: Foo) -> [String]? {
return Mirror(reflecting: foo).children
.flatMap { ($0 ?? name + ".") == name ? ($1 as? [String]) : nil }.first
}
/* example usage/debugging */
let string1 = "apple"
let string2 = "Array"
let string3 = string1 + string2
let foo = Foo()
if let strArr = attemptToReadStringArrayProperty(string3, fromFoo: foo) {
strArr.forEach { print($0) }
} /* red
green
yellow */
Naturally, you could apply this approach for non-debugging purposes, but I wouldn't recommend it.
Related
I've got an array of fonts which each have a familyName and a fontName.
I would like to transform them into an array of tuples in the form (familyName: String, fontNames: [String]).
I feel like there should be an easy functional way to do this, but can't work it out. The closest I've got is two calls to reduce: First into a dictionary and then into an array.
let dictionary = fonts.reduce(into [String : [String]]() ) { result, font in
let array = result[font.fontFamily] ?? []
result[fontFamily] = array + [font.fontName]
}
let array = dictionary(into: [(String, [String])]() ) { result, element in
result.append( (element.key, element.value.sorted()) )
}.sorted { $0.0 < $1.0 }
I'm also sorting the array of tuples and the array of fontNames in the array of tuples.
Is there a way I can avoid the intermediary dictionary?
Many thanks.
Update
I created a playground to show sanjaykmwt the results of their suggestions:
struct Font {
let family: String
let name: String
}
let fonts = [
Font(family: "ABC", name: "abc"),
Font(family: "ABC", name: "def"),
Font(family: "ABC", name: "ghi"),
Font(family: "XYZ", name: "xyz"),
Font(family: "XYZ", name: "uvw")
]
let sortedFamily = fonts.sorted(by: { (lhs, rhs) -> Bool in
return lhs.family < rhs.family
})
let dict = sortedFamily.map({["family":$0.family,
"fonts":$0.name]})
print("dict: \(dict)")
Output:
dict: [["family": "ABC", "fonts": "abc"], ["family": "ABC", "fonts": "def"], ["family": "ABC", "fonts": "ghi"], ["family": "XYZ", "fonts": "xyz"], ["family": "XYZ", "fonts": "uvw"]]
if You have an array of Fonts with fontFamily, fontName
you can make grouping then map
// Array Of Fonts Example
let array = [Font.init(fontFamily: "Cago", fontName: "AA"),
Font.init(fontFamily: "Cago", fontName: "CCCC"),
Font.init(fontFamily: "Mango", fontName: "AAsss"),
Font.init(fontFamily: "Mango", fontName: "mngoo")]
// Grouping
let groupedByFamilayName = Dictionary.init(grouping: array) {$0.fontFamily}
// Map
let arrayOfTuple = groupedByFamilayName.map { (key,array) -> (String,[String]) in
return (key,array.map({$0.fontName}))
}
print(arrayOfTuple)
Expanding (or contracting!) on Abdelahad Darwish's answer…
let tuples = Dictionary(grouping: fonts) { $0.family }
.map { (familyName: $0.key, fontNames: $0.value.map { $0.name }) }
print(tuples)
[(familyName: "XYZ", fontNames: ["xyz", "uvw"]), (familyName: "ABC", fontNames: ["abc", "def", "ghi"])]
let sortedFamily = fonts.sorted(by: { (lhs, rhs) -> Bool in
return lhs.family < rhs.family
})
let dict = sortedFamily.map({["family":$0.family,"fonts":$0.fonts.sorted()]})
try and print the dict you will get everything sorted
if you want even shorter it can be:
let dict = fonts.sorted(by: { (lhs, rhs) -> Bool in
return lhs.family < rhs.family
}).map({["family":$0.family,"fonts":$0.fonts.sorted()]})
I've two cases where I need to convert strings to different formats.
for ex:
case 1:
string inputs: abc, xyz, mno, & llr // All Strings from a dictionary
output: ["abc","xyz", "mno", "llr"] //I need to get the String array like this.
But when I use this code:
var stringBuilder:[String] = [];
for i in 0..<4 {
stringBuilder.append("abc"); //Appends all four Strings from a Dictionary
}
print(stringBuilder); //Output is 0: abc, 1:xyz like that, how to get desired format of that array like ["abc", "xyz"];
Real usage:
let arr = Array(stringReturn.values);
//print(arr) // Great, it prints ["abc","xyz"];
let context = JSContext()
context?.evaluateScript(stringBuilder)
let testFunction = context?.objectForKeyedSubscript("KK")
let result = testFunction?.call(withArguments:arr); // Here when I debugger enabled array is passed to call() like 0:"abc" 1:"xyz". where as it should be passed as above print.
Secondly how to replace escape chars in swift: I used "\" in replaceOccurances(of:"\\'" with:"'"); but its unchanged. why and how to escape that sequnce.
case 2:
string input: \'abc\'
output: 'abc'
To get all values of your dictionary as an array you can use the values property of the dictionary:
let dictionary: Dictionary<String, Any> = [
"key_a": "value_a",
"key_b": "value_b",
"key_c": "value_c",
"key_d": "value_d",
"key_e": 3
]
let values = Array(dictionary.values)
// values: ["value_a", "value_b", "value_c", "value_d", 3]
With filter you can ignore all values of your dictionary that are not of type String:
let stringValues = values.filter({ $0 is String }) as! [String]
// stringValues: ["value_a", "value_b", "value_c", "value_d"]
With map you can transform the values of stringValues and apply your replacingOccurrences function:
let adjustedValues = stringValues.map({ $0.replacingOccurrences(of: "value_", with: "") })
// adjustedValues: ["a", "b", "c", "d"]
Why not try something like this? For part 1 of the question that is:
var stringReturn: Dictionary = Dictionary<String,Any>()
stringReturn = ["0": "abc","1": "def","2": "ghi"]
print(stringReturn)
var stringBuilder = [String]()
for i in stringReturn {
stringBuilder.append(String(describing: i.value))
}
print(stringBuilder)
Also, part 2 seems to be trivial unless I'm not mistaken
var escaped: String = "\'abc\'"
print(escaped)
case 1:
I have Implemented this solutions, Hope this will solve your problem
let dict: [String: String] = ["0": "Abc", "1": "CDF", "2": "GHJ"]
var array: [String] = []
for (k, v) in dict.enumerated() {
print(k)
print(v.value)
array.append(v.value)
}
print(array)
case 2:
var str = "\'abc\'"
print(str.replacingOccurrences(of: "\'", with: ""))
I have an array, of type String:
var Arr = ["apple", "banana", "orange", "grapes", "yellow banana", "urban"]
How do I filter every word in array that has a prefix of my keyword?
Now I have this:
.filter { $0.contains(keyword) }
.sorted { ($0.hasPrefix(keyword) ? 0 : 1) < ($1.hasPrefix(keyword) ? 0 : 1) }
But if I have keyword "ban", it will return "banana", "yellow banana", and "urban".
I need only to filter prefix of every word in array element, to get "banana" and "yellow banana".
You can use a regular expression which checks if the keyword
occurs at a word boundary (\b pattern):
let array = ["apple", "banana", "orange", "grapes", "yellow banana", "urban"]
let keyword = "ban"
let pattern = "\\b" + NSRegularExpression.escapedPattern(for: keyword)
let filtered = array.filter {
$0.range(of: pattern, options: .regularExpression) != nil
}
print(filtered) // ["banana", "yellow banana"]
And for a case-insensitive search use
options: [.regularExpression, .caseInsensitive]
instead.
Performance Note
I just want to refer to the Time-Complexity caused by filtering the array for matching.
For example :
private var words: [String]
func words(matching prefix: String) -> [String]
{
return words.filter { $0.hasPrefix(prefix) }
}
words(matching:) will go through the collection of strings and return
the strings that match the prefix.
If the number of elements in the words array is small, this is a
reasonable strategy. But if you’re dealing with more than a few
thousand words, the time it takes to go through the words array will
be unacceptable. The time complexity of words(matching:) is O(k*n),
where k is the longest string in the collection, and n is the number of
words you need to check.
Trie data structure has excellent performance characteristics for
this type of problem.
Reference : https://www.raywenderlich.com/892-swift-algorithm-club-swift-trie-data-structure
You will need to first break up your string into words using enumerateSubstrings method and then you can check if any of the words contains the keyword prefix:
extension String {
var words: [String] {
var words: [String] = []
enumerateSubstrings(in: startIndex..<endIndex, options: .byWords) { word,_,_,_ in
guard let word = word else { return }
words.append(word)
}
return words
}
}
let arr = ["apple", "banana", "orange", "grapes", "yellow banana", "urban"]
let keyword = "ban"
let filtered = arr.filter { $0.words.contains(where: {$0.hasPrefix(keyword)}) }
filtered // ["banana", "yellow banana"]
Alternatively in Swift 3+:
let array = ["apple", "banana", "orange", "grapes", "yellow banana", "urban"]
let keyword = "ban"
let filtered = array.filter {
$0.components(separatedBy: " ").first { $0.hasPrefix(keyword) } != nil
}
print(filtered) // ["banana", "yellow banana"]
You can filter array with hasPrefix as follows:
// I Use two arrays, actualArray with original data and filteredArray contains data after filtration. You can use whatever is best for your scenario
filteredArray = actaulArray.filter({ $0.hasPrefix(searchBar.text!) })
say I have a variable
let stringArray = "[\"You\", \"Shall\", \"Not\", \"PASS!\"]"
// if I show this, it would look like this
print(stringArray)
["You", "Shall", "Not", "PASS!"]
now let's have an Array< String>
let array = ["You", "Shall", "Not", "PASS!"]
// if I convert this into string
// it would roughly be equal to the variable 'stringArray'
if String(array) == stringArray {
print("true")
} else {
print("false")
}
// output would be
true
now say what should I do to convert variable 'stringArray' to 'Array< String>'
The answer would be to convert the string using NSJSONSerialization
Thanks Vadian for that tip
let dataString = stringArray.dataUsingEncoding(NSUTF8StringEncoding)
let newArray = try! NSJSONSerialization.JSONObjectWithData(dataString!, options: []) as! Array<String>
for string in newArray {
print(string)
}
voila there you have it, it's now an array of strings
A small improvement for Swift 4.
Try this:
// Array of Strings
let array: [String] = ["red", "green", "blue"]
let arrayAsString: String = array.description
let stringAsData = arrayAsString.data(using: String.Encoding.utf16)
let arrayBack: [String] = try! JSONDecoder().decode([String].self, from: stringAsData!)
For other data types respectively:
// Set of Doubles
let set: Set<Double> = [1, 2.0, 3]
let setAsString: String = set.description
let setStringAsData = setAsString.data(using: String.Encoding.utf16)
let setBack: Set<Double> = try! JSONDecoder().decode(Set<Double>.self, from: setStringAsData!)
I have a large array and need to access it by a key (a lookup) so I need to create Dictionary. Is there a built in function in Swift 3.0 to do so, or do I need to write it myself?
First I will need it for a class with key "String" and later on maybe I will be able to write a template version for general purpose (all types of data and key).
Note for 2019. This is now simply built-in to Swift 5, uniqueKeysWithValues and similar calls.
Is that it (in Swift 4)?
let dict = Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Note:
As mentioned in the comment, using uniqueKeysWithValues would give a fatal error (Fatal error: Duplicate values for key: 'your_key':) if you have duplicated keys.
If you fear that may be your case, then you can use init(_:uniquingKeysWith:) e.g.
let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] // or `let pairsWithDuplicateKeys = array.map{ ($0.key, $0) }`
let firstValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (first, _) in first })
print(firstValues)
//prints ["a": 1, "b": 2]
let lastValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (_, last) in last })
print(lastValues)
//prints ["a": 3, "b": 4]
On Swift 4, you can achieve this by using Dictionary's grouping:by: initializer
For ex:
You have class named A
class A {
var name: String
init(name: String) {
self.name = name
}
// .
// .
// .
// other declations and implementions
}
Next, you have an array of objects of type A
let a1 = A(name: "Joy")
let a2 = A(name: "Ben")
let a3 = A(name: "Boy")
let a4 = A(name: "Toy")
let a5 = A(name: "Tim")
let array = [a1, a2, a3, a4, a5]
Let's say you want to create a Dictionary by grouping all the names by their first letter. You use Swifts Dictionary(grouping:by:) to achieve this
let dictionary = Dictionary(grouping: array, by: { $0.name.first! })
// this will give you a dictionary
// ["J": [a1], "B": [a2, a3], "T": [a4, a5]]
source
Note however that the resulting Dictionary "dictionary" is of type
[String : [A]]
it is not of type
[String : A]
as you may expect. (Use #uniqueKeysWithValues to achieve the latter.)
I think you're looking for something like this:
extension Array {
public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
var dict = [Key:Element]()
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
You can now do:
struct Person {
var name: String
var surname: String
var identifier: String
}
let arr = [Person(name: "John", surname: "Doe", identifier: "JOD"),
Person(name: "Jane", surname: "Doe", identifier: "JAD")]
let dict = arr.toDictionary { $0.identifier }
print(dict) // Result: ["JAD": Person(name: "Jane", surname: "Doe", identifier: "JAD"), "JOD": Person(name: "John", surname: "Doe", identifier: "JOD")]
If you'd like your code to be more general, you could even add this extension on Sequence instead of Array:
extension Sequence {
public func toDictionary<Key: Hashable>(with selectKey: (Iterator.Element) -> Key) -> [Key:Iterator.Element] {
var dict: [Key:Iterator.Element] = [:]
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
Do note, that this causes the Sequence to be iterated over and could have side effects in some cases.
As others already said, we need to understand which are the keys.
However I am trying to provide a solution to my interpretation of your question.
struct User {
let id: String
let firstName: String
let lastName: String
}
Here I am assuming that 2 users with the same id cannot exist
let users: [User] = ...
let dict = users.reduce([String:User]()) { (result, user) -> [String:User] in
var result = result
result[user.id] = user
return result
}
Now dict is a dictionary where the key is the user id and the value is the user value.
To access a user via its id you can now simply write
let user = dict["123"]
Update #1: General approach
Given an array of a given type Element, and a closure that determine the key of an Element, the following generic function will generate a Dictionary of type [Key:Element]
func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
return elms.reduce([Key:Element]()) { (dict, elm) -> [Key:Element] in
var dict = dict
dict[extractKey(elm)] = elm
return dict
}
}
Example
let users: [User] = [
User(id: "a0", firstName: "a1", lastName: "a2"),
User(id: "b0", firstName: "b1", lastName: "b2"),
User(id: "c0", firstName: "c1", lastName: "c2")
]
let dict = createIndex(elms: users) { $0.id }
// ["b0": {id "b0", firstName "b1", lastName "b2"}, "c0": {id "c0", firstName "c1", lastName "c2"}, "a0": {id "a0", firstName "a1", lastName "a2"}]
Update #2
As noted by Martin R the reduce will create a new dictionary for each iteration of the related closure. This could lead to huge memory consumption.
Here's another version of the createIndex function where the space requirement is O(n) where n is the length of elms.
func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
var dict = [Key:Element]()
for elm in elms {
dict[extractKey(elm)] = elm
}
return dict
}
let pills = ["12", "34", "45", "67"]
let kk = Dictionary(uniqueKeysWithValues: pills.map{ ($0, "number") })
["12": "number", "67": "number", "34": "number", "45": "number"]
swift5 swift4
The following converts an array to a dictionary.
let firstArray = [2,3,4,5,5]
let dict = Dictionary(firstArray.map { ($0, 1) } , uniquingKeysWith: +)
Swift 5
extension Array {
func toDictionary() -> [Int: Element] {
self.enumerated().reduce(into: [Int: Element]()) { $0[$1.offset] = $1.element }
}
}
This extension works for all sequences (including arrays) and lets you select both key and value:
extension Sequence {
public func toDictionary<K: Hashable, V>(_ selector: (Iterator.Element) throws -> (K, V)?) rethrows -> [K: V] {
var dict = [K: V]()
for element in self {
if let (key, value) = try selector(element) {
dict[key] = value
}
}
return dict
}
}
Example:
let nameLookup = persons.toDictionary{($0.name, $0)}
Just do it simply,
let items = URLComponents(string: "https://im.qq.com?q=13&id=23")!.queryItems!
var dic = [String: Any?]()
items.foreach {
dic[$0.name] = $0.value
}
reduce is not very suitable,
let dic: [String: Any?] = items.reduce([:]) { (result: [String: Any?], item: URLQueryItem) -> [String: Any?] in
var r = result
r[item.name] = item.value // will create an copy of result!!!!!!
return r
}
As i understand from you're question you would like to convert to Array to Dictionary.
In my case i create extension for the Array and keys for the dictionary will be indexes of the Array.
Example:
var intArray = [2, 3, 5, 3, 2, 1]
extension Array where Element: Any {
var toDictionary: [Int:Element] {
var dictionary: [Int:Element] = [:]
for (index, element) in enumerate() {
dictionary[index] = element
}
return dictionary
}
}
let dic = intArray.toDictionary
Compatible with Swift 5 Standard Library (Xcode 10.2+ , iOS 12.2).
Here's an example of usage of an initializer init(uniqueKeysWithValues:)
The input let array: [String] = Locale.isoRegionCodes is an array of ISO31661-2 codes represented by a string.
let countryCodeAndName: [String: String] = Dictionary(uniqueKeysWithValues: Locale.isoRegionCodes.map { ($0, Locale.current.localizedString(forRegionCode: $0) ?? "")} )
Returned dictionary, will list all regions with ISO31661-2 code as a key and a localized region name as a value.
Output:
...
"PL":"Poland"
"DE":"Germany"
"FR":"France"
"ES":"Spain"
...
Example 2:
let dictionary: [String: String] = Dictionary(uniqueKeysWithValues: [ ("key1", "value1"), ("key2", "value2")] )
Output:
["key1": "value1", "key2": "value2"]
Important:
Precondition: The sequence must not have duplicate keys.
Code below will crash an app:
let digitWords = ["one", "two", "three", "four", "five", "five"]
let wordToValue = Dictionary(uniqueKeysWithValues: zip(digitWords, 1...6))
with:
Fatal error: Duplicate values for key: 'five'
If you want to follow the pattern set out by map and reduce in swift you could do something nice and functional like this:
extension Array {
func keyBy<Key: Hashable>(_ keyFor: (Element) -> Key) -> [Key: Element] {
var ret = [Key: Element]()
for item in self{
ret[keyFor(item)] = item
}
return ret
}
}
Usage:
struct Dog {
let id: Int
}
let dogs = [Dog(id: 1), Dog(id: 2), Dog(id: 3), Dog(id: 4)]
let dogsById = dogs.keyBy({ $0.id })
// [4: Dog(id: 4), 1: Dog(id: 1), 3: Dog(id: 3), 2: Dog(id: 2)]
Swift way:
extension Sequence {
func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key: Element] {
reduce(into: [:]) { $0[selectKey($1)] = $1 }
}
}
// let arr = [Person(id: 1, name: "Alan")]
// arr.toDictionary { $0.id }
// ==
// [1: Person(id: 1, name: "Alan")]