I'm looking to highlight text in a textarea by specifying a background color and a range of indexes. I'll then be constantly changing and updating the highlighted text dynamically as the user types.
jquery.highlightTextarea is almost perfect. The problem is that it appears that it wasn't really designed to update the highlighted text on the fly like this. And in addition to that, the author has now discontinued it and won't be updating it anymore.
I even went so far as to look at Ace Code Editor, but the ability to highlight based on simple indexes wasn't really there with that one (you have to highlight based on the rows/columns). Plus it seemed like it was overkill for what I'm doing.
So I'm looking for alternatives. In addition to the features I mentioned, performance is high on my list of priorities as well. I'd like to compile a list of libraries to consider. Any suggestions?
I finally figured something out that's working well for me. I'm using the Ace Editor. I went back and forth between Ace and CodeMirror, and finally settled on Ace because I felt like this particular task was a little more straightforward and the documentation is better too. I say straightforward, but the complication is that I have to convert index ranges into column / row ranges because that's the format the Range object in Ace is expecting.
But here's what I came up with:
function HighlightMatches(matches) {
var matchClass = "myMarker";
var oldMarkers = inputEditor.session.$backMarkers;
// clear out old markers
for (var prop in oldMarkers) {
if (oldMarkers[prop].clazz == matchClass) {
inputEditor.session.removeMarker(oldMarkers[prop].id);
}
}
if (matches && matches.length > 0) {
var ranges = NormalizedRanges(GetInput(), matches);
// add new markers
for (var i = 0; i < ranges.length; i++) {
inputEditor.session.addMarker(new Range(ranges[i].startRow, ranges[i].startCol, ranges[i].endRow, ranges[i].endCol), matchClass);
}
}
}
function NormalizedRanges(input, matches) {
var ranges = [];
var startCol = 0;
var endCol = 0;
var startRow = 0;
var endRow = 0;
var currentMatch = 0;
var matchStart = 0;
var matchLength = 0;
var matchEnd = 0;
for (var i = 0; i < input.length; i++) {
matchStart = matches[currentMatch].index;
matchLength = matches[currentMatch].length;
matchEnd = matchStart + matchLength;
// process match
if (i >= matchStart && i <= matchEnd) {
endCol = startCol;
endRow = startRow;
for (var k = 0; k < matchLength; k++) {
endCol++;
var advanceNewLine = AdvanceNewLine(input.substr(i + k, 2))
if (advanceNewLine > 0) {
endRow++;
endCol = 0;
if (advanceNewLine == 2)
k++;
}
}
ranges.push(new IndexRange(startCol, endCol, startRow, endRow));
startCol = endCol;
startRow = endRow;
// set index to end of match
i = matchEnd - 1;
// advance current match
currentMatch++;
if (currentMatch == matches.length)
return ranges;
}
else {
// advance range
startCol++;
var advanceNewLine = AdvanceNewLine(input.substr(i, 2));
if (advanceNewLine > 0) {
startRow++;
startCol = 0;
if (advanceNewLine == 2)
i++;
}
}
}
return ranges;
}
function AdvanceNewLine(text) {
if (text == '\r\n')
return 2;
else {
var char = text.charAt(0);
if (char == '\n' || text == '\r')
return 1;
else
return 0;
}
}
function IndexRange(startCol, endCol, startRow, endRow) {
this.startCol = startCol;
this.endCol = endCol;
this.startRow = startRow;
this.endRow = endRow;
}
You simply call HighlightMatches and pass in a matches JSON object that might look something like this:
{"matches":[{"index":0,"length":1},{"index":4,"length":1}]}
Related
I'm trying to get N ways of solves a N rook problem. The issue I am having is currently, I seem to get n*n solutions while it needs to be N! . Below is my code, I have written it in simple loops and functions, so it's quite long. Any help would be greatly appreciated
Note: Please ignore case for n = 2. I get some duplicates which I thought I would handle via JSON.stringify
var createMatrix = function (n) {
var newMatrix = new Array(n);
// build matrix
for (var i = 0; i < n; i++) {
newMatrix[i] = new Array(n);
}
for (var i = 0; i < n; i++) {
for (var j = 0; j < n; j++) {
newMatrix[i][j] = 0;
}
}
return newMatrix;
};
var newMatrix = createMatrix(n);
// based on rook position, greying out function
var collision = function (i, j) {
var col = i;
var row = j;
while (col < n) {
// set the row (i) to all 'a'
col++;
if (col < n) {
if (newMatrix[col][j] !== 1) {
newMatrix[col][j] = 'x';
}
}
}
while (row < n) {
// set columns (j) to all 'a'
row++;
if (row < n) {
if (newMatrix[i][row] !== 1) {
newMatrix[i][row] = 'x';
}
}
}
if (i > 0) {
col = i;
while (col !== 0) {
col--;
if (newMatrix[col][j] !== 1) {
newMatrix[col][j] = 'x';
}
}
}
if (j > 0) {
row = j;
while (row !== 0) {
row--;
if (newMatrix[i][row] !== 1) {
newMatrix[i][row] = 'x';
}
}
}
};
// checks position with 0 and sets it with Rook
var emptyPositionChecker = function (matrix) {
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix.length; j++) {
if (matrix[i][j] === 0) {
matrix[i][j] = 1;
collision(i, j);
return true;
}
}
}
return false;
};
// loop for every position on the board
loop1:
for (var i = 0; i < newMatrix.length; i++) {
var row = newMatrix[i];
for (var j = 0; j < newMatrix.length; j++) {
// pick a position for rook
newMatrix[i][j] = 1;
// grey out collison zones due to the above position
collision(i, j);
var hasEmpty = true;
while (hasEmpty) {
//call empty position checker
if (emptyPositionChecker(newMatrix)) {
continue;
} else {
//else we found a complete matrix, break
hasEmpty = false;
solutionCount++;
// reinitiaze new array to start all over
newMatrix = createMatrix(n);
break;
}
}
}
}
There seem to be two underlying problems.
The first is that several copies of the same position are being found.
If we consider the case of N=3 and we visualise the positions by making the first rook placed red, the second placed green and the third to be placed blue, we get these three boards:
They are identical positions but will count as 3 separate ones in the given Javascript.
For a 3x3 board there are also 2 other positions which have duplicates. The gets the count of unique positions to 9 - 2 - 1 -1 = 5. But we are expecting N! = 6 positions.
This brings us to the second problem which is that some positions are missed. In the case of N=3 this occurs once when i===j==1 - ie the mid point of the board.
This position is reached:
This position is not reached:
So now we have the number of positions that should be found as 9 - 2 - 1 - 1 +1;
There appears to be nothing wrong with the actual Javascript in as much as it is implementing the given algorithm. What is wrong is the algorithm which is both finding and counting duplicates and is missing some positions.
A common way of solving the N Rooks problem is to use a recursive method rather than an iterative one, and indeed iteration might very soon get totally out of hand if it's trying to evaluate every single position on a board of any size.
This question is probably best taken up on one of the other stackexchange sites where algorithms are discussed.
How do I create an array of values that are in a set of cells in Google sheets?
The array should be the same rows and columns as the cells and it should have the same values as the sheet has in each location.
Also, I want to be able to pass the range of the array in as a parameter so that I can use the function for different ranges.
edit 2:
New code, nearly working, I just need to have it receive the ranges from user input on the google sheet itself. This is what I am trying to get work, but the beginning is struggling to work, I can't pass in a choice of ranges and have the cell update and have the function run.
Also, I am having a problem with getting a reference error almost every time even when I try to preset the ranges within the function without any parameters
function sortingtest(pWO, pInfo, pSearch) {
var WO = SpreadsheetApp.getActiveSpreadsheet().getRange(pWO).getValues();
var Info = SpreadsheetApp.getActiveSpreadsheet().getRange(pInfo).getValues();
var Search = SpreadsheetApp.getActiveSpreadsheet().getRange(pSearch).getValues();
//[row][column]
var FinalArray1 = [];
var FinalArray2 = [];
var FinalArray3 = [];
var LastArray = [];
var a = 0;
var b = 0;
var c = 0;
var d = 0;
for (var row = 0; row < WO.length; row ++) {
var counter = row - 1;
while (WO[row] == "") {
WO[row] = WO[counter];
counter--;
}
}
for (var col = 0; col < Info[0].length; col++) {
for (var row = 0; row < Info.length; row++) {
if (Info[row][col] == Search[col]) {
if (col == 0) {
FinalArray1[a] = WO[row];
a++;
}
else if (col == 1) {
FinalArray2[b] = WO[row];
b++;
}
else if (col == 2) {
FinalArray3[c] = WO[row];
c++;
}
}
}
}
for (var i = 0; i < FinalArray1.length; i++) {
for (var j = 0; j < FinalArray2.length; j++) {
for (var k = 0; k < FinalArray3.length; k++) {
if (FinalArray3[k] == FinalArray2[j] && FinalArray2[j] == FinalArray1[i]) {
LastArray[d] = FinalArray1[i];
d++;
}
}
}
}
return LastArray;
}
If you call your function from within the spreadsheet as you indicate it in your comment (=sortingtest(sheet1!A1:C12,sheet3!D1:E12,sheet2!F1:G4)), you do not need to call any of the SpreadsheetApp functions to get arrays: pWO, pInfo and pSearch will already be 2 dimensional arrays.
Quoting the Google custom function article:
If you call your function with a reference to a range of cells as an argument (like =DOUBLE(A1:B10)), the argument will be a two-dimensional array of the cells' values. For example, in the screenshot below, the arguments in =DOUBLE(A1:B2) are interpreted by Apps Script as double([[1,3],[2,4]]).
I'e been trying to write one and it's getting messy!
Suppose I have two strings textStart, textTarget and I want to keep track of the characters I would need to add and remove from textStart in order to product textTarget.
For instance, if textStart = "dude" and textTarget = "deck", then characters that would need to be added would be 'c' and 'k' and the characters that would need to be substracted would be the 'u' and one of the 'd's.
I'm thinking that I first need to create maps that represent the number of each character in textStart and textTarget.
So I wrote this:
var startChars = {};
for (var k = 0, n = textStart.length; k < n; ++k)
{
if (textStart[k] in startChars)
++startChars[textStart[k]];
else
startChars[textStart[k]] = 1;
}
var targetChars = {};
for (var k = 0, n = textTarget.length; k < n; ++k)
{
if (textTarget[k] in startChars)
++targetChars[textTarget[k]];
else
map1[targetChars[k]] = 1;
}
Which would give me
startChars['d']=2,
startChars['u']=1,
startChars['e']=1
and
targetChars['d']=1,
targetChars['e']=1,
targetChars['c']=1,
targetChars['k']=1
Then I can make create maps needAdded and needRemoved that look at the difference in the above two maps:
var needAdded = {};
var needRemoved = {};
I'm not sure how to fill those maps as intended, because I don't know how to iterate through the keys of a map using JavaScript. I somehow need to end up with
needAdded['c']=1,
needAdded['k']=1,
needRemoved['u']=1,
needRemoved['d']=1
That's where you guys come in and help me.
I hope I've done a good job describing what I'm trying to do and how I've tried to do it so far. My programming intuition tells me that I'm writing too many lines of code and that I need to consult StackOverflow for help. Any way to do this elegantly without JQuery or Regex? I know someone's going to come in this thread and write a 1-line Regex solution or something like that.
var s = 'dude',
t = 'deck',
finalOutput = '';
for (var i = 0; i < s.length; i++){
if ( typeof t[i] != 'undefined' ){
if ( s[i] != t[i] ){
console.log(s[i] + ' changed to ' + t[i]);
s[i] = t[i];
finalOutput += t[i];
} else{
finalOutput += s[i];
}
}
}
console.log('FINAL: ' + finalOutput);
Here's a jsfiddle I just spent way too much time on... hopefully it makes sense :)
var textStart = 'dude';
var textTarget = 'deck';
var startChars = {};
for (var k = 0, n = textStart.length; k < n; ++k)
{
if (textStart[k] in startChars)
++startChars[textStart[k]];
else
startChars[textStart[k]] = 1;
}
var targetChars = {};
for (var k = 0, n = textTarget.length; k < n; ++k)
{
if (textTarget[k] in targetChars)
++targetChars[textTarget[k]];
else
targetChars[textTarget[k]] = 1;
}
console.log('start: ' + JSON.stringify(startChars));
console.log('target: ' + JSON.stringify(targetChars));
var needAdded = {};
var needRemoved = {};
for (var c in startChars) {
// If target does not contain letter, remove all, otherwise remove excess
if (targetChars[c] > 0) {
if (startChars[c] > targetChars[c])
needRemoved[c] = startChars[c] - targetChars[c];
else if (startChars[c] < targetChars[c])
needAdded[c] = targetChars[c] - startChars[c];
} else {
needRemoved[c] = startChars[c];
}
}
for (var c in targetChars) {
// If start does not contain letter, add all, otherwise add excess
if (startChars[c] > 0) {
if (startChars[c] > targetChars[c])
needRemoved[c] = startChars[c] - targetChars[c];
else if (startChars[c] < targetChars[c])
needAdded[c] = targetChars[c] - startChars[c];
} else {
needAdded[c] = targetChars[c];
}
}
console.log('needAdded: ' + JSON.stringify(needAdded));
console.log('needRemoved: ' + JSON.stringify(needRemoved));
The output is as follows:
start: {"d":2,"u":1,"e":1}
target: {"d":1,"e":1,"c":1,"k":1}
needAdded: {"c":1,"k":1}
needRemoved: {"d":1,"u":1}
Ok, also too much time on this:
var textStart = "dude";
var textTarget = "duck";
var map = {};
MapCharacters(textStart, map, 1);
MapCharacters(textTarget, map, -1);
console.log(map);
var toDelete = [];
var toAdd = [];
for (var prop in map) {
if (map.hasOwnProperty(prop)) {
while (map[prop] > 0) {
toDelete.push(prop);
map[prop]--;
}
while (map[prop] < 0) {
toAdd.push(prop);
map[prop]++;
}
}
}
console.log(toDelete);
console.log(toAdd);
function MapCharacters(string, map, add) {
for (var k = 0, n = string.length; k < n; ++k) {
if (string[k] in map) {
map[string[k]] += add;
} else {
map[string[k]] = add;
}
}
}
http://jsfiddle.net/nSV2J/1/
It could probably be done more efficiently, but as I said - too much time!
I realized that the best way to do this is not to make two maps, but just one. In the first case you increment the count for each letter and in the second case you decrease it. Now it's easy to find which ones need to be removed (the ones that end up > 0) and which ones need to be added (the ones that end up < 0)
How do I generate objects on a map, without them occupying the same space or overlapping on a HTML5 Canvas?
X coordinate is randomly generated, to an extent. I thought checking inside the array to see if it's there already, and the next 20 values after that (to account for the width), with no luck.
var nrOfPlatforms = 14,
platforms = [],
platformWidth = 20,
platformHeight = 20;
var generatePlatforms = function(){
var positiony = 0, type;
for (var i = 0; i < nrOfPlatforms; i++) {
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
var positionx = (Math.random() * 4000) + 500 - (points/100);
var duplicatetest = 21;
for (var d = 0; d < duplicatetest; d++) {
var duplicate = $(jQuery.inArray((positionx + d), platforms));
if (duplicate > 0) {
var duplicateconfirmed = true;
}
}
if (duplicateconfirmed) {
var positionx = positionx + 20;
}
var duplicateconfirmed = false;
platforms[i] = new Platform(positionx,positiony,type);
}
}();
I originally made a cheat fix by having them generate in an area roughly 4000 big, decreasing the odds, but I want to increase the difficulty as the game progresses, by making them appear more together, to make it harder. But then they overlap.
In crude picture form, I want this
....[]....[].....[]..[]..[][]...
not this
......[]...[[]]...[[]]....[]....
I hope that makes sense.
For reference, here is the code before the array check and difficulty, just the cheap distance hack.
var nrOfPlatforms = 14,
platforms = [],
platformWidth = 20,
platformHeight = 20;
var generatePlatforms = function(){
var position = 0, type;
for (var i = 0; i < nrOfPlatforms; i++) {
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
platforms[i] = new Platform((Math.random() * 4000) + 500,position,type);
}
}();
EDIT 1
after some debugging, duplicate is returning as [object Object] instead of the index number, not sure why though
EDIT 2
the problem is the objects are in the array platforms, and x is in the array object, so how can I search inside again ? , that's why it was failing before.
Thanks to firebug and console.log(platforms);
platforms = [Object { image=img, x=1128, y=260, more...}, Object { image=img, x=1640, y=260, more...} etc
You could implement a while loop that tries to insert an object and silently fails if it collides. Then add a counter and exit the while loop after a desired number of successful objects have been placed. If the objects are close together this loop might run longer so you might also want to give it a maximum life span. Or you could implement a 'is it even possible to place z objects on a map of x and y' to prevent it from running forever.
Here is an example of this (demo):
//Fill an array with 20x20 points at random locations without overlap
var platforms = [],
platformSize = 20,
platformWidth = 200,
platformHeight = 200;
function generatePlatforms(k) {
var placed = 0,
maxAttempts = k*10;
while(placed < k && maxAttempts > 0) {
var x = Math.floor(Math.random()*platformWidth),
y = Math.floor(Math.random()*platformHeight),
available = true;
for(var point in platforms) {
if(Math.abs(point.x-x) < platformSize && Math.abs(point.y-y) < platformSize) {
available = false;
break;
}
}
if(available) {
platforms.push({
x: x,
y: y
});
placed += 1;
}
maxAttempts -= 1;
}
}
generatePlatforms(14);
console.log(platforms);
Here's how you would implement a grid-snapped hash: http://jsfiddle.net/tqFuy/1/
var can = document.getElementById("can"),
ctx = can.getContext('2d'),
wid = can.width,
hei = can.height,
numPlatforms = 14,
platWid = 20,
platHei = 20,
platforms = [],
hash = {};
for(var i = 0; i < numPlatforms; i++){
// get x/y values snapped to platform width/height increments
var posX = Math.floor(Math.random()*(wid-platWid)/platWid)*platWid,
posY = Math.floor(Math.random()*(hei-platHei)/platHei)*platHei;
while (hash[posX + 'x' + posY]){
posX = Math.floor(Math.random()*wid/platWid)*platWid;
posY = Math.floor(Math.random()*hei/platHei)*platHei;
}
hash[posX + 'x' + posY] = 1;
platforms.push(new Platform(/* your arguments */));
}
Note that I'm concatenating the x and y values and using that as the hash key. This is to simplify the check, and is only a feasible solution because we are snapping the x/y coordinates to specific increments. The collision check would be more complicated if we weren't snapping.
For large sets (seems unlikely from your criteria), it'd probably be better to use an exclusion method: Generate an array of all possible positions, then for each "platform", pick an item from the array at random, then remove it from the array. This is similar to how you might go about shuffling a deck of cards.
Edit — One thing to note is that numPlatforms <= (wid*hei)/(platWid*platHei) must evaluate to true, otherwise the while loop will never end.
I found the answer on another question ( Searching for objects in JavaScript arrays ) using this bit of code to search the objects in the array
function search(array, value){
var j, k;
for (j = 0; j < array.length; j++) {
for (k in array[j]) {
if (array[j][k] === value) return j;
}
}
}
I also ended up rewriting a bunch of the code to speed it up elsewhere and recycle platforms better.
it works, but downside is I have fewer platforms, as it really starts to slow down. In the end this is what I wanted, but its no longer feasible to do it this way.
var platforms = new Array();
var nrOfPlatforms = 7;
platformWidth = 20,
platformHeight = 20;
var positionx = 0;
var positiony = 0;
var arrayneedle = 0;
var duplicatetest = 21;
function search(array, value){
var j, k;
for (j = 0; j < array.length; j++) {
for (k in array[j]) {
if (array[j][k] === value) return j;
}
}
}
function generatePlatforms(ind){
roughx = Math.round((Math.random() * 2000) + 500);
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
var duplicate = false;
for (var d = 0; d < duplicatetest; d++) {
arrayneedle = roughx + d;
var result = search(platforms, arrayneedle);
if (result >= 0) {
duplicate = true;
}
}
if (duplicate = true) {
positionx = roughx + 20;
}
if (duplicate = false) {
positionx = roughx;
}
platforms[ind] = new Platform(positionx,positiony,type);
}
var generatedplatforms = function(){
for (var i = 0; i < nrOfPlatforms; i++) {
generatePlatforms(i);
};
}();
you go big data, generate all possibilities, store each in an array, shuffle the array,
trim the first X items, this is your non heuristic algorithm.
I'm creating a program that will ask a question and give 5 choices for answers.
One is pre-defined and is correct, the others I want to be random selections from a bank of answers and the entire array is to be shuffled too.
I've written something, but it has some inconsistencies.
For one, sometimes the pre-defined choice appears twice in the list (it appears to skip over my if check).
Another is that sometimes, the editor crashes when I run it.
I use for in loops and I'm worried the crash is caused by a never-ending loop.
Here's my code:
private var numberOfComponents:int;
private var maxComponents:int = 5;
//numberOfComponents returns the length property of my 'components' answer bank
componentsSelection = buildComponentSelectionList(0); //0 is the index of my correct answer
function buildComponentSelectionList(correctItemIndex){
var theArray:Array = new Array();
var indicesOfSelection:Array = getIndicesByIncluding(correctItemIndex);
Debug.Log(indicesOfSelection);
for (var i=0;i<indicesOfSelection.length;i++)
theArray.Push(components[indicesOfSelection[i]]);
return theArray;
}
function getIndicesByIncluding(correctItem){
var indicesArray:Array = new Array();
var numberOfChoices = maxComponents-1;
for(var i=0;i<numberOfChoices;i++){
var number = Mathf.Round(Random.value*(numberOfComponents-1));
addToRandomNumberSelection(indicesArray, number,correctItem);
}
indicesArray.Push(correctItem);
RandomizeArray(indicesArray);
return indicesArray;
}
function addToRandomNumberSelection(indicesArray:Array,number,correctItem){
if(indicesArray.length == 0){
indicesArray.Push(number);
} else {
var doesntExist = true;
for(var i=0;i<indicesArray.length;i++){
if(indicesArray[i] == correctItem)
doesntExist = false;
if (indicesArray[i] == number)
doesntExist = false;
}
if(doesntExist) {
indicesArray.Push(number);
} else {
addToRandomNumberSelection(indicesArray, Mathf.Round(Random.value*(numberOfComponents-1)),correctItem);
}
}
}
function RandomizeArray(arr : Array)
{
for (var i = arr.length - 1; i > 0; i--) {
var r = Random.Range(0,i);
var tmp = arr[i];
arr[i] = arr[r];
arr[r] = tmp;
}
}
The editor is Unity3D, and the code is a version of JavaScript; I think my error is a logic one, rather than a syntactical one.
I feel I've been staring at this code for too long now and I'm missing something obvious.
Can anybody help me?
You can loop through the options and determine the probability that it should be included, then shuffle the included options:
function getRandomOptions(allOptions, correctIndex, count){
var result = [allOptions[correctIndex]];
count--;
var left = allOptions.length;
for (var i = 0; count > 0; i++) {
if (i != correctIndex && Math.floor(Math.random() * left) < count) {
result.push(allOptions[i]);
count--;
}
left--;
}
shuffleArray(result);
return result;
}
function shuffleArray(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var r = Math.floor(Math.random() * i);
var tmp = arr[i];
arr[i] = arr[r];
arr[r] = tmp;
}
}
Demo: http://jsfiddle.net/Guffa/wXsjz/