Get struct properties from string - swift - arrays

I declared a struct with 4 properties ( informationA, informationB, informationC, informationD ).
Also I've declared an array like this (The array includes some names of the my_struct properties as "strings" :
let keys = ["informationA", "informationB", "informationC"]
Now I'd like a for-loop to go through the "keys"-array and print out the struct property values for the current string ("informationA", "informationB", "informationC").
struct my_struct {
var informationA = "test"
var informationB = "test...test"
var informationC = "test...test"
var informationD = "test...test..."
}
func getInformation() {
let keys = ["informationA", "informationB", "informationC"]
for i in keys {
print(my_struct.i) // ERROR: Type 'my_struct' has no member 'i'
// should print ---> "test", "test...test", "test...test"
}
}
Using the code from above I'm getting this error ERROR: Type 'my_struct' has no member 'i'.
Is there a way to avoid this message and achieve the result I'd like to get?

What you are looking for is reflection:
struct MyStruct {
var informationA = "test"
var informationB = "test...test"
var informationC = "test...test"
var informationD = "test...test..."
}
func getInformation() {
let my_struct = MyStruct()
let keys = ["informationA", "informationB", "informationC"]
let m = Mirror(reflecting: my_struct)
let properties = Array(m.children)
for k in keys {
if let prop = properties.first(where: { $0.label == k }) {
print(prop.value)
}
}
}

Related

How to group the same element in array and sum it in Swift?

How to filter duplicate category element into different array and count their amount?
This is the format, record is from the core data.
var record = [Record]()
[<Record:...; data: {accountbook = "MyBook";
amount = "10.50";
category = "A";
id = 1;
},<Record:...; data: {accountbook = "MyBook";
amount = "5.50";
category = "B";
id = 2;
},<Record:...; data: {accountbook = "MyBook";
amount = "4.50";
category = "B";
id = 3;
}]
What I want
var category = ["A", "B"] //success
var total = [10.50, 10.00]
This is what I do for finding the category, and it works but how to group the same category and sum the total?
var category =[String]()
for categoryObject in record{
if let categoryItem = categoryObject.category{
category.append(categoryItem)
}
}
//I tried this code to group the same category but fail.
let result = Set(record).map{ category in return record.filter{$0 == category} }
Another way is this. but how if I have A-Z category? it will have very long code..Is there any way can detect the same value and split it to different array so that I can sum it by category.
categoryFilter = record.filter { $0.category!.contains("A") }
First group your record object by category like this
extension Sequence {
func group<GroupingType: Hashable>(by key: (Iterator.Element) -> GroupingType) -> [[Iterator.Element]] {
var groups: [GroupingType: [Iterator.Element]] = [:]
var groupsOrder: [GroupingType] = []
forEach { element in
let key = key(element)
if case nil = groups[key]?.append(element) {
groups[key] = [element]
groupsOrder.append(key)
}
}
return groupsOrder.map { groups[$0]! }
}
}
Then you will get your distinct arrays based on category like this
var records : [Record] = []// your objects
let distinctRecords = records.group(by: {$0. category})
Now you can use reduce to calculate sum of values of that category
for items in distinctRecords{
let sum = items.reduce(0.0){$0.0 + $1. amount ?? 0.0}// assuming you have float values in your amount
print(items)// do whatever you want to do with your distinct array
print(" \(sum)")
}
#Wan Jern I have written a piece of code, you can try this one. Hopefully, it will work.
var category = [String]()
var totalArr = [CGFloat]()
for categoryObject in record{
if let categoryItem = categoryObject.category{
if !category.contains(categoryItem) {
category.append(categoryItem)
totalArr.append(categoryObject.amount)
} else {
let index = category.index(of: categoryItem)
let itemAtIndex = category[index]
let itemAtIndex = itemAtIndex + categoryObject.amount
totalArr.insert(itemAtIndex, at: index)
}
}
}
Do you have your record struct in a class model?
like my data model selected from sqlite:
//Data model
import Foundation
import UIKit
class scoreModel: NSObject {
var lessonName:String = String()
var lessonCode:String = String()
var creditPoint:Double = Double()
var totalStudentNumber:Int = Int()
var teacherName:String = String()
var semesterName:String = String()
var scoreValue:String = String()
var studentCount:Int = Int()
}
If the answer is yes, we can use pointer in array and repeat while loop to do this manually.
Like my code:
let mysql = ""
let dataArray = SQLiteManager.shareInstance.queryDB(sql:mysql)
var i = 0
while i<dataArray.count-1
{
var scoreArray = [Dictionary<String, Int>]()
var range = 0
var test = 0
test = i
//print("pointer i is'\(test)'")
while ((dataArray[test]as! scoreModel).lessonCode == (dataArray[test+range]as! scoreModel).lessonCode && (test+range)<dataArray.count-1)
{
let key = (dataArray[test+range]as! scoreModel).scoreValue
let value = (dataArray[test+range]as! scoreModel).studentCount
var dict: [String: Int] = [String: Int]()
dict[key] = value
scoreArray.append(dict)
//print("working pointer is'\(test+range)'")
range = range+1
}
//transfer array
let model:resultModel = resultModel()
model.lessonName = (dataArray[test]as! scoreModel).lessonName
model.lessonCode = (dataArray[test]as! scoreModel).lessonCode
model.creditPoint = (dataArray[test]as! scoreModel).creditPoint
model.semesterName = (dataArray[test]as! scoreModel).semesterName
model.teacherName = (dataArray[test]as! scoreModel).teacherName
model.totalStudentNumber = (dataArray[test]as! scoreModel).totalStudentNumber
model.scoreArray = scoreArray
resultArray.add(model)
i = i+range
//print("range is'\(range)'")
}
Make the Records as Hashable with "category" as the unique in stubs function
struct Record: Hashable {
var accountbook = ""
var category = ""
var amount = 0.0
// added from stubs of Hashable
var hashValue: Int { return category.hashValue }
static func ==(lhs: Record, rhs: Record) -> Bool {
return lhs.category == rhs.category
}
}
Then filter the unique categories
let categories = Set(record).map { $0.category }
print(categories) // ["B", "A"]
And make a sum of each category
let totals = categories.map { c in
record.filter { $0.category == c }.map{ $0.amount }.reduce(0, +)
}
print(totals) // get the sums as [10.0, 10.5]

Find element in an array of object

I created an array of objects:
var fullMonthlyList = [SimulationMonthly]()
The class here:
class SimulationMonthly {
var monthlyMonthDuration: NSNumber = 0
var monthlyYearDuration: NSNumber = 0
var monthlyFullAmount: NSNumber = 0
var monthlyAmount: Int = 0
init(monthlyMonthDuration: NSNumber, monthlyYearDuration: NSNumber, monthlyFullAmount: NSNumber, monthlyAmount: Int){
self.monthlyMonthDuration = monthlyMonthDuration
self.monthlyYearDuration = monthlyYearDuration
self.monthlyFullAmount = monthlyFullAmount
self.monthlyAmount = monthlyAmount
}
}
I just did append to populate it, now I want to find for example if they're an existing value, for example monthlyAmount equals to "194" by search in the array, how can I do ? I have tried filter and contains but I get errors.
What I've tried:
if self.fullMonthlyList.filter({ $0.monthlyAmount == self.monthlyAmount.intValue }) { ... }
Error:
Cannot invoke 'filter' with an argument list of type '((SimulationMonthly) throws -> Bool)'
You can do:
if let sim = fullMonthlyList.first(where: { $0.monthlyAmount == 194 }) {
// Do something with sim or print that the object exists...
}
This will give you the first element in your array where monthlyAmount equals 194.
If you want all elements with that condition, you can use filter:
let result = fullMonthlyList.filter { $0.monthlyAmount == 194 }
If you don't need the object at all but you just want to know if one exists, then contains would be enough:
let result = fullMonthlyList.contains(where: { $0.monthlyAmount == 194 })
Here's a simple playground example of filtering objects based on matching a property. You should be able to expand it to your situation.
class Item {
var value: Int
init(_ val: Int) {
value = val
}
}
var items = [Item]()
for setting in 0..<5 {
items.append(Item(setting))
}
func select(_ with: Int) -> [Item] {
return items.filter { $0.value == with }
}
let found = select(3)

Swift Any Array not accessing Struct Methods

So, I have the following code in Swift 2:
struct IntToken {
var value:Int
init(val:Int) {
self.value = val
}
mutating func add(val:IntToken) {
self.value += val.value
}
}
var table = [Any]()
table.append(IntToken(val:3))
table.append(IntToken(val:4))
table[0].add(table[1])
This gives me the following error
Playground execution failed: /var/folders/jx/bhltcyc90117d2wx_r82p4fr0000gn/T/./lldb/73237/playground71.swift:22:6: error: value of type 'Any' (aka 'protocol<>') has no member 'add'
table[0].add(table[1])
~~~~~^~~ ~~~
The issue is, I am trying to create an Array, that will contain different type of objects, not just IntToken's, but I also want to be able to access the elements of that array and use their methods on one another. Whenever I try to use the Any array, however, it doesn't allow me to do this, because it's reading the accessed struct instance as an any type.
What's a good way to accomplish what I am trying to do?
Starting with your original struct:
struct IntToken {
var value:Int
init(val:Int) {
self.value = val
}
mutating func add(val:IntToken) {
self.value += val.value
}
}
Do like this:
var table = [Any]()
table.append(IntToken(val:3))
table.append(IntToken(val:4))
var it = table[0] as! IntToken // take it out with cast and var ref
it.add(table[1] as! IntToken) // add with cast; `it` is now IntToken(val:7)
table[0] = it // put it back in (if you want to)
i slightly modified matt's answer (accepted answer) to show how to avoid crash in case some table element has other type as IntToken.
var table:[Any] = []
table.append(IntToken(val:3))
table.append(IntToken(val:4))
if let it0 = table[0] as? IntToken,
let it1 = table[1] as? IntToken {
var it0 = it0
it0.add(it1)
table[0] = it0
}
print(table) // [IntToken(value: 7), IntToken(value: 4)]
I prefer to modify your IntToken
struct IntToken {
var value:Int
func add1(token: IntToken)->IntToken {
var token = token
token.value += value
return token
}
}
and remove any 'mutating' function from it. Now the original code snippet could be modified to 'more functional' and easy to understand version
var table = [Any]()
table.append(IntToken(value:3))
table.append(IntToken(value:4))
if let it1 = table[1] as? IntToken,
let v = (table[0] as? IntToken)?.add1(it1) {
table[0] = v
}
print(table) // [IntToken(value: 7), IntToken(value: 4)]

How to Create 2D array in Swift?

Hi there I am new to Swift, I am trying to save Longitude and Latitude and place name from map's coordinate object to an Multidimensional array i.e:
Can anyone please help me how do i create these dynamically?
var pinArray[0][Lat] = 51.130231
var pinArray[0][Lon] = -0.189201
var pinArray[0][Place] = "Home"
var pinArray[1][Lat] = 52.130231
var pinArray[1][Lon] = -1.189201
var pinArray[1][Place] = "Office"
var pinArray[2][Lat] = 42.131331
var pinArray[2][Lon] = -1.119201
var pinArray[2][Place] = "Dinner"
You can make an array of dictionaries, but I suggest using structs instead.
Array of dictionaries
Create an empty array of dictionaries:
var pinArray = [[String:AnyObject]]()
Append dictionaries to the array:
pinArray.append(["lat":51.130231, "lon":-0.189201, "place":"home"])
pinArray.append(["lat":52.130231, "lon":-1.189201, "place":"office"])
But since your dictionaries hold two types of value (Double and String) it will be cumbersome to get the data back:
for pin in pinArray {
if let place = pin["place"] as? String {
print(place)
}
if let lat = pin["lat"] as? Double {
print(lat)
}
}
So, better use structs instead:
Array of structs
Create a struct that will hold our values:
struct Coordinates {
var lat:Double
var lon:Double
var place:String
}
Create an empty array of these objects:
var placesArray = [Coordinates]()
Append instances of the struct to the array:
placesArray.append(Coordinates(lat: 51.130231, lon: -0.189201, place: "home"))
placesArray.append(Coordinates(lat: 52.130231, lon: -1.189201, place: "office"))
It's then easy to get the values:
for pin in placesArray {
print(pin.place)
print(pin.lat)
}
Without more information, this is what I can offer.
var pinArray = [[AnyObject]]()
for location in mapLocations {
var innerArray = [location["latitude"], location["longitude"], location["place"]]
pinArray.append(innerArray)
}
Solution using an enum for Lat/Lon/Place (as you don't show us what these are):
enum Pos {
case Lat
case Lon
case Place
static let allPositions = [Lat, Lon, Place]
}
var myMatrix = [[Pos:Any]]()
myMatrix.append([.Lat: 51.130231, .Lon: -0.189201, .Place: "Home"])
myMatrix.append([.Lat: 52.130231, .Lon: -1.189201, .Place: "Office"])
myMatrix.append([.Lat: 42.131331, .Lon: -1.119201, .Place: "Dinner"])
/* check */
for (i,vector) in myMatrix.enumerate() {
for pos in Pos.allPositions {
print("myMatrix[\(i)][\(pos)] = \(vector[pos] ?? "")")
}
}
/*
myMatrix[0][Lat] = 51.130231
myMatrix[0][Lon] = -0.189201
myMatrix[0][Place] = Home
myMatrix[1][Lat] = 52.130231
myMatrix[1][Lon] = -1.189201
myMatrix[1][Place] = Office
myMatrix[2][Lat] = 42.131331
myMatrix[2][Lon] = -1.119201
myMatrix[2][Place] = Dinner */

Changing The value of struct in an array

I want to store structs inside an array, access and change the values of the struct in a for loop.
struct testing {
var value:Int
}
var test1 = testing(value: 6 )
test1.value = 2
// this works with no issue
var test2 = testing(value: 12 )
var testings = [ test1, test2 ]
for test in testings{
test.value = 3
// here I get the error:"Can not assign to 'value' in 'test'"
}
If I change the struct to class it works. Can anyone tell me how I can change the value of the struct.
Besides what said by #MikeS, remember that structs are value types. So in the for loop:
for test in testings {
a copy of an array element is assigned to the test variable. Any change you make on it is restricted to the test variable, without doing any actual change to the array elements. It works for classes because they are reference types, hence the reference and not the value is copied to the test variable.
The proper way to do that is by using a for by index:
for index in 0..<testings.count {
testings[index].value = 15
}
in this case you are accessing (and modifying) the actual struct element and not a copy of it.
Well I am going to update my answer for swift 3 compatibility.
When you are programming many you need to change some values of objects that are inside a collection. In this example we have an array of struct and given a condition we need to change the value of a specific object. This is a very common thing in any development day.
Instead of using an index to determine which object has to be modified I prefer to use an if condition, which IMHO is more common.
import Foundation
struct MyStruct: CustomDebugStringConvertible {
var myValue:Int
var debugDescription: String {
return "struct is \(myValue)"
}
}
let struct1 = MyStruct(myValue: 1)
let struct2 = MyStruct(myValue: 2)
let structArray = [struct1, struct2]
let newStructArray = structArray.map({ (myStruct) -> MyStruct in
// You can check anything like:
if myStruct.myValue == 1 {
var modified = myStruct
modified.myValue = 400
return modified
} else {
return myStruct
}
})
debugPrint(newStructArray)
Notice all the lets, this way of development is safer.
The classes are reference types, it's not needed to make a copy in order to change a value, like it happens with structs. Using the same example with classes:
class MyClass: CustomDebugStringConvertible {
var myValue:Int
init(myValue: Int){
self.myValue = myValue
}
var debugDescription: String {
return "class is \(myValue)"
}
}
let class1 = MyClass(myValue: 1)
let class2 = MyClass(myValue: 2)
let classArray = [class1, class2]
let newClassArray = classArray.map({ (myClass) -> MyClass in
// You can check anything like:
if myClass.myValue == 1 {
myClass.myValue = 400
}
return myClass
})
debugPrint(newClassArray)
To simplify working with value types in arrays you could use following extension (Swift 3):
extension Array {
mutating func modifyForEach(_ body: (_ index: Index, _ element: inout Element) -> ()) {
for index in indices {
modifyElement(atIndex: index) { body(index, &$0) }
}
}
mutating func modifyElement(atIndex index: Index, _ modifyElement: (_ element: inout Element) -> ()) {
var element = self[index]
modifyElement(&element)
self[index] = element
}
}
Example usage:
testings.modifyElement(atIndex: 0) { $0.value = 99 }
testings.modifyForEach { $1.value *= 2 }
testings.modifyForEach { $1.value = $0 }
How to change Array of Structs
for every element:
itemsArray.indices.forEach { itemsArray[$0].someValue = newValue }
for specific element:
itemsArray.indices.filter { itemsArray[$0].propertyToCompare == true }
.forEach { itemsArray[$0].someValue = newValue }
You have enough of good answers. I'll just tackle the question from a more generic angle.
As another example to better understand value types and what it means they get copied:
struct Item {
var value:Int
}
func change (item: Item, with value: Int){
item.value = value // cannot assign to property: 'item' is a 'let' constant
}
That is because item is copied, when it comes in, it is immutable — as a convenience.
Had you made Item a class type then you were able to change its value.
var item2 = item1 // mutable COPY created
item2.value = 10
print(item2.value) // 10
print(item1.value) // 5
This is very tricky answer. I think, You should not do like this:
struct testing {
var value:Int
}
var test1 = testing(value: 6)
var test2 = testing(value: 12)
var ary = [UnsafeMutablePointer<testing>].convertFromArrayLiteral(&test1, &test2)
for p in ary {
p.memory.value = 3
}
if test1.value == test2.value {
println("value: \(test1.value)")
}
For Xcode 6.1, array initialization will be
var ary = [UnsafeMutablePointer<testing>](arrayLiteral: &test1, &test2)
It is possible to use the map function to get this effect - essentially creating a new array
itemsArray = itemsArray.map {
var card = $0
card.isDefault = aCard.token == token
return card
}
I ended up recreating a new array of struct see the example below.
func updateDefaultCreditCard(token: String) {
var updatedArray: [CreditCard] = []
for aCard in self.creditcards {
var card = aCard
card.isDefault = aCard.token == token
updatedArray.append(card)
}
self.creditcards = updatedArray
}
I tried Antonio's answer which seemed quite logical but to my surprise it does not work. Exploring this further I tried the following:
struct testing {
var value:Int
}
var test1 = testing(value: 6 )
var test2 = testing(value: 12 )
var testings = [ test1, test2 ]
var test1b = testings[0]
test1b.value = 13
// I would assume this is same as test1, but it is not test1.value is still 6
// even trying
testings[0].value = 23
// still the value of test1 did not change.
// so I think the only way is to change the whole of test1
test1 = test1b

Resources