Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialMark Goldenson
1,765 PointsQuestions on FunFacts exercise
Hi, I have three questions:
A. I am trying to create an empty array that I can append items to. I think I followed the syntax from the latest Swift documentation but it's not working:
var factIndexes = [Int]()
factIndexes.append(1)
The integer 1 does not get appended to the array.
B. For the Fun Fact exercise that displays random facts, I am trying to improve the code so that I always display a new fact that has not been previously shown until all facts are shown.
I'm implementing this by creating an array of integers (factIndexes) between 0 and the number of facts (which is 10 in the exercise). The integers represent indexes for my array of facts (factsArray). When a fact is displayed, I remove its index from factIndexes and pull the next fact's index from the remaining integers in factIndexes. Once all facts are displayed, I reset factIndexes.
Is this a good approach? If not, what would be a better one?
C. I'm getting an error with the code for B. that I haven't been able to fix:
/*
Goal: Get a new random fact each time.
Method: Create an array of indexes, then remove each index when its fact is used.
Reset the array when all facts have been used.
*/
var factIndexes = [Int]()
var factIndex = 0
func createFactIndexes() {
while factIndex < factsArray.count {
factIndexes.append(factIndex) // Error here
}
}
Error:
"Immutable value of type '[(Int)]' only has mutating members named 'append'"
This seems related to my inability to create a mutable array in 1.
Thanks for any help!
5 Answers
Pasan Premaratne
Treehouse TeacherThis is actually a tad bit more complicated than it seems. I'm also tagging the mods Kevin Lozandier and Ricardo Hill-Henry so that they're aware if this issue comes up again.
Let's start with the first problem. You can't append an item to your array. As Kevin mentioned, the first issue is a syntactical problem where you need to be writing
var factIndexes: [Int] = []
to create the array. Note that the semi colons aren't necessary since they are optional in Swift. But even with the right syntax you still won't be able to add items and this is because the way Swift is designed. (Fair warning: It's about to get computer sciency here). In the Fun Facts app, we're creating FactBook as a structure, or struct. In Swift, structures (and enums) are value types. This is in contrast to a Swift class, which is a reference type.
These two types differ in how data is stored and accessed. In simple terms, a value type copies the data directly while a reference type stores the memory address and uses that to refer to the data. This is also known as deep copy (value type) vs shallow copy (reference type). In the former, the data is safer because it is copied over before use so that if it is changed, the data you are working with remains unchanged.
Because of this, despite creating a mutable array using the keyword var, when the struct is instantiated, it creates an immutable copy instead by deep copying the data. This is why it's telling you that you can't append items. There's two solutions to this issue (neither of which I've covered yet in courses).
- To keep using a struct as your data model but be able to append items or change the data after creation, you need to append the
mutating
keyword to your methods. The following code will work.
var factIndex: [Int] = []
mutating func randomFact() -> String {
factIndex.append(1)
var unsignedArrayCount = UInt32(factsArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
return factsArray[randomNumber]
}
Note to do this, when you instantiate your struct in your view controller, you can't assign it to a constant, it needs to be a variable.
- Use a class instead. Classes are reference types and you won't run into this problem.
Phew! That should help you with your first problem of appending stuff to your index. Problem 2! How to improve your code to make sure facts aren't repeated.
Here's a simpler approach that should work for what you are trying to achieve.
- Create a stored property of type Int to store the previous randomNumber generated
- Every time you generate a random number, check to see if it is the same as the previously generated one.
- If it is, generate another number, otherwise use that one and store it for the next time
Now you ensure that you're not repeating the same random number twice, which would mean the same fact is not displayed twice. Your approach is fine, but using an array makes more sense if you wanted to ensure that a couple facts don't repeat in a row.
I think I covered everything!
Happy learning :D
Kevin Lozandier
Courses Plus Student 53,747 PointsAs Ricardo Hill-Henry politely stated, you need to typecast not unlike regular variables. A colon in addition to a valid type surrounded by square brackets: let arrayOfNumbers : [Int] = []
In addition to that, you should use the .count
instance method to have a number based on an existing array; in this case, an existing array of facts:
let numberOfFacts = arrayOfFacts.count
Mark Goldenson
1,765 PointsHey Stephen, thanks for the reply.
On A, I actually want to create an empty array and that add elements to it. How could I do that?
Ricardo Hill-Henry
38,442 PointsThis should accomplish the ability to populate the array. You can edit as needed. If you need any further explaining, outside of the comments I've added, feel free to ask.
/*
Goal: Get a new random fact each time.
Method: Create an array of indexes, then remove each index when its fact is used.
Reset the array when all facts have been used.
*/
var factIndexes: [Int] = []; //creates an empty array, that accepts only integers
var factIndex = 0 //counter
var arraySize = 10; //I want 10 items in my array 0-9 index
func createFactIndexes() {
while factIndex < arraySize {
factIndexes.append(factIndex+1) // Array will be populated with values 1 - 10
println(factIndexes[factIndex]) //Just checking to see if my values are being placed.
factIndex++ //Increment the array
}
}
createFactIndexes()
Mark Goldenson
1,765 PointsPasan, Kevin, and Ricardo: thanks for your help.
I'm encountering a separate problem that I haven't found an answer to. I'm trying to initialize a Factbook class with an array of integers, one for each fact. I then want to access that array in class methods.
I'm getting an error with this code:
class FactBook {
init() {
let factsArray = [
"Ants stretch when they wake up in the morning.",
"Ostritches can run faster than horses." // <snip>
]
var factIndexes: [Int] = []
initFactIndexes()
}
func initFactIndexes() -> [Int] {
factIndexes = [] // Reset the factIndexes array. Producing an error: "Use of unresolved identifier 'factIndexes'
var factIndex = 0
var numberOfFacts = factIndexes.count
while factIndex < numberOfFacts {
factIndexes.append(factIndex)
factIndex++
}
return factIndexes
}
}
I don't seem able to access the "factIndexes" class variable from the "initFactIndexes" class method.
If I move initFactIndexes() into the init() function, then I have access to factIndexes. However, since I want to reset the factIndexes array at any time, not just initialization, I think initFactIndexes() should be outside init().
I'm also just wondering how to access class variables from the class's methods. I keep getting "Use of unresolved identifier [variable]" or "Factbook does not have a member named [variable]" errors. I searched the documentation but couldn't find the problem.
Thanks!
Pasan Premaratne
Treehouse TeacherBoth errors are because you are creating the factIndexes
array inside a method (in this case the init method). This limits its scope to inside that particular method. What you need is to assign the variable as a stored property and you were on track with your train of thought.
Stored properties are declared outside of any of the class/struct/enum's methods. Move the declaration of the factIndexes array above and outside the init method as shown below and you should be fine.
class FactBook {
var factIndexes: [Int] = []
init() {
let factsArray = [
"Ants stretch when they wake up in the morning.",
"Ostritches can run faster than horses." // <snip>
]
initFactIndexes()
}
func initFactIndexes() -> [Int] {
factIndexes = []
var factIndex = 0
var numberOfFacts = factIndexes.count
while factIndex < numberOfFacts {
factIndexes.append(factIndex)
factIndex++
}
return factIndexes
}
}
Mark Goldenson
1,765 PointsAha, moving the declaration of factIndexes above init() did the trick. Thanks! My series of random facts are now unique each time. :)