Tracing and storing an array (AS3) - arrays

I have an array in which the response to each question is recorded using a list response. A for loop where once the last question in the array is reached, another button (titled "continue") is made visible. A person clicks this now visible button to continue the task. I have two trace commands in my code, one in the for loop and one in for when the continued button is clicked. The trace function in the for loop works; however, the trace executed in the function when the continue button is clicked returns "undefined" values. (If my description isn't clear, I will make this more concrete using my code below.)
My question is why is it that the exact same value that is being trace would return values in one instance and not the other? My goal is to store the responses to the questions in the array into a String.
var listOfQuestions:Array = new Array;
var listOfAnswers:Array = new Array;
var i:int = 0;
listOfQuestions[0] = "Question 1";
listOfQuestions[1] = "Question 2";
listOfQuestions[2] = "Question 3";
lstResponses.addItem({label: "Response 1", data: "1"});
lstResponses.addItem({label: "Response 2", data: "2"});
lstResponses.addItem({label: "Response 3", data: "3"});
btnNextQuestion.addEventListener(MouseEvent.CLICK, presentNextQuestion);
function presentNextQuestion(evt:MouseEvent){
listOfAnswers[i] = lstResponses.selectedItem.data;
lstResponses.selectedItem = null;
i++;
//Present the element stored in index ā€œiā€;
if(i == listOfQuestions.length)
{
txtQuestion.htmlText = "<b>End of list. Click the Continue to Part II for the next part.</b>", btnContinue.visible = true, btnNextQuestion.visible = false;
//Output all the questions and answers;
for (i = 0; i <listOfQuestions.length; i++)
{
trace(i, listOfQuestions[i], listOfAnswers[i]);
}
}
/*If there are more elements left, present the element stored in index ā€œi.ā€*/
else
{
txtQuestion.htmlText = listOfQuestions[i];
}
}
btnContinue.addEventListener(MouseEvent.CLICK, continueClicked);
function continueClicked(evt:MouseEvent){
trace(listOfAnswers[i]);
}
To reiterate my issue using my code above:
trace(i, listOfQuestions[i], listOfAnswers[i])
yields the anticipated result, i.e., 0 Question 1 [response]. However, the
trace(listOfAnswers[i]);
in the last line of the code yields "undefined".
I've also wondered whether this error was due to the data needing to be converted to a string. In that regard, I added the following code (see below), but the error I received was: Error #1010: A term is undefined and has no properties.
var b:String = new String;
b = listOfAnswers[i].toString()
b = listOfAnswers[i].join("");
Thanks for your time and patience.

The issue appears to be how you're tracing the answer when you click on the continue button. You are tracing the answer at the index of i but your code doesn't prevent i from being incremented past the length of your list.
My guess is you're clicking the next button until it tells you that you have reached the end of the list, then only do you click the continue button. If so, i will be 3, and you're trying to trace listOfAnswers [3] and that is one more than the length of your list, giving you undefined.
Try replacing
trace(listOfAnswers[i]);
with
for (i = 0; i <listOfQuestions.length; i++)
{
trace(listOfAnswers[i]);
}

Related

Removing data from array correctly

I am facing the problem that I fill up an array with data and when I want to remove it later, even though I call the removeAll() the array still is not empty.
Better see my code for a more clear view on the problem
Updated new code
#objc func textFieldDidChange() {
doSearch()
if let commentText = commentTextField.text , !commentText.isEmpty {
sendButton.setTitleColor(UIColor.blue, for: UIControlState.normal)
sendButton.isEnabled = true
return
}
sendButton.setTitleColor(UIColor.lightGray, for: UIControlState.normal)
sendButton.isEnabled = false
}
func doSearch() {
let caption = commentTextField.text
let words = caption?.components(separatedBy: CharacterSet.whitespacesAndNewlines)
self.usersSuggestion.removeAll()
for var word in words! {
self.usersSuggestion.removeAll()
if word.hasPrefix("#") {
word = word.trimmingCharacters(in: CharacterSet.punctuationCharacters)
self.isCellSelected = true
self.usersSuggestion.removeAll()
API.User.suggestUsers(withText: word, completion: { (user) in
print("closure", word)
self.usersSuggestion.append(user)
self.tableView.reloadData()
print("#", self.usersSuggestion.count)
})
self.usersSuggestion.removeAll()
tableView.reloadData()
} else {
self.isCellSelected = false
self.usersSuggestion.removeAll()
tableView.reloadData()
}
self.usersSuggestion.removeAll()
tableView.reloadData()
}
}
I am facing the problem here that the array, even though I call multiple times the removeAll() method, never gets back to 0. When I have 2 user, link one, the next time I write an # I get the 2 users+ the linked user (so him twice). I can do it infinitely, having like 100 userSuggestions with just 2 existing users.
photos
You are printing the count immediately after inserting an element in the array. This will alway give a count of 1.
// count was zero
self.hashTags.insert(hashTag, at: 0) // adding one
print("# = ", self.hashTags.count) // count is now 1
Given that the API.user... function uses a completion handler, I would assume that this happens in a separate thread or at least asynchronously so you could event get into situations where the UI shows empty and suddenly shows one.
You may want to structure your code differently to better convey the separation between requesting data and displaying the (asynchronously obtained) results. Embedded completion handlers tend be misleading and give the impression that execution will happen in their visually organized sequence.

Checking arrays in AS3

I'm collecting rows of answers from a database which are made in to arrays. Something like:
for (var i:int = 0; i < event.result.length; i++) {
var data = event.result[i];
var answer:Array = new Array(data["question_id"], data["focus_id"], data["attempts"], data["category"], data["answer"], data["correct"], data["score"]);
trace("answer: " + answer);
restoreAnswer(answer, i);
}
Now, if I trace answer, I typically get something like:
answer: 5,0,2,IK,1.a,3.1,0
answer: 5,0,1,IK,2.a,3.1,0
answer: 4,1,1,AK,3,3,2
From this we see that focus_id 0 (second array item) in question_id 5 (first array item) has been attempted twice (third array item), and I only want to use the last attempt in my restoreAnswer function.
My problem is that first attempt answers override the second attempts since the first are parsed last it seems. How do I go about only calling my restoreAnswer only when appropriate?
The options are:
1 attempts, correct score (2 points)
2 attempts, correct score (1 points)
1 attempt, wrong score (0 points)
2 attemps, wrong score (0 points)
There can be multiple focus_id in each question_id.
Thank you very much! :)
I would consider having the DB query return only the most recent attempt, or if that doesn't work efficiently, return the data in attempt order. You may score question 5 twice, but at least it'll score correctly on the last pass.
You can also filter or sort the data you get back from the server.
Michael Brewer-Davis suggested using the database query to resolve this; normally speaking, this would be the right solution.
If you wait until you get it back from the web method call or whatever in AS3, then consider creating an additional Vector variable:
var vAttempts:Vector.<Vector.<int>> = new Vector.<Vector.<int>>(this.m_iNumQuestions);
You mentioned that it seems that everything is sorted so that earlier attempts come last. First you want to make sure that's true. If so, then before you do any call to restoreAnswer(), you'll want to check vAttempts to make sure that you have not already called restoreAnswer() for that question_id and focus_id:
if (!vAttempts[data["question_id"]])
{
vAttempts[data["question_id"]] = new Vector.<int>(); // ensuring a second dimension
}
if (vAttempts[data["question_id"]].indexOf(data["focus_id"]) == -1)
{
restoreAnswer(answer, i);
vAttempts[data["question_id"]].push(data["focus_id"]);
}
So optimizing this just a little bit, what you'll have is as follows:
private final function resultHandler(event:ResultEvent):void {
var vAttempts:Vector.<Vector.<int>> = new Vector.<Vector.<int>>(this.m_iNumQuestions);
var result:Object = event.result;
var iLength:int = result.length;
for (var i:int = 0; i < iLength; i++) {
var data = result[i];
var iQuestionID:int = data["question_id"];
var iFocusID:int = data["focus_id"];
var answer:Array = [iQuestionID, iFocusID, data["attempts"],
data["category"], data["answer"], data["correct"], data["score"]];
trace("answer: " + answer);
var vFocusIDs:Vector.<int> = vAttempts[iQuestionID];
if (!vFocusIDs) {
vAttempts[iQuestionID] = new <int>[iFocusID];
restoreAnswer(answer, i);
} else if (vFocusIDs.indexOf(iFocusID) == -1) {
restoreAnswer(answer, i);
vFocusIDs.push(iFocusID);
}
}
}
Note: In AS3, Arrays can skip over certain indexes, but Vectors can't. So if your program doesn't already have some sort of foreknowledge as to the number of questions, you'll need to change vAttempts from a Vector to an Array. Also account for whether question IDs are 0-indexed (as this question assumes) or 1-indexed.

Clearing my array leaves blank array positions

So I'm facing a problem with an AS3 class that's not operating the way I need it to. It's a simple problem, but a complex set of methods that cause it.
Firstly, the 'quiz' I'm building has 6 questions loaded as external SWFs inside of a Shell, run by a class. First we declared a var "a_quiz" to hold 6 values pushed from the external SWFs. These 6 values are reduced to a string and then checked against another array that contains the correct answers. The following loadQuiz function is designed to launch one of three random quizes and clear the a_quiz array so it can take new answers:
public function loadQuiz():void {
a_quiz.length=0;
trace("loadQuiz");
n_quizCorrect = n_quizScore = 0;
n_currentQuiz = Math.floor(Math.random() * (n_totalTopics - n_quizStart + 1) + n_quizStart);
loadTopic(n_currentQuiz);
}
Now I've tested the Shell and confirmed that the trace "loadQuiz" fires every time. And the first time you load the quiz, everything behaves as it should. The 6 questions trace correct or incorrect responses and push 6 binary values into the a_quiz array. The output looks like this:
loadQuiz
incorrect
correct
incorrect
incorrect
incorrect
incorrect
0,1,0,0,0,0
you failed
Then I jump back to the main menu and launch again. This deploys the loadQuiz function all over again. The first line of the function:
a_quiz.length=0;
should be emptying the a_quiz array to accept new answers to mark against. But when I complete the quiz I get this:
loadQuiz
correct
correct
correct
correct
correct
correct
,,,,,,1,1,1,1,1,1
you failed
For some reason beyond my understanding, the push values are stacking on top of empty positions, so when the strings compare...they won't match. What is going on here?
The push function:
function handleClick(evt:MouseEvent):void{
var tempCORRECT = a_answerSheet.toString();
var tempSELECTION = a_selected.toString();
//
if(tempSELECTION == tempCORRECT){
trace('correct');
parentObj1.a_quiz[ parentObj1.n_currentQuestion - 1 ] = 1;
}else{
trace('incorrect');
parentObj1.a_quiz[ parentObj1.n_currentQuestion - 1 ] = 0;
}
parentObj1.n_currentQuestion ++;
// GOTO NEXT SLIDE
parentObj1.LOADNEXT('up');
}
The problem originated in the loading of the Quiz itself.
as you can see on the handleClick function:
parentObj.n_currentQuestion++
was increasing an integer variable on the parentObj's timeline called n_currentQuestion. The problem then, originated in how the quiz was logging n_currentQuestion when the quiz loads with this function:
public function loadQuiz():void {
a_quiz= [];
trace("loadQuiz");
n_quizCorrect = n_quizScore = 0;
n_currentQuiz = Math.floor(Math.random() * (n_totalTopics - n_quizStart + 1) + n_quizStart);
loadTopic(n_currentQuiz);
}
So to correct the error, I needed to add a line to the loadQuiz, so that it would always load with n_currentQuestion set to 1.
public function loadQuiz():void {
a_quiz= [];
n_currentQuestion = 1;
trace("loadQuiz");
n_quizCorrect = n_quizScore = 0;
n_currentQuiz = Math.floor(Math.random() * (n_totalTopics - n_quizStart + 1) + n_quizStart);
loadTopic(n_currentQuiz);
}

How does ng-repeat work?

I dissected ng-repeat and extracted the code blocks attached, seeing that these comprise the logic that handles the repeating algorithm (which I want to understand how it works).
I have quite a few questions, but since they are all about the internals of ng-repeat I chose to ask them all here. I don't see any reason to separate them into different SO questions. I have marked inline to which line(s) of code each question refers to.
Why do they need to make sure that trackById is not the native hasOwnProperty function? (that's what that assertNotHasOwnProperty function does, part of Angular's internal API)
As far as my intuition go, this code executes on items already in the repeater, when it has to update the collection - it just picks them up and pushes them into the list for processing, right?
This code block obviously looks for duplicates in the repeater collection. But how exactly does it do that is beyond me. Please explain.
Why does Angular have to store the block object both nextBlockMap and in nextBlockOrder?
What are block.endNode and block.startNode?
I assume the answer to the above question will clarify how this algorithm work, but please explain why it has to check if the nextNode has (been) '$$NG_REMOVED'?
What happens here? Again, I assume question 6 will already provide an answer to this one. But still pointing that out.
Like I said, I dug through ng-repeat to find the code I deemed relevant to the repeating mechanism. Plus, I do understand the rest of the directive. So without further ado, here is the code (from v1.2.0):
length = nextBlockOrder.length = collectionKeys.length;
for (index = 0; index < length; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
trackById = trackByIdFn(key, value, index);
// question #1
assertNotHasOwnProperty(trackById, '`track by` id');
// question #2
if (lastBlockMap.hasOwnProperty(trackById)) {
block = lastBlockMap[trackById];
delete lastBlockMap[trackById];
nextBlockMap[trackById] = block;
nextBlockOrder[index] = block;
// question #3
} else if (nextBlockMap.hasOwnProperty(trackById)) {
// restore lastBlockMap
forEach(nextBlockOrder, function(block) {
if (block && block.startNode) lastBlockMap[block.id] = block;
});
// This is a duplicate and we need to throw an error
throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
expression, trackById);
// question #4
} else {
// new never before seen block
nextBlockOrder[index] = { id: trackById };
nextBlockMap[trackById] = false;
}
}
for (index = 0, length = collectionKeys.length; index < length; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
block = nextBlockOrder[index];
// question #5
if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
if (block.startNode) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = block.scope;
// question #6
nextNode = previousNode;
do {
nextNode = nextNode.nextSibling;
} while(nextNode && nextNode[NG_REMOVED]);
if (block.startNode != nextNode) {
// existing item which got moved
$animate.move(getBlockElements(block), null, jqLite(previousNode));
}
previousNode = block.endNode;
} else {
// new item which we don't know about
childScope = $scope.$new();
}
// question #7
if (!block.startNode) {
linker(childScope, function(clone) {
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
$animate.enter(clone, null, jqLite(previousNode));
previousNode = clone;
block.scope = childScope;
block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0];
block.endNode = clone[clone.length - 1];
nextBlockMap[block.id] = block;
});
}
}
lastBlockMap = nextBlockMap;
After some tinkering with the directive, I became familiar with ng-repeaters code, and managed to answer some of my questions. I highlighted in bold the things I couldn't yet figure out on my own, and would appreciate if anyone could shed some light on the bold parts:
The ID is tested for hasOwnProperty, because they use that method to check whether the ID is present in the iteration objects (lastBlockMap, nextBlockMap) (this process explained below). I couldn't find out on what scenario this can actually happen, however.
I was correct in my assumption. nextBlockMap contains all items that will be transcluded on the current model change. lastBlockMap contains everything from the previous model update. It used for finding duplicates in the collection.
OK, this one is pretty straightforward actually. In this for loop, ng-repeat fills up nextBlockMap with items from lastBlockMap. Looking at the order of ifs, it's easy to see that if the item cannot be found in lastBlockMap, but it is already present in nextBlockMap (meaning, it was already copied there from lastBlockMap, and therefore its trackById appears twice in the collection) - it's a duplicate. What the forEach does is simply run through all initialized items in nextBlockMap (blocks that have a startNode property) and push their ID back into lastBlockMap. I cannot however understand why this is necessary.
The only reason I could find for separating nextBlockOrder (all trackByIds in an array) from nextBlockMap (all block objects in a trackById hash), is this line, which working with an array makes it an easy and simple operation: if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;. It is explained in the answers to question 5 and 6:
block.startNode and block.endNode are the first and last DOM nodes in the block belonging to an item in the collected being repeated. Therefore, this line here sets previousNode to reference the last DOM node of the previous item in the repeater.
previousNode is then used as the first node, in a loop that checks how the DOM changed when items have been moved around or removed from the repeater collection - again, only in case we are not working with the first block in the array.
This is easy - it initializes the block - assigning the $scope and startNode and endNode for later reference, and saves everything in nextBlockMap. The comment created right after the cloned element, is there to guarantee we always have an endNode.

Flash Resetting array values

I was working on a test project that I will later incorporate into a much larger work that is a simple quiz game. I made an array with my questions:
var questions1:Array=["nitrogen dioxide","sulfur hexafluoride",..."]
and in a second layer I made a button that cycles through the questions randomly.
import flash.events.MouseEvent;
var qno=0;var rnd1;
function change_question(){
rnd1=Math.ceil(Math.random()*questions1.length)-1;
q.text=questions1[rnd1];
if(questions1[rnd1]=="X"){change_question();}
questions1[rnd1]="X";
}
change_question();
next_b.addEventListener(MouseEvent.CLICK, ButtonAction1);
function ButtonAction1(eventObject:MouseEvent){
qno++;change_question();
}
This part works great, because I was following a tutorial. Text appears in a dynamic text box I created as it should. This tutorial taught to change the value of the array to X with every choice and to ignore choose a different question every time it encountered an X
After it cycles through all the questions I basically get an infinite loop in my output section of flash because it can't find any more non-X values. I was hoping someone had information on how to press a button an change the array back to its default settings so that a teacher (because that is who it is for) has a way of resetting the file when they have reached the end of the quiz.
Thanks everyone!
As per my understanding you wanted to randomize your questions and after showing all the entire questions you wanted to reset your questions.
As per your code you get a random question and updating the same array by pushing value 'X'. Rather than doing this what you need to do is to preserve the array only randomize its position. So that you can use the same value once you cover your all questions
I have added code here.
import flash.events.MouseEvent;
var qno=0;var rnd1;
var questions1:Array=["nitrogen dioxide","sulfur hexafluoride","carbon dioxide","carbon monooxide"];
var nAttmeptedCount = 0;
var shuffledLetters:Array;
function change_question()
{
if(qno == questions1.length)
{
qno = 0;
resetQuestion()
}
else
{
q.text = questions1[shuffledLetters[qno]];
qno++;
}
}
function resetQuestion()
{
shuffledLetters = new Array(questions1.length);
for(var i=0;i<shuffledLetters.length;i++)
{
shuffledLetters[i] = i;
}
shuffledLetters.sort(randomize);
}
function randomize ( a : *, b : * ) : int {
return ( Math.random() > .5 ) ? 1 : -1;
}
resetQuestion()
change_question();
next_b.addEventListener(MouseEvent.CLICK, ButtonAction1);
function ButtonAction1(eventObject:MouseEvent){
change_question();
}
In above solution after showing all questions I have reset the questions automatically. you can modify code as per your requirement. If you want to do the reset questions you can put you code qno = 0;resetQuestion() on button click.
hope above solution work for you.

Resources