I need to map the doors of buildings into a single map, from which afterward I need to populate three pickers using this data.
Each door contains the following data: building, level, range, door number and other information that is less relevant. So I created the following map:
public var doorsMap: [String : [String : [String : [String: Door]]]] = [:]
and a have a list of doors that I need to populate this map with, the problem is that I can't understand what should be the right syntax to perform this task, I tried:
doorsMap[door.building]?[door.level]?[door.range]?[door.number] = door
but this doesn't create the inner sets of dictionaries. when I tried to do:
doorsMap[door.building]![door.level]![door.range]![door.number] = door
Obviously, I get the:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
because I try to unwrap a nil value.
So what would be the correct syntax in swift to populate this map from a list of doors?
A single assignment won't create the multiple, intermediate directories. You need to do it explicitly.
You could use something like this:
func add(door: Door) {
var building = self.doorsMap[door.building] ?? [String : [String:[String: Door]]]()
var level = building[door.level] ?? [String : [String: Door]]()
var range = level[door.range] ?? [String:Door]
range[door.number] = door
level[door.range] = range
building[door.level] = level
self.doorsMap[door.building] = building
}
Personally, I would look for a better data structure, perhaps use a struct to hold the doorsMap. This struct could have functions to handle the insertion and retrieval of doors.
Perhaps something like this:
struct Door {
let building: String
let level: String
let range: String
let number: String
}
struct DoorMap {
private var buildingsSet = Set<String>()
private var levelsSet = Set<String>()
private var rangesSet = Set<String>()
private var numberSet = Set<String>()
private var doorsArray = [Door]()
var buildings: [String] {
get {
return Array(buildingsSet).sorted()
}
}
var levels: [String] {
get {
return Array(levelsSet).sorted()
}
}
var ranges: [String] {
get {
return Array(rangesSet).sorted()
}
}
var numbers: [String] {
get {
return Array(numberSet).sorted()
}
}
var doors: [Door] {
get {
return doorsArray
}
}
mutating func add(door: Door) {
buildingsSet.insert(door.building)
levelsSet.insert(door.level)
rangesSet.insert(door.range)
numberSet.insert(door.number)
doorsArray.append(door)
}
func doorsMatching(building: String? = nil, level: String? = nil, range: String? = nil, number: String? = nil) -> [Door]{
let matches = doorsArray.filter { (potentialDoor) -> Bool in
var included = true
if let b = building {
if potentialDoor.building != b {
included = false
}
}
if let l = level {
if potentialDoor.level != l {
included = false
}
}
if let r = range {
if potentialDoor.range != r {
included = false
}
}
if let n = number {
if potentialDoor.number != n {
included = false
}
}
return included
}
return matches
}
}
var map = DoorMap()
let d1 = Door(building: "b1", level: "1", range: "r1", number: "1")
let d2 = Door(building: "b1", level: "2", range: "r1", number: "2")
let d3 = Door(building: "b2", level: "2", range: "r1", number: "2")
map.add(door: d1)
map.add(door: d2)
map.add(door: d3)
let b1Doors = map.doorsMatching(building:"b1")
let level2Doors = map.doorsMatching(level:"2")
let allBuildings = map.buildings()
Now, maybe you have more information on buildings and levels etc, so they could be structs too instead of just strings.
Related
I have a certain string given like so..
let string = "[#he man:user:123] [#super man:user:456] [#bat man:user:789]"
Now, I need an array containing just the name and the id. For that, I applied the following regex..
extension String {
func findMentionText2() -> [[String]] {
let regex = try? NSRegularExpression(pattern: "(#\\w+(?: \\w+)*):user:(\\w+)", options: [])
if let matches = regex?.matches(in: self, options:[], range:NSMakeRange(0, self.count)) {
return matches.map { match in
return (1..<match.numberOfRanges).map {
let rangeBounds = match.range(at: $0)
guard let range = Range(rangeBounds, in: self) else {
return ""
}
return String(self[range])
}
}
} else {
return []
}
}
}
Now when I do let hashString = string.findMentionText() and print hashString, I get an array like so..
[["#he man", "123"], ["#super man", "456"], ["#bat man", "789"]]
So far so good..:)
Now I made a typealias and want to add it to an array..
So I did this...
typealias UserTag = (name: String, id: String)
var userTagList = [UserTag]()
and then,
let hashString2 = string.findMentionText2()
for unit in hashString2 {
let user: UserTag = (name: unit.first!, id: unit.last!)
userTagList.append(user)
}
for value in userTagList {
print(value.id)
print(value.name)
}
Now here, instead of giving unit.first and unit.last in let user: UserTag = (name: unit.first!, id: unit.last!), want to add the name and id to the typealias as and when they are matched from the regex..ie.when I get the name or id, it should be added to the array instead of giving unit.first or unit.last..
How can I achieve that..?
You just need to refactor your map to generate an array of UserTag instead of an array of string arrays. Here's one approach:
typealias UserTag = (name: String, id: String)
extension String {
func findMentionText2() -> [UserTag] {
let regex = try? NSRegularExpression(pattern: "(#\\w+(?: \\w+)*):user:(\\w+)", options: [])
if let matches = regex?.matches(in: self, options:[], range:NSMakeRange(0, self.count)) {
return matches.compactMap { match in
if match.numberOfRanges == 3 {
let name = String(self[Range(match.range(at: 1), in:self)!])
let id = String(self[Range(match.range(at: 2), in:self)!])
return UserTag(name: name, id: id)
} else {
return nil
}
}
} else {
return []
}
}
}
let string = "[#he man:user:123] [#super man:user:456] [#bat man:user:789]"
print(string.findMentionText2())
But I suggest you create a struct instead of using a tuple. It doesn't really change the implementation of findMentionText2 but using a struct lets you add other properties and methods as needed.
> Iam trying to insert values into dictionary . I tried 2 ways one in which the 2 variables are used and other in which i directly pass the value inside dictionary . Both are not working
for index in 0..<classes.count{
var classification = classes[index].className
var score = classes[index].score
//not working
self.classificationDictionary[classification] = score
//not working
self.classificationDictionary[classes[index].className] = classes[index].score
}
After the closure this seems to work. I dont understand how to append values into dictionary .
classificationDictionary["abc"] = 131
print("dictionary : ",classificationDictionary)
print(classificationDictionary.count)
Try this one:
classificationDictionary.updateValue(classes[index].score, forKey: classes[index].className)
you code will be :
for index in 0..<classes.count{
var classification = classes[index].className
var score = classes[index].score
classificationDictionary.updateValue(score, forKey: classification)
}
For example:
class ViewController: UIViewController {
var classes = [
AnyClass(name: "pizza", score: 2.0),
AnyClass(name: "sushi", score: 3.0),
AnyClass(name: "potatoe", score: 4.0)
]
var classificationDictionary: [String: Double]!
override func viewDidLoad() {
super.viewDidLoad()
any()
}
func any() {
classificationDictionary = [String: Double]()
for index in 0..<classes.count {
if let name = classes[index].name, let score = classes[index].score {
classificationDictionary.updateValue(score, forKey: name)
}
print(classificationDictionary)
}
}
}
code of custom class:
class AnyClass: NSObject {
var name: String?
var score: Double?
init(name: String?, score: Double?) {
if let name = name {
self.name = name
}
if let score = score {
self.score = score
}
}
}
result: Screenshot
when I filter a field other fields in the structure are not getting filtered
struct Objects {
var sectionName : String!
var sectionObjects : [String]
var sectionid:[String]!
var sectionph:[String]!
var sectionImage:[String]!
}
var objectArray = [Objects]()
var objectArrayFilter = [Objects]()
objectArrayFilter = objectArray.flatMap{
var filterObjects = $0
print(filterObjects)
filterObjects.sectionObjects = $0.sectionObjects.filter {
//print($0.sectionObjects.filter)
$0.range(of : searchBar.text!, options: .caseInsensitive) != nil
}
return filterObjects.sectionObjects.isEmpty ? nil : filterObjects
}
I need to get only sonam id but every ones id in the section is coming
.Objects(sectionName: S, sectionObjects: ["Sonam"], sectionid: ["4", "2", "5"], sectionph: ["", "8086285424", ""], sectionImage: ["", "http://www.***.com/**/images/participant/SintoSep_04_2017_09:43:482.jpg", ""])]
you say you only want the section names.. im not sure what you mean so I wrote what I think is likely...
only get section names (map structs and filter, you could keep objects if you let out the map directly)
let sectionNames = objectArray.map {
return $0.sectionName
}
let filteredItems = sectionNames.filter {
return $0.range(of : searchBar.text!, options: .caseInsensitive) != nil
}
let selectedSectionNames = filteredItems.isEmpty ?? sectionNames
I dont get why you do the flatMap at the beginning. are the sections somehow nested in that case?
I have this code in my viewController
var myArray :Array<Data> = Array<Data>()
for i in 0..<mov.count {
myArray.append(Data(...))
}
class Data {
var value :CGFloat
var name :String=""
init({...})
}
My input of Data is as:
10.5 apple
20.0 lemon
15.2 apple
45
Once I loop through, I would like return a new array as:
sum(value) group by name
delete last row because no have name
ordered by value
Expected result based on input:
25.7 apple
20.0 lemon
and nothing else
I wrote many rows of code and it is too confused to post it. I'd find easier way, anyone has a idea about this?
First of all Data is reserved in Swift 3, the example uses a struct named Item.
struct Item {
let value : Float
let name : String
}
Create the data array with your given values
let dataArray = [Item(value:10.5, name:"apple"),
Item(value:20.0, name:"lemon"),
Item(value:15.2, name:"apple"),
Item(value:45, name:"")]
and an array for the result:
var resultArray = [Item]()
Now filter all names which are not empty and make a Set - each name occurs one once in the set:
let allKeys = Set<String>(dataArray.filter({!$0.name.isEmpty}).map{$0.name})
Iterate thru the keys, filter all items in dataArray with the same name, sum up the values and create a new Item with the total value:
for key in allKeys {
let sum = dataArray.filter({$0.name == key}).map({$0.value}).reduce(0, +)
resultArray.append(Item(value:sum, name:key))
}
Finally sort the result array by value desscending:
resultArray.sorted(by: {$0.value < $1.value})
---
Edit:
Introduced in Swift 4 there is a more efficient API to group arrays by a predicate, Dictionary(grouping:by:
var grouped = Dictionary(grouping: dataArray, by:{$0.name})
grouped.removeValue(forKey: "") // remove the items with the empty name
resultArray = grouped.keys.map { (key) -> Item in
let value = grouped[key]!
return Item(value: value.map{$0.value}.reduce(0.0, +), name: key)
}.sorted{$0.value < $1.value}
print(resultArray)
First of all, you should not name your class Data, since that's the name of a Foundation class. I've used a struct called MyData instead:
struct MyData {
let value: CGFloat
let name: String
}
let myArray: [MyData] = [MyData(value: 10.5, name: "apple"),
MyData(value: 20.0, name: "lemon"),
MyData(value: 15.2, name: "apple"),
MyData(value: 45, name: "")]
You can use a dictionary to add up the values associated with each name:
var myDictionary = [String: CGFloat]()
for dataItem in myArray {
if dataItem.name.isEmpty {
// ignore entries with empty names
continue
} else if let currentValue = myDictionary[dataItem.name] {
// we have seen this name before, add to its value
myDictionary[dataItem.name] = currentValue + dataItem.value
} else {
// we haven't seen this name, add it to the dictionary
myDictionary[dataItem.name] = dataItem.value
}
}
Then you can convert the dictionary back into an array of MyData objects, sort them and print them:
// turn the dictionary back into an array
var resultArray = myDictionary.map { MyData(value: $1, name: $0) }
// sort the array by value
resultArray.sort { $0.value < $1.value }
// print the sorted array
for dataItem in resultArray {
print("\(dataItem.value) \(dataItem.name)")
}
First change your data class, make string an optional and it becomes a bit easier to handle. So now if there is no name, it's nil. You can keep it as "" if you need to though with some slight changes below.:
class Thing {
let name: String?
let value: Double
init(name: String?, value: Double){
self.name = name
self.value = value
}
static func + (lhs: Thing, rhs: Thing) -> Thing? {
if rhs.name != lhs.name {
return nil
} else {
return Thing(name: lhs.name, value: lhs.value + rhs.value)
}
}
}
I gave it an operator so they can be added easily. It returns an optional so be careful when using it.
Then lets make a handy extension for arrays full of Things:
extension Array where Element: Thing {
func grouped() -> [Thing] {
var things = [String: Thing]()
for i in self {
if let name = i.name {
things[name] = (things[name] ?? Thing(name: name, value: 0)) + i
}
}
return things.map{$0.1}.sorted{$0.value > $1.value}
}
}
Give it a quick test:
let t1 = Thing(name: "a", value: 1)
let t2 = Thing(name: "b", value: 2)
let t3 = Thing(name: "a", value: 1)
let t4 = Thing(name: "c", value: 3)
let t5 = Thing(name: "b", value: 2)
let t6 = Thing(name: nil, value: 10)
let bb = [t1,t2,t3,t4,t5,t6]
let c = bb.grouped()
// ("b",4), ("c",3) , ("a",2)
Edit: added an example with nil for name, which is filtered out by the if let in the grouped() function
I'm trying to add a search bar to the top of a grouped table. However I'm unsure how to filter through my data. The data is stored in a nested array with objects held in this format;
struct Group {
var id: String
var type: String
var desc: String
var avatar: String
var name: String
init() {
id = ""
type = ""
desc = ""
avatar = ""
name = ""
}
}
Because I get data from two sources, two arrays are nested together, this also makes it simpler to create the two sections of the grouped table. I'll note, they both use the same Group struct.
self.masterArray = [self.clientArray, self.departmentArray]
This "masterArray" is then used to populate the table. Filtering/searching a single array isn't too difficult, but how do I search through a nested array?
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
}
EDIT:
I've finally got things working, courtesy of #appzYourLife.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print("Searching for:" + searchText)
if searchText.isEmpty {
filterArray = masterArray
} else {
filterArray = [ clientArray.filter { $0.name.range(of: searchText) != nil }] + [ departmentArray.filter { $0.name.range(of: searchText) != nil } ]
}
tableView.reloadData()
}
You can make use of .flatten() to flatten your array prior to filtering it for whatever search criteria you want to use. E.g.
struct Group {
var id: String
var name: String
init(_ id: String, _ name: String) { self.id = id; self.name = name }
/* .... */
}
let clientArray = [Group("a", "John"), Group("b", "Jane"), Group("c", "Phil")]
let departmentArray = [Group("a", "Foo"), Group("b", "Bar"),
Group("c", "Baz"), Group("d", "Bax")]
let arr = [clientArray, departmentArray]
// find some id
let searchForId = "c"
let hits = arr.flatten()
.filter { $0.id.lowercaseString.containsString(searchText.lowercaseString) }
print(hits)
// [Group(id: "c", name: "Phil"), Group(id: "c", name: "Baz")]
From the edits of your questions, it seems, however, that you want the resulting filtered array to be of the same nested array type as the "master" array. In such case, the following is a more appropriate approach:
/* ... */
// find some id but keep [[Group]] type
let searchText = "c"
let hits = arr.map { $0.filter { $0.id.lowercaseString.containsString(searchText.lowercaseString) } }
print(hits)
// [[Group(id: "c", name: "Phil")], [Group(id: "cc", name: "Baz")]]
Given
struct Group {
let id: String = ""
let type: String = ""
let desc: String = ""
let avatar: String = ""
let name: String = ""
}
let clients = [Group(), Group(), Group(), Group()]
let departmens = [Group(), Group(), Group(), Group()]
let clientsAndDepartments = [clients, departmens]
You can search inside clients and department writing
let results = (clients + departmens).filter { $0.id == "123" }
Update #1
Now understand that you want to filter both arrays but as result you still want something like this [[Group]].
So here's the code
var filterArray = [clients.filter { $0.name == "White" }] + [departmens.filter { $0.name == "White" }]
Update #2
If you want to search for string inclusione the use this code
var filterArray = [ clients.filter { $0.name.rangeOfString("White") != nil }] + [ departmens.filter { $0.name.rangeOfString("White") != nil } ]
You can map over each of the arrays, and filter them independently:
self.masterArray.map{ subarray in
subarray.filter { element in
trueWhenElementShouldStay(element)
}
}
P.S. I suspect masterArray should NOT be an instance variable, it would be more appropriate as a local variable instead.