I'm writing a flexible search algorithm for a portion of my XML file. I have this exact same method working flawlessly elsewhere, thus why this is driving me batty.
Here is the section of my function that I'm having trouble with.
var searchResults:Array = [];
var r:Array = [];
//Other code...
var classSplit:Array = inClass.split("-");
var profs:Array = getCourseProfs(classSplit[0], classSplit[1]);
trace(searchResults + "-vs-" + profs);
for each(var si:String in searchResults)
{
trace(si + " is at index " + profs.indexOf(si));
trace(profs);
if(r.indexOf(si) == -1 && profs.indexOf(si) != -1)
{
r.push(si);
trace(r);
}
}
And here is the output for a particular run's trace statements (from the above code).
GABIOMA1,GABIOMA1-vs-GABIOMA1,MITCHKA1,GIBSOCA1
GABIOMA1 is at index -1
GABIOMA1,MITCHKA1,GIBSOCA1
GABIOMA1 is at index -1
GABIOMA1,MITCHKA1,GIBSOCA1
Final:
As you can see, the function has two results in the searchResults array (of strings). They're both identical since I haven't yet sorted out duplicate search results. "GABIOMA1,GABIOMA1"
Then, I'm comparing that to an array "profs" with these three values: "GABIOMA1,MITCHKA1,GIBSOCA1"
You can see the matching result in index 0 of profs. However, as you see from the next line, profs.indexOf(si)' whensi` is "GABIOMA1" incorrectly returns "-1".
I can write a workaround in a heartbeat, but I need to know why indexOf has decided to bork.
EDIT: Even more infurating, the code in the other branch of the IF statement from this works like a charm...
for each(var s:String in searchResults)
{
if(r.indexOf(s) == -1)
{
r.push(s);
}
}
EDIT 2: This is my WORKING workaround. As you can see, all of the arrays are working properly.
for each(var si:String in searchResults)
{
var match:Boolean = false;
for each(var pi:String in profs)
{
if(si == pi)
{
pr.push(si);
break;
}
}
}
Given your trace(profs) returns a single string, your array contains a string GABIOMA1,MITCHKA1,GIBSOCA1 as a single entity, that's why your index of GABIOMA1 returns -1. You need to do split by comma within getCourseProfs() to get your GABIOMA1 as a separate string.
UPDATE: I have tested with the following:
var t:Array = ['GABIOMA1', 'MISCHKA1', 'GIBSOCA1'];
trace(t);
trace(t.indexOf('GABIOMA1'));
var a:Array = ['MITCHKA1', 'GABIOMA1\x00'];
for each(var si:String in a) trace(si, 'is at index', t.indexOf(si));
This gave me an expected result of -1 for either element of a, and expected trace results as in your example, however, equality test fails. Please try trimming the searchResults strings prior to checking for indexOf().
A side note: placing a \x00 as the first char of a string produces different results for tracing! trace(si, 'is at index', t.indexOf(si)); results in an empty string displayed in place of si like this: is at index -1, while trace(si + ' is at index ' + t.indexOf(si)); results in full string displayed: GABIOMA1 is at index -1. So carefully check either string for non-printable symbols, it's likely there is some hidden zero char that screws indexOf().
Related
This works:
Suppose I want to use in an ArrayFormula() the indirect() function, which doesn't work. That is, supposed I type this in cell E1, =ArrayFormula(indirect(address(row(E:E),column(A:A)))), this will return the value of cell A1 in all the cells in column E. To circumvent this, I created the custom function below:
function retValue(cell){
if(cell.map) {
return cell.map(retValue);
} else {
var cellRang = SpreadsheetApp.getActive().getRange(cell);
return cellRang.getValue();
}
}
Now, when I enter this =ArrayFormula(retValue(address(row(E:E),column(A:A)))) in cell E1, each cell in column E will have the corresponding value of the cell in the same row of column A.
My problem:
What I need is to have a custom function that receives 2 arguments, like function retValue2(cell, anotherRange) but I only care if cell is an array, as anotherRange must be an array anyway. What happens is that when I iteratively call cell.map(retValue2) the argument anotherRange is lost and I'm not entirely sure how to go about this.
I tried to come up with this:
function retValue2(cell, anotherRange) {
if (cell.map) {
return cell.map(retValue2);
} else {
var range = SpreadsheetApp.getActive().getRange(anotherRange);
var nrRows = range.getNumRows();
var nrCols = range.getNumColumns();
return cell + ',' + nrRows + ',' + nrCols;
}
}
But it fails because anotherRange is not recognized inside the iteration I think. How do I solve this?
PS.: in the example that works, why exactly does it work at all? I understand that when I do this return cell.map(retValue); it will use my own function as the callback, which would return all the values in the array, but in the spreadsheet it shows only the one on the same row. What is the magic here?
EDIT:
My end goal is to create my own lookup function where I pass a search key and a 2-dimensional array (rows and columns) and then, it locates the coordinates of that key in the array.
Look here:
function retCoord(sKey, sIRange) {
try {
var key = SpreadsheetApp.getActive().getRange(sKey).getValue();
}
catch(e) {
var key = sKey;
}
var range = SpreadsheetApp.getActive().getRange(sIRange).getValues();
nbRow = range.length;
nbColumn = range[0].length;
for(var i = 0; i<nbRow; i++){
for(var j = 0; j<nbColumn; j++){
if(range[i][j] == key){
return i + ", " + j;
}
}
}
}
If in my spreadsheet I enter something like =retCoord("K4","A:L") it will search the content of cell K4 in my 2-dimensional array A:L and return where in the array the value is, 1, 2 for example. It also works if I use =retCoord(K4,"A:L") or =retCoord("term searched","A:L"), and in this latter case I enter directly the term searched. This works fine until I use it in an ArrayFormula().
First, instead of =retCoord("K4","A:L") I could very well use =retCoord(address(4, 11),"A:L") for instance and my .getRange() method would get the cell K4 just fine.
Now, here is the big problem. I want to use my function in an ArrayFormula(), and, positioning my cursor in O1 and hoping to search the items from column D in the columns E through L I want to pass as one of the inputs of address(), row(O:O), like this: =ArrayFormula(retCoord(address(row(O:O),4,4),"E:L")), meaning that for each row, a new address is passed. that is, in O1 cell, it should return the result of retCoord(D1,"E:L"), in O2 should be retCoord(D2,"E:L"), in O3 should be retCoord(D3,"E:L") and so on.
The issue happens because in my function, sKey is an array and if I try to use the same approach as my function retValue (here above in the This works: section) it fails because now, in retCoord, I have 2 inputs, and the introspection function calling from before fails because of the second input. Of course I'm missing something and there is always a better and more elegant way to approach a problem. But for now, can anyone help me with this one?
EDIT2:
I changed the code a little and it seems I moved forward but not quite yet. Check comment below for line indicated by (*):
function retCoord(sKey, sIRange) {
var key = '';
try {
key = SpreadsheetApp.getActive().getRange(sKey).getValue();
return key;
}
catch(e) {
if (sKey.map) {
var objKey = sKey.map(retCoord);
return objKey; // (*) <--- comments below
key = objKey;
} else {
key = sKey;
}
}
var range = SpreadsheetApp.getActive().getRange(sIRange).getValues();
nbRow = range.length;
nbColumn = range[0].length;
for(var i = 0; i<nbRow; i++){
for(var j = 0; j<nbColumn; j++){
if(range[i][j] == key){
return key + " = "+ i + ", " + j;
}
}
}
}
This (*) line I added only to see what was returning from the map. Surprisingly (in a way), it is an object with all the elements of that column and that is expected. What I didn't expect was that if I return that object to my spreadsheet cell, it brings back only the value of that specific cell (as I wanted but not really as expected). But the problem is I cannot use that object to compare against a string as it will expand and become something else and will never match. Look:
If I do return objKey in my custom function, over cell O1 it returns "a", in O2 it returns
"b", in O3 it returns "c" as expected because those are the values
of my cells D1, D2, D3 respectively.
If I do return "-> " + objKey
in in my custom function, instead of returning -> a, -> b, -> c in O1, O2, O3 respectively, it returns -> =A:A,a,b,c,d,e,f for all the cells in column O, which seems it did some type of objKey.toString() under the hood before concatenating with "-> "
Conclusion: how do I "coerce" the apparent result of objKey into string keeping the apparent result when you return the object without changing it? Simply put, I want the concatenation "some string" + to be equal to "some string" + "one string representing the value in that row instead of an object". In other words, what the hell is happening here? How does Google Sheets now that in that row, that element is the one representing the one I want? This is what I asked in the "PS." in the first part of this post.
You want to search all keys in the sKey column within the Range sIRange and note the position of each key within the range into the corresponding row in a destination column ?
This is how you can do it with Apps Script without formulas:
function retCoord(sKeyColumn, destinationColumn, sIRange) {
var key = sKeyColumn;
var range=SpreadsheetApp.getActive().getRange(sIRange);
var rangeValues = SpreadsheetApp.getActive().getRange(sIRange).getValues();
var nbRow = rangeValues.length;
var nbColumn = rangeValues[0].length;
var sKeyRange=SpreadsheetApp.getActive().getRange(sKeyColumn);
var destinationRange=SpreadsheetApp.getActive().getRange(destinationColumn);
var sKeyValues=sKeyRange.getValues();
for(var k=0;k<sKeyValues.length;k++){
for(var i = 1; i<=nbRow; i++){
for(var j = 1; j<=nbColumn; j++){
if(range.getCell(i, j).getValue() == sKeyValues[k][0]){
destinationRange.getCell(k+1, 1).setValue(range.getCell(i, j).getA1Notation());
}
}
}
}
}
Sample call:
function myFunction(){
retCoord('A1:A6','B1:B6','C1:J7');
}
retCoord('A:A','B:B','C:J'); would also work but would take very long, since the code would also loop through empty rows
first question ever here...
I am coding a simple 3-card poker hand evaluator and am having problems finding/extracting multiple "straights" (sequential series of values) from an array of values.
I need to extract and return EVERY straight the array possibly has. Here's an example:
(assume array is first sorted numerically incrementing)
myArray = [1h,2h,3c,3h,4c]
Possible three-value sequences are:
[1h,2h,3c]
[1h,2h,3h]
[2h,3c,4c]
[2h,3h,4c]
Here is my original code to find sequences of 3, where the array contains card objects with .value and .suit. For simplicity in this question I just put "2h" etc here:
private var _pokerHand = [1h,2h,3c,3h,4c];
private function getAllStraights(): Array
{
var foundStraights:Array = new Array();
for (var i: int = 0; i < (_handLength - 2); i++)
{
if ((_pokerHand[i].value - _pokerHand[i + 1].value) == 1 && (_pokerHand[i + 1].value - _pokerHand[i + 2].value) == 1)
{
trace("found a straight!");
foundStraights.push(new Array(_pokerHand[i], _pokerHand[i + 1], _pokerHand[i + 2]));
}
}
return foundStraights;
}
but it of course fails when there are value duplicates (like the 3's above). I cannot discard duplicates because they could be of different suits. I need every possible straight as in the example above. This allows me to run the straights through a "Flush" function to find "straight flush".
What array iteration technique am I missing?
This is an interesting problem. Given the popularity of poker games (and Flash) I'm sure this has been solved many times before, but I couldn't find an example online. Here's how I would approach it:
Look at it like a path finding problem.
Begin with every card in the hand as the start of a possible path (straight).
While there are possible straights:
Remove one from the list.
Find all the next valid steps, (could be none, or up to 4 following cards with the same value), and for each next valid step:
If it reaches the goal (completes a straight) add it to a list of found straights.
Otherwise add the possible straight with the next step back to the stack.
This seems to do what you want (Card object has .value as int):
private function getAllStraights(cards:Vector.<Card>, straightLength:uint = 3):Vector.<Vector.<Card>> {
var foundStraights:Vector.<Vector.<Card>> = new <Vector.<Card>>[];
var possibleStraights:Vector.<Vector.<Card>> = new <Vector.<Card>>[];
for each (var startingCard:Card in cards) {
possibleStraights.push(new <Card>[startingCard]);
}
while (possibleStraights.length) {
var possibleStraight:Vector.<Card> = possibleStraights.shift();
var lastCard:Card = possibleStraight[possibleStraight.length - 1];
var possibleNextCards:Vector.<Card> = new <Card>[];
for (var i:int = cards.indexOf(lastCard) + 1; i < cards.length; i++) {
var nextCard:Card = cards[i];
if (nextCard.value == lastCard.value)
continue;
if (nextCard.value == lastCard.value + 1)
possibleNextCards.push(nextCard);
else
break;
}
for each (var possibleNextCard:Card in possibleNextCards) {
var possibleNextStraight:Vector.<Card> = possibleStraight.slice().concat(new <Card>[possibleNextCard]);
if (possibleNextStraight.length == straightLength)
foundStraights.push(possibleNextStraight);
else
possibleStraights.push(possibleNextStraight);
}
}
return foundStraights;
}
Given [1♥,2♥,3♣,3♥,4♣] you get: [1♥,2♥,3♣], [1♥,2♥,3♥], [2♥,3♣,4♣], [2♥,3♥,4♣]
It gets really interesting when you have a lot of duplicates, like [1♥,1♣,1♦,1♠,2♥,2♣,3♦,3♠,4♣,4♦,4♥]. This gives you:
[1♥,2♥,3♦], [1♥,2♥,3♠], [1♥,2♣,3♦], [1♥,2♣,3♠], [1♣,2♥,3♦], [1♣,2♥,3♠], [1♣,2♣,3♦], [1♣,2♣,3♠], [1♦,2♥,3♦], [1♦,2♥,3♠], [1♦,2♣,3♦], [1♦,2♣,3♠], [1♠,2♥,3♦], [1♠,2♥,3♠], [1♠,2♣,3♦], [1♠,2♣,3♠], [2♥,3♦,4♣], [2♥,3♦,4♦], [2♥,3♦,4♥], [2♥,3♠,4♣], [2♥,3♠,4♦], [2♥,3♠,4♥], [2♣,3♦,4♣], [2♣,3♦,4♦], [2♣,3♦,4♥], [2♣,3♠,4♣], [2♣,3♠,4♦], [2♣,3♠,4♥]
I haven't checked this thoroughly but it looks right at a glance.
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.
I'm trying to search a substring in an array of Strings. I'm using the following code (in Unity3):
var obstacles = ["Border", "Boundary", "BoundaryFlame"];
var frontAvailable = true;
var leftAvailable = true;
var rightAvailable = true;
var hitFront: RaycastHit;
if (Physics.Raycast(transform.position, transform.position + transform.forward, hitFront, 1.5)) {
Debug.Log("I hit this in front: ");
Debug.Log(hitFront.collider.gameObject.name);
for (var i = 0; i < obstacles.length; i++)
{
if (obstacles[i].IndexOf(hitFront.collider.gameObject.name) > -1)
{
Debug.Log("Hit in front!");
frontAvailable = false;
}
}
}
The problem is, the Debug.Log shows Boundary(Clone). I've included Boundary in the array obstacles. Shouldn't below code set frontAvailable to false? Or did I make a mistake here?
In addition to Kolink's answer, your if is looking for Boundary(clone) at the beginning of Boundary, rather than the other way around. I think you're looking for:
if (hitFront.collider.gameObject.name.IndexOf(obstacles[i]) >= 0)
I think you need indexOf, not IndexOf. Assuming you're talking about the native string function.
In addition, indexOf returns -1 if there is no match, 0 if the match is at the start, 1, 2, 3... for further positions. So you need > -1 instead of > 0
suppose you have an array with a number of strings in ActionScript3 and you want to test if a test string is "in" that array. "in" only works against the index with Arrays in AS3 (which is totally retardo if you ask me), though it does work with ojects, but we're not talking about objects.
Can someone improve (reduce) on this code I'm using now? I'm hoping to avoid defining a utility function - I'd like a nice elegant one-liner.
myArray.filter(function(item:*, i:int, a:Array) { return (item == testString); }).length
Since 0 == false we can use it in a test. Do note that testString's scope is defined in the containing function, encapsulated by the closure.
if (allowedProfiles.filter(function(item:*, i:int, a:Array) { return (item == name); }).length){ // yay! }
Use the Array.indexOf() method to see that the index of the string in the array is not -1 (not found):
var myArray:Array = ["hello", "world"];
trace(myArray.indexOf("hello")); // == 0;
trace(myArray.indexOf("goodbye")); // == -1
Why not just use indexOf()?
if(myArray.indexOf("testString") != -1) { // it's in there