How to parse data effectively - ios

I've a data coming from api which is in the form
"A,B,C,D,E\n
17945,10091,10088,3907,10132\n
2,12,13,48,11"
in the above form.
The meaning of data is A is mapped to 17945 and 2 (A->17945->2) similarly for others. I want to save this data on my model array
struct DataModel {
var name : String
var id1 : String
var id2 : String
}
The question is how do I do this effectively.
What Im thinking is splitting the the data from api , creating arrays respectively and initialising the data model accordingly, but is there a another way to do this using dictionaries , it is not neccassary to use model here , I just need all the respective data in one go.

Well if you want to create data from this raw data, you can do as like below
let names = ["A","B","C","D","E"]
let element2 = [17945,10091,10088,3907,10132]
let element3 = [2,12,13,48,11]
struct DataModel {
var name : String
var id1 : String
var id2 : String
}
var allElements: [DataModel] = []
for i in zip(names, zip(element2, element3)) {
let model = DataModel(name: i.0, id1: i.1.0.description, id2: i.1.1.description)
allElements.append(model)
}

I don't know if it is what you need, but one of the variants how to
struct DataModel {
var name: String
var id1: String
var id2: String
public init(n: String, i1: String, i2: String) {
name = n
id1 = i1
id2 = i2
}
}
let numberOfArrays = string.components(separatedBy: "\n").count
let aString = string.replacingOccurrences(of: "\n", with: ",").components(separatedBy: ",")
let step = aString.count / numberOfArrays
for i in 0..<step {
allElements.append(DataModel(n: aString[i], i1: aString[i + step], i2: aString[i + step * 2]))
}
UPDATE:
here is else one more variant with dictionary result
private func parse() {
var dictionary: [Int: String] = [:]
_ = inputString
.components(separatedBy: "\n")
.compactMap { $0.components(separatedBy: ",") }
.compactMap { arr in
arr
.enumerated()
.compactMap { index, element in
var str = dictionary[index] ?? ""
str += element
dictionary[index] = str
}
}
}
where dictionary.values() will be desired elements

Related

Dictionary of Dictionaries of Dictionaries of Dictionaries syntax understanding

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.

Swift 3: sum value with group by of an array of objects

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

Generating an array of keyed arrays: More efficient way?

I want to sort an array of performers so that they are grouped by the first character of their first name. So for example 'A' in the following output, is for a collection of performers who's first name starts with 'A'.
[
"A"[Performer,Performer,Performer,Performer]
"B"[Performer,Performer,Performer]
"C"[Performer,Performer,Performer]
"D"[Performer,Performer,Performer]
"F"[Performer,Performer,Performer]
"M"[Performer,Performer,Performer]
... etc
]
I have achieved it but I'm hoping there is a more trivial way to do it. The following is how I achieved it.
class Performer {
let firstName: String
let lastName: String
let dateOfBirth: NSDate
init(firstName: String, lastName: String, dateOfBirth: NSDate) {
self.firstName = firstName
self.lastName = lastName
self.dateOfBirth = dateOfBirth
}
}
private var keyedPerformers: [[String: [Performer]]]?
init() {
super.init()
let performers = generatePerformers()
let sortedPerformers = performers.sort { $0.0.firstName < $0.1.firstName }
keyedPerformers = generateKeyedPerformers(sortedPerformers)
}
//'sortedPerformers' param must be sorted alphabetically by first name
private func generateKeyedPerformers(sortedPerformers: [Performer]) -> [[String: [Performer]]] {
var collectionKeyedPerformers = [[String: [Performer]]]()
var keyedPerformers = [String: [Performer]]()
var lastLetter: String?
var collection = [Performer]()
for performer in sortedPerformers {
let letter = String(performer.firstName.characters.first!)
if lastLetter == nil {
lastLetter = letter
}
if letter == lastLetter {
collection.append(performer)
} else {
keyedPerformers[lastLetter!] = collection
collectionKeyedPerformers.append(keyedPerformers)
keyedPerformers = [String: [Performer]]()
collection = [Performer]()
}
lastLetter = letter
}
return collectionKeyedPerformers.sort { $0.0.keys.first! < $0.1.keys.first! }
}
First of all, since you have an (sorted) array of section index titles it's not necessary to use an array of dictionaries. The most efficient way is to retrieve the array of Performers from the dictionary by key.
Add a lazy instantiated variable initialLetter in the Performer class. I added also the description property to get a more descriptive string representation.
class Performer : CustomStringConvertible{
let firstName: String
let lastName: String
let dateOfBirth: NSDate
init(firstName: String, lastName: String, dateOfBirth: NSDate) {
self.firstName = firstName
self.lastName = lastName
self.dateOfBirth = dateOfBirth
}
lazy var initialLetter : String = {
return self.firstName.substringToIndex(self.firstName.startIndex.successor())
}()
var description : String {
return "\(firstName) \(lastName)"
}
To create the performers use an array rather than a simple string. The "missing" letters are already omitted.
private let createIndexTitles = ["A","B","C","D","E","F","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
The function to create the dummy instances pre-sorts the arrays of performers by initialLetter and then by lastName
private func generatePerformers() -> [Performer] {
var performers = [Performer]()
for i in 0...99 {
let x = i % createIndexTitles.count
let letter = createIndexTitles[x]
performers.append(Performer(firstName: "\(letter)", lastName: "Doe", dateOfBirth: NSDate()))
}
return performers.sort({ (p1, p2) -> Bool in
if p1.initialLetter < p2.initialLetter { return true }
return p1.lastName < p2.lastName
})
}
Now create an empty array of strings for the section title indexes
private var sectionIndexTitles = [String]()
The function to create the keyed performers follows a simple algorithm : If the key for initialLetter doesn't exist, create it. Then append the performer to the array. At the end assign the sorted keys of the dictionary to sectionIndexTitles, the elements of the keyed arrays are already sorted.
private func generateKeyedPerformers(sortedPerformers: [Performer]) -> [String: [Performer]] {
var keyedPerformers = [String: [Performer]]()
for performer in sortedPerformers {
let letter = performer.initialLetter
var keyedPerformer = keyedPerformers[letter]
if keyedPerformer == nil {
keyedPerformer = [Performer]()
}
keyedPerformer!.append(performer)
keyedPerformers[letter] = keyedPerformer!
}
sectionIndexTitles = Array(keyedPerformers.keys).sort { $0 < $1 }
return keyedPerformers
}
Now test it
let performers = generatePerformers()
let keyedPerformers = generateKeyedPerformers(performers)
print(sectionIndexTitles , keyedPerformers)
In the (presumably) table view use sectionIndexTitles as the section array and get the performer arrays by key respectively.

Create dictionary from an array of objects using property of object as key for the dictionary?

With Swift is it possible to create a dictionary of [String:[Object]] from an array of objects [Object] using a property of those objects as the String key for the dictionary using swift's "map"?
class Contact:NSObject {
var id:String = ""
var name:String = ""
var phone:String = ""
init(id:String, name:String, phone:String){
self.id = id
self.name = name
self.phone = phone
}
}
var contactsArray:[Contact]
var contactsDict:[String:Contact]
contactsDict = (contactsArray as Array).map { ...WHAT GOES HERE... }
Let's say you want to use id as the key for the dictionary:
var contactsArray = [Contact]()
// add to contactsArray
var contactsDict = [String: Contact]()
contactsArray.forEach {
contactsDict[$0.id] = $0
}
The difference between map and forEach is that map returns an array. forEach doesn't return anything.
You can achieve this via reduce in a one-line functional-style code:
let contactsDict = contactsArray.reduce([String:Contact]()) { var d = $0; d[$1.id] = $1; return d; }
This also keeps contactsDict immutable, which is the preferred way to handle variables in Swift.
Or, if you want to get fancy, you can overload the + operator for dictionaries, and make use of that:
func +<K,V>(lhs: [K:V], rhs: Contact) -> [K:V] {
var result = lhs
result[rhs.0] = rhs.1
return result
}
let contactsDict = contacts.reduce([String:Contact]()) { $0 + ($1.id, $1) }
Swift 4
There's now a direct way to do this:
https://developer.apple.com/documentation/swift/dictionary/3127163-init
It's an initializer for Dictionary that lets you return a string key for each element in a Collection that specifies how it should be grouped in the resulting Dictionary.

How can I merge two arrays into a dictionary?

I have 2 arrays:
var identic = [String]()
var linef = [String]()
I've appended them with data. Now for usability purposes my goal is to combine them all into a dictionary with the following structure
FullStack = ["identic 1st value":"linef first value", "identic 2nd value":"linef 2nd value"]
I've been browsing around the net and couldnt find a viable solution to this.
Any ideas are greatly appreciated.
Thank you!
Starting with Xcode 9.0, you can simply do:
var identic = [String]()
var linef = [String]()
// Add data...
let fullStack = Dictionary(uniqueKeysWithValues: zip(identic, linef))
If your keys are not guaranteed to be unique, use this instead:
let fullStack =
Dictionary(zip(identic, linef), uniquingKeysWith: { (first, _) in first })
or
let fullStack =
Dictionary(zip(identic, linef), uniquingKeysWith: { (_, last) in last })
Documentation:
https://developer.apple.com/documentation/swift/dictionary/init(uniquekeyswithvalues:)
https://developer.apple.com/documentation/swift/dictionary/init(_:uniquingkeyswith:)
Use enumerated():
var arrayOne: [String] = []
var arrayTwo: [String] = []
var dictionary: [String: String] = [:]
for (index, element) in arrayOne.enumerated() {
dictionary[element] = arrayTwo[index]
}
The pro approach would be to use an extension:
extension Dictionary {
public init(keys: [Key], values: [Value]) {
precondition(keys.count == values.count)
self.init()
for (index, key) in keys.enumerate() {
self[key] = values[index]
}
}
}
Edit: enumerate() → enumerated() (Swift 3 → Swift 4)
A slightly different method, which doesn't require the arrays to be of the same length, because the zip function will safely handle that.
extension Dictionary {
init(keys: [Key], values: [Value]) {
self.init()
for (key, value) in zip(keys, values) {
self[key] = value
}
}
}
If you'd like to be safer and ensure you're picking the smaller array count each time (so that you're not potentially crashing if the second array is smaller than the first), then do something like:
var identic = ["A", "B", "C", "D"]
var linef = ["1", "2", "3"]
var Fullstack = [String: String]()
for i in 0..<min(linef.count, identic.count) {
Fullstack[identic[i]] = linef[i]
}
print(Fullstack) // "[A: 1, B: 2, C: 3]"
This is a generic solution
func dictionaryFromKeysAndValues<K : Hashable, V>(keys:[K], values:[V]) -> Dictionary<K, V>
{
assert((count(keys) == count(values)), "number of elements odd")
var result = Dictionary<K, V>()
for i in 0..<count(keys) {
result[keys[i]] = values[i]
}
return result
}
var identic = ["identic 1st value", "identic 2nd value", "identic 3rd value"]
var linef = ["linef 1st value", "linef 2nd value", "linef 3rd value"]
let mergedDictionary = dictionaryFromKeysAndValues(identic, linef)
Here is a extension that combines some of the previous answers and accepts all Sequences, not only Arrays.
public extension Dictionary {
init<K: Sequence, V: Sequence>(keys: K, values: V) where K.Element == Key, V.Element == Value, K.Element: Hashable {
self.init()
for (key, value) in zip(keys, values) {
self[key] = value
}
}
}
That extension doesn't require the sequences to be the same lengths. If you want that, here is an extension with assertions.
public extension Dictionary {
init<K: Sequence, V: Sequence>(keys: K, values: V) where K.Element == Key, V.Element == Value, K.Element: Hashable {
self.init()
var keyIterator = keys.makeIterator()
for value in values {
let key = keyIterator.next()
assert(key != nil, "The value sequence was longer.")
self[key!] = value
}
assert(keyIterator.next() == nil, "The key sequence was longer.")
}
}

Resources