How do I iterate through a [String:[String]] Dictionary in Swift - dictionary

I have a dictionary.
var params: [String: [String]] = [:]
I assign an array to the first key and the first key only. Now print params prints :
["names" : ["jack", "joe", "jill"]]
How do I iterate through this array at this given key so that I could loop through and print jack, joe, and jill?

for (key, names) in params {
for name in names {
print("\(name))
}
}

var params: [String: [String]] = ["names" : ["jack", "joe", "jill"]]
for key in params.keys {
NSLog("%#", key)
let list = params[key]
for obj in list! {
NSLog("%#", obj)
}
}
Using tuples:
let params: [String: [String]] = ["names" : ["jack", "joe", "jill"]]
for (key, names) in params {
print("\(key)")
for name in names {
print("\(name)")
}
}

Related

Kotlin - group elements by a key under some conditions with new value type

I'm trying to find a way to use Kotlin collection operation to do some logic that I'm going to explain:
Let's say type Classroom contains a list of Student as a field in it, eg. classroom.getSudents() returns a list of certain studends.
Now I have a list of mixed Student that I need to group by one of its fields say major, and the value of the resultant map to be Classroom.
So I need to convert List<Student> to Map<Student.major, Classroom>
Also at some cases of major, for example for all major == chemistry, I'll need to group by another criteria, say firstname, so the keys of major chemistry would be major_firstname
Here's an example, I have a list of Student(major, firstname):
[
Student("chemistry", "rafael"),
Student("physics", "adam"),
Student("chemistry", "michael"),
Student("math", "jack"),
Student("chemistry", "rafael"),
Student("biology", "kevin")
]
I need the result to be:
{
"math" -> Classroom(Student("math", "jack")),
"physics" -> Classroom(Student("physics", "adam")),
"chemistry_michael" -> Classroom(Student("chemistry", "michael")),
"chemistry_rafael" -> Classroom(Student("chemistry", "rafael"), Student("chemistry", "rafael")),
"biology" -> Classroom(Student("biology", "kevin"))
}
I've tried groupBy, flatMapTo and associateBy but as far as I understand all of these doesn't group by a certain condition.
I will try to answer the 1st part as Roland posted an answer for the 2nd part (although I did not try it).
Assuming your classes are:
class Student(val major: String, val firstName: String)
class Classroom(val studentList: MutableList<Student>) {
fun getStudents(): MutableList<Student> {
return studentList
}
}
and with an initialization like:
val list = mutableListOf<Student>(
Student("chemistry", "rafael"),
Student("physics", "adam"),
Student("chemistry", "michael"),
Student("math", "jack"),
Student("chemistry", "rafael"),
Student("biology", "kevin"))
val classroom = Classroom(list)
val allStudents = classroom.getStudents()
you can have a result list:
val finalList: MutableList<Pair<String, Classroom>> = mutableListOf()
allStudents.map { it.major }.distinctBy { it }.forEach { major ->
finalList.add(major to Classroom(allStudents.filter { it.major == major }.toMutableList()))
}
so by the below code:
finalList.forEach {
println(it.first + "->")
it.second.getStudents().forEach { println(" " + it.major + ", " + it.firstName) }
}
this will be printed:
chemistry->
chemistry, rafael
chemistry, michael
chemistry, rafael
physics->
physics, adam
math->
math, jack
biology->
biology, kevin
It's actually the mixture of those methods which you require. There are also other ways to achieve it, but here is one possible example using groupBy and flatMap:
val result = students.groupBy { it.major }
.flatMap { (key, values) -> when (key) {
"chemistry" -> values.map { it.firstname }
.distinct()
.map { firstname -> "chemistry_$firstname" to ClassRoom(values.filter { it.firstname == firstname }) }
else -> listOf(key to ClassRoom(values))
}
}.toMap()
Assuming the following data classes:
data class Student(val major: String, val firstname: String)
data class ClassRoom(val students : List<Student>)
If you also want a map with all students grouped by major, the following suffices:
val studentsPerMajor = students.groupBy { it.major }
.map { (major, values) -> major to ClassRoom(values) }
If you then rather want to continue working with that map instead of recalculating everything from the source, it's also possible, e.g. the following will then return your desired map based on the studentsPerMajor:
val result = studentsPerMajor.flatMap { (key, classroom) -> when (key) {
"chemistry" -> classroom.students.map { it.firstname }
.distinct()
.map { firstname -> "chemistry_$firstname" to ClassRoom(classroom.students.filter { it.firstname == firstname }) }
else -> listOf(key to classroom)
}
}.toMap()

Firestore update array field in a Document

I have a document like below, in my document, have a Array . in array have Objects. How can i update new object to an Array.
As you see below i can add document in a colletion wit an array, but when i tried to update it gives error
java.lang.IllegalArgumentException: Invalid data. Unsupported type: com.test.data.modal.Product
What i tried;
var pid = ""
btnAdd.setOnClickListener {
val list = ArrayList<Product>()
list.add(Product("u1", "1", 1))
list.add(Product("u2", "2", 1))
list.add(Product("u3", "3", 1))
val testObject = TestObject("Talha", "Kosen", list)
FirebaseFirestore.getInstance().collection("Test")
.add(testObject)
.addOnCompleteListener { task ->
pid = task.result.id
}
}
btnUpdate.setOnClickListener {
val list = ArrayList<Product>()
list.add(Product("u1", "1", 1))
list.add(Product("u2", "2", 1))
list.add(Product("u3", "3", 1))
list.add(Product("u4", "4", 4))
FirebaseFirestore.getInstance()
.collection("Test")
.document(pid)
.update("product", list)
}
document:
POJO:
#IgnoreExtraProperties
#Keep
class TestObject {
var name: String = ""
var surname: String = ""
lateinit var productList: List<Product>
constructor()
constructor(name: String, surname: String, productList: List<Product>) {
this.name = name
this.surname = surname
this.productList = productList
}
}
#IgnoreExtraProperties
#Keep
class Product {
var imagePath: String = ""
var productUrl: String = ""
var brand: Int = 0
constructor()
constructor(imagePath: String, productUrl: String, brand: Int) {
this.imagePath = imagePath
this.productUrl = productUrl
this.brand = brand
}
}
I have probably not the answer but this could help :
You try to update with an ArrayList but you store a List (in your TestObject)
Refer to the documentation to update fields in nested objects https://firebase.google.com/docs/firestore/manage-data/add-data#update_fields_in_nested_objects
It could be a good idea to store a collection in your TestObject document instead of a list ? You'll able to update easily your fields

Range of all linkAttributed String in MutableAttributedString in swift

I have a mutableAttributedString in which few strings are linkAttributed,I want to find the Range of all link attributed string. How to do that in swift3 ?
When user start type # in textview i show list of few name. If user select any row then following method gets called.
func didSelectMemberId(_ model: BaseModel) {
var fullName = ""
if model.entityType == ReceiverType.Active.rawValue{
fullName = model.name
}else{
fullName = AtMention + model.name + " " + model.name2
}
let attributedString = NSMutableAttributedString(string:fullName, attributes:[NSFontAttributeName:(appNeedsAutoResize ? (UIUtils.getFontForApproprieteField(.headlineWithoutBold).font) : UIFont.systemFont(ofSize: 14))])
attributedString.addAttribute(NSLinkAttributeName, value: "connectmention://\(model.entityId.stringValue())", range: NSRange(location: 0, length: fullName.length))
attributedString.append(NSAttributedString(string: emptySpaceStringByUC, attributes:[NSFontAttributeName:(appNeedsAutoResize ? (UIUtils.getFontForApproprieteField(.headlineWithoutBold).font) : UIFont.systemFont(ofSize: 14))]))
self.composeBar.textView.textStorage.insert(attributedString, at:self.composeBar.textView.selectedRange.location)
}
self.composeBar.textView.selectedRange = NSMakeRange(self.composeBar.textView.selectedRange.location+fullName.length, 0 )
To get the link proprty I am using the following method
func getlinkActionRange(attributeString: NSAttributedString) -> [MentionStruct] {
var arrMentions = [MentionStruct]()
_ = attributeString.enumerateAttribute(NSLinkAttributeName, in: NSRange.init(location: 0, length: attributeString.length), options: [], using: { (value, range, stop) in
if let url = value {
let occurrence = (attributeString.string as NSString).substring(with:range)
arrMentions.append(MentionStruct(link: url as! String, text: occurrence, range: range))
}
})
return arrMentions
}
If user type anything after inserting that name , that type string also coming.

Dictionary contains a certain value swift 3

I want to check if a string exists in any of the values in my Dictionary
Dictionary<String, AnyObject>
I know arrays has .contains so I would think a dictionary does too. Xcode tells me to use the following when I start typing contains
countDic.contains(where: { ((key: String, value: AnyObject)) -> Bool in
<#code#>
})
I just don't understand how to use this I know inside I need to return a Bool, but I don't understand where I put what String I'm looking for. Any help would be great.
contains(where:) checks if any element of the collection satisfies
the given predicate, so in your case it would be
let b = countDic.contains { (key, value) -> Bool in
value as? String == givenString
}
or, directly applied to the values view of the dictionary:
let b = countDic.values.contains { (value) -> Bool in
value as? String == givenString
}
In both cases it is necessary to (optionally) cast the AnyObject
to a String in order to compare it with the given string.
It would be slightly easier with a dictionary of type
Dictionary<String, String> because strings are Equatable,
and the contains(element:) method can be used:
let b = countDic.values.contains(givenString)
Since your values are AnyObject – Any in Swift 3 - you have to check if the value is a string. If yes check if the value contains the substring.
let countDic : [String:Any] = ["alpha" : 1, "beta" : "foo", "gamma" : "bar"]
countDic.contains { (key, value) -> Bool in
if let string = value as? String { return string.contains("oo") }
return false
}
However if you want to check if any of the values is equal to (rather than contains) a string you could use also the filter function and isEmpty
!countDic.filter { (key, value) -> Bool in
value as? String == "foo"
}.isEmpty
You may need to learn basic usage of contains(where:) for Dictionarys first:
For [String: Int]:
let myIntDict1: [String: Int] = [
"a" : 1,
"b" : 101,
"c" : 2
]
let myIntDict1ContainsIntGreaterThan100 = myIntDict1.contains {
key, value in //<- `value` is inferred as `Int`
value > 100 //<- true when value > 100, false otherwise
}
print(myIntDict1ContainsIntGreaterThan100) //->true
For [String: String]:
let myStringDict1: [String: String] = [
"a" : "abc",
"b" : "def",
"c" : "ghi"
]
let myStringDict1ContainsWordIncludingLowercaseE = myStringDict1.contains {
key, value in //<- `value` is inferred as `String`
value.contains("e") //<- true when value contains "e", false otherwise
}
print(myStringDict1ContainsWordIncludingLowercaseE) //->true
So, with [String: AnyObject]:
let myAnyObjectDict1: [String: AnyObject] = [
"a" : "abc" as NSString,
"b" : 101 as NSNumber,
"c" : "ghi" as NSString
]
let myAnyObjectDict1ContainsWordIncludingLowercaseE = myAnyObjectDict1.contains {
key, value in //<- `value` is inferred as `AnyObject`
//`AnyObject` may not have the `contains(_:)` method, so you need to check with `if-let-as?`
if let stringValue = value as? String {
return value.contains("e") //<- true when value is a String and contains "e"
} else {
return false //<- false otherwise
}
}
print(myAnyObjectDict1ContainsWordIncludingLowercaseE) //->false
So, in your case:
let countDic: [String: AnyObject] = [
"a" : 1 as NSNumber,
"b" : "no" as NSString,
"c" : 2 as NSNumber
]
let countDicContainsString = countDic.contains {
key, value in //<- `value` is inferred as `AnyObject`
value is String //<- true when value is a String, false otherwise
}
print(countDicContainsString) //->true

Swift Dictionary Filter

So it looks like the filter function on a Swift (2.x) dictionary returns a tuple array. My question is there an elegant solution to turning it back into a dictionary? Thanks in advance.
let dictionary: [String: String] = [
"key1": "value1",
"key2": "value2",
"key3": "value3"
]
let newTupleArray: [(String, String)] = dictionary.filter { (tuple: (key: String, value: String)) -> Bool in
return tuple.key != "key2"
}
let newDictionary: [String: String] = Dictionary(dictionaryLiteral: newTupleArray) // Error: cannot convert value of type '[(String, String)]' to expected argument type '[(_, _)]'
If you are looking for a more functional approach:
let result = dictionary.filter {
$0.0 != "key2"
}
.reduce([String: String]()) { (var aggregate, elem) in
aggregate[elem.0] = elem.1
return aggregate
}
reduce here is used to construct a new dictionary from the filtered tuples.
Edit: since var parameters has been deprecated in Swift 2.2, you need to create a local mutable copy of aggregate:
let result = dictionary.filter {
$0.0 != "key2"
}
.reduce([String: String]()) { aggregate, elem in
var newAggregate = aggregate
newAggregate[elem.0] = elem.1
return newAggregate
}
You can extend Dictionary so that it takes a sequence of tuples as initial values:
extension Dictionary {
public init<S: SequenceType where S.Generator.Element == (Key, Value)>(_ seq: S) {
self.init()
for (k, v) in seq { self[k] = v }
}
}
and then do
let newDictionary = Dictionary(newTupleArray)

Resources