I've been trying to extract some data from a dictionary. There should be 5 values. The first code snippet fails, only populating the scheduleForCurrentDay with 1 value, whereas the second snippet works, getting all 5. Can anyone explain why the first one doesn't work? I'm guessing it's something to do with the dictionary being copied but I'm not really sure.
// Fails; only gets one value
private var categories = [StudyCategory]() {
didSet {
let c = categories
for subject in c {
guard let target = subject.quota[currentDayComponent] else { continue }
scheduleForCurrentDay[subject.title] = target
}
}
}
// Succeeds; gets all 5 values
private var categories = [StudyCategory]() {
didSet {
let c = categories
var schedule = [String:Double]()
for subject in c {
schedule[subject.title] = subject.quota[currentDayComponent]
}
scheduleForCurrentDay = schedule
}
}
Related
I have an easy question that is also hard at the same time. I have two separate structs (this can also work for classes):
struct FBTweet {
var tweetId: Int? //set
var tweetText: String? //set
}
and
struct Status {
var statusId: Int? //set
var statusText: String? //no value
}
I have an array of both structs var fbTweetArray: [FBTweet] = [] and var statusArray: [Status] = []
I have set every variable in to a certain value in each index in fbTweetArray but I only set the .statusId variable in each index for statusArray. For every statusArray.statusId value in statusArray, there is only one fbTweetArray.tweetId that has the same exact Int value. I am trying to make is so that if these two variables are the same then I should set set
statusArray.statusText to whatever fbTweetarray.tweetText is. So for example only fbTweetArray[1].tweetid = 2346 and statusArray[4].statusId = 2346 have 2346 as their value. There for if fbTweetArray[1].tweetText = "hello friend" then statusArray[4].statusText needs to be set to "hello friend".
So far I have
func testWhat () {
var fbTweetArray: [FBTweet] = []
var statusArray: [Status] = []
for fbTweet in fbTweetArray {
for var status in statusArray {
if (status.statusId == fbTweet.tweetId ) {
status.statusText = fbTweet.tweetText
}
}
}
}
how do I set the for var status in the for loop back into the statusArray since it is now a var and is different than one of the indexes in var statusArray: [Status] = []
Basically, you need only one for/forEach loop to achieve what you want:
var fbTweetArray: [FBTweet] = [
FBTweet(tweetId: 1, tweetText: "1"),
FBTweet(tweetId: 2, tweetText: "2"),
FBTweet(tweetId: 3, tweetText: "3")
]
var statusArray: [Status] = [
Status(statusId: 2, statusText: nil),
Status(statusId: 1, statusText: nil),
Status(statusId: 3, statusText: nil)
]
fbTweetArray.forEach { tweet in
if let index = statusArray.index(where: { $0.statusId == tweet.tweetId }) {
statusArray[index].statusText = tweet.tweetText
}
}
print(statusArray.map { $0.statusText }) // [Optional("2"), Optional("1"), Optional("3")]
Note, that your ids in both structures can be nil. To handle this situation (if both id is nil - objects are not equal) you can write custom == func:
struct Status {
var statusId: Int? //set
var statusText: String? //no value
static func == (lhs: Status, rhs: FBTweet) -> Bool {
guard let lhsId = lhs.statusId, let rhsId = rhs.tweetId else { return false }
return lhsId == rhsId
}
}
...
// rewrite .index(where: ) in if condition
if let index = statusArray.index(where: { $0 == tweet }) { ... }
Also, there is some pro-tip. If you adopt your structs to Hashable protocol, you will be able to place FBTweets and Statuses into Set structure. The benefits of that:
If you instead store those objects in a set, you can theoretically
find any one of them in constant time (O(1)) — that is, a lookup on a
set with 10 elements takes the same amount of time as a lookup on a
set with 10,000.
You can find more in-depth info about it in a new great article by NSHipster.
Your question is interesting only if both the arrays are not ordered.
To find the element from fbTweet array, you can sort it and employ binary search.
Then enumerate status array and find the fbTweet object with the same identifier and modify the status object. It needs to be saved again in the array as structs get copied on write.
extension Array where Element == FBTweet {
func binarySearchFBTweetWith(_ id:Int) -> FBTweet? {
var range = 0..<self.count
while range.startIndex < range.endIndex {
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
guard let tweetId = self[midIndex].tweetId else {
continue
}
if tweetId == id {
return self[midIndex]
} else if tweetId < id {
range = midIndex+1..<range.endIndex
} else {
range = range.startIndex..<midIndex
}
}
return nil
}
}
fbTweetArray.sort{($0.tweetId ?? 0) < ($1.tweetId ?? 0)}
for (index, status) in statusArray.enumerated() {
guard let statusId = status.statusId else {continue}
guard let fbTweet = fbTweetArray.binarySearchFBTweetWith(statusId) else {continue}
var status = status
status.statusText = fbTweet.tweetText
statusArray[index] = status
}
An alternative would be use dictionaries instead of arrays, for better performance and easier implementation (in this case). You can easily get the array of keys and values later If you need.
In this case, the Id would be the Key of the dictionary, and the text the value.
I am making a rhythm app, but I can't seem to randomize the circles. Here is my code:
var alternator = 0
var fallTimer:NSTimer?
var flag:Bool = true
let circleIndexes = (0..<5).map { return NSNumber(integer: $0) }
let randomIndexes = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(circleIndexes) as! [Int]
func fallCircleWrapper() {
if (flag == true) {
self.alternator += 1
} else {
self.alternator -= 1
}
if (self.alternator == 0) {
flag = true
} else if (self.alternator == 5) {
flag = false
}
self.hitAreaArray[randomIndexes[self.alternator]].emitNote(self.texture!)
}
The problem is with this line:
let randomIndexes = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(circleIndexes) as! [Int]
which gives the error "Instance member 'circleIndexes' cannot be used on type 'GameScene'". How should I go about fixing this?
Consider this example that reproduces your error on a simpler scale:
func agePlusOne(_ num: Int) -> Int { return num + 1 }
class Cat {
var age = 5
var addedAge = agePlusOne(age) // ERROR!
}
You're trying to use a property /before/ it has been initialized... that is, before init() has finished, thus giving you a self to work with.
A way around this is to use lazy properties... lazy properties are initialized only when first called by an object.. that is, they are properly initialized only after there is a self available.
private(set) lazy var randomIndices: [Int] = {
GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(self.circleIndexes) as! [Int]
}()
computed and static properties are also lazy by default, without needing to use the keyword. Unfortunately, you cannot use lazy let, so the private(set) was added to give you more immutability, but not total.
PS, this is Swift3 so you may need to convert it to Swift2.
First, I initialize the variables to hold the stock data
var applePrice: String?
var googlePrice: String?
var twitterPrice: String?
var teslaPrice: String?
var samsungPrice: String?
var stockPrices = [String]()
I fetch current stock prices from YQL, and put those values into an array
func stockFetcher() {
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
if let applePrice = json["query"]["results"]["quote"][0]["Ask"].string {
print(applePrice)
self.applePrice = applePrice
self.tableView.reloadData()
}
if let googlePrice = json["query"]["results"]["quote"][1]["Ask"].string {
print(googlePrice)
self.googlePrice = googlePrice
self.tableView.reloadData()
}
if let twitterPrice = json["query"]["results"]["quote"][2]["Ask"].string {
print(twitterPrice)
self.twitterPrice = twitterPrice
self.tableView.reloadData()
}
if let teslaPrice = json["query"]["results"]["quote"][3]["Ask"].string {
print(teslaPrice)
self.teslaPrice = teslaPrice
self.tableView.reloadData()
}
if let samsungPrice = json["query"]["results"]["quote"][4]["Ask"].string {
print(samsungPrice)
self.samsungPrice = samsungPrice
self.tableView.reloadData()
}
let stockPrices = ["\(self.applePrice)", "\(self.googlePrice)", "\(self.twitterPrice)", "\(self.teslaPrice)", "\(self.samsungPrice)"]
self.stockPrices = stockPrices
print(json)
}
}
}
in cellForRowAt indexPath function I print to the label
if self.stockPrices.count > indexPath.row + 1 {
cell.detailTextLabel?.text = "Current Stock Price: \(self.stockPrices[indexPath.row])" ?? "Fetching stock prices..."
} else {
cell.detailTextLabel?.text = "No data found"
}
I'm running into the issue of printing Current Stock Price: Optional("stock price"), with the word optional. I gather that this is because I'm giving it an array of optional values, but I sort of have to since I actually don't know if there will be data coming from YQL, one of the 5 stocks might be nil while the others have data. From reading other similar questions I can see that the solution would be to unwrap the value with !, but I'm not so sure how to implement that solution when it's an array with data that might be nil, and not just an Int or something.
How can I safely unwrap here and get rid of the word Optional?
First off:
Any time you repeat the same block of code multiple times and only increase a value from 0 to some max, it is a code smell. You should think about a different way to handle it.
You should use an array to do this processing.
How about a set of enums for indexes:
enum companyIndexes: Int {
case apple
case google
case twitter
case tesla
//etc...
}
Now you can run through your array with a loop and install your values more cleanly:
var stockPrices = [String?]()
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
let pricesArray = json["query"]["results"]["quote"]
for aPriceEntry in pricesArray {
let priceString = aPriceEntry["ask"].string
stockPrices.append(priceString)
}
}
}
And to fetch a price from the array:
let applePrice = stockPrices[companyIndexes.apple.rawValue]
That will result in an optional.
You could use the nil coalescing operator (??) to replace a nil value with a string like "No price available.":
let applePrice = stockPrices[companyIndexes.apple.rawValue] ?? "No price available"
or as shown in the other answer:
if let applePrice = stockPrices[companyIndexes.apple.rawValue] {
//we got a valid price
} else
//We don't have a price for that entry
}
I'm writing this outside of Xcode (so there might be typos), but this kind of logic should work.
if self.stockPrices.count > indexPath.row + 1 {
var txt = "Fetching stock prices..."
if let price = self.stockPrices[indexPath.row] {
txt = price
}
cell.detailTextLabel?.text = txt
} else {
cell.detailTextLabel?.text = "No data found"
}
For safe unwrap use that code:
if let currentStockPrice = self.stockPrices[indexPath.row]
{
// currentStockPrice available there
}
// currentStockPrice unavailable
If you need to unwrap multiple variables in one if after another it may lead to unreadable code. In such case use this pattern
guard let currentStockPrice = self.stockPrices[indexPath.row]
else
{
// currentStockPrice is unavailable there
// must escape via return, continue, break etc.
}
// currentStockPrice is available
I have a custom class like this -
class Event: NSObject
{
var eventID: String?
var name:String?
}
Now i have an array of Event object's like
var events = [Event]()
var event1 = Event()
event1.eventID = "1"
event1.name = "Anu"
var event2 = Event()
event2.eventID = "2"
event2.name = "dev"
var event3 = Event()
event3.eventID = "1"
event3.name = "Anu"
events.append(event1)
events.append(event2)
events.append(event3)
to get the unque eventID's from array i have written code like this which is working great -
func getUniqueIDsFromArrayOfObjects(events:[Event])->NSArray
{
let arr = events.map { $0.eventID!}
let uniquearr:NSMutableArray = NSMutableArray()
for obj in arr
{
if !uniquearr.containsObject(obj) {
uniquearr.addObject(obj)
}
}
return uniquearr;
}
print(getUniqueIDsFromArrayOfObjects(events))
I wanted to know is there any alternate way to get the unique id's from array of objects more effectively than i am using . May be something by using NSPredicate.
Because an array having thousands of objects, my code going to do more iteration .
You can use a Set to obtain only the unique values. I would suggest that you have your function return a Swift array rather than NSArray too.
func getUniqueIDsFromArrayOfObjects(events:[Event])->[String]
{
let eventIds = events.map { $0.eventID!}
let idset = Set(eventIds)
return Array(idset)
}
let uniqueRecords = jobs.reduce([], {
$0.contains($1) ? $0 : $0 + [$1]
})
A Set is a collection similar to an array, which prevents duplicates. You can do:
func getUniqueIDsFromArrayOfObjects(events:[Event])->[Event] {
return Array(Set(events.map { $0.eventID! }))
}
Note that the order of the items in a set is undefined, so if you care about the order of the elements, you should try a different solution.
I am reading a very large json file and pulling in data. I can currently pull in the correct data, but when I try and append it to an array, it ends up replacing it.
func parseJSON(json: JSON){
let predicate = {
(json: JSON) -> Bool in
if let jsonID = json["class"].string where jsonID == "sg-header-heading"{
return true
}
return false
}
var backclass = ViewController()
let foundJSON = json.findmultiple(backclass, predicate: predicate)
Using this extension to search for multiple values that match "sg-header-heading"
When I print the array in the extension this is what I get. But when I print it above I get nil. Also I am only getting one value per instead of 6 values in the end.
extension JSON{
func findmultiple(viewclass: ViewController, #noescape predicate: JSON -> Bool) -> JSON? {
var backclass = ViewController()
if predicate(self) {
return self
}
else {
if let subJSON = (dictionary?.map { $0.1 } ?? array) {
for json in subJSON {
if let foundJSON = json.findmultiple(backclass, predicate: predicate) {
let shorten = foundJSON["html"].stringValue
let firstshort = shorten.componentsSeparatedByString(" ")
let secondshort = firstshort[1].componentsSeparatedByString("\r")
let classname = secondshort[0]
if(classname == "STUDY HALL (INSTRUCT)"){
print("skip")
}else{
backclass.importClass.append(classname)
print("fsgsfg \(backclass.importClass)")
//backclass.collection.reloadData()
}
}
}
}
}
return nil
}
}
You are first calling findmultiple() from the outermost context (let foundJSON = json.findmultiple(predicate)). As the code is executed and findmultiple() is actually executed it creates a new instance of with var backclass = ViewController(). As the code goes into its loop it executes findmultiple() again, recursively. As the code goes into its loop an additional time it creates a new instance of backclass. Each time it goes through the loop, the recursion goes deeper and a new instance of ViewController is created each time so backclass always points to a new instance of ViewController.
The solution to this would be to create backclass at a higher level. You might try an approach like this:
var backclass = ViewController()
let foundJSON = json.findmultiple(backclass, predicate)
Note that this would mean passing backclass to findmultiple from inside the loop where the recursive call happens.