I'm trying to create a key mapping that keeps track of the frequency for each character of a string in my createArrayMap() function but I keep getting this error from firebug: TypeError: str.charAt(...) is not a function
I found the charAt() function on Mozilla's developer website it should be a function that exists.
var input;
var container;
var str;
var arrMapKey = [];
var arrMapValue = [];
function initDocElements() {
container = document.getElementById("container");
input = document.getElementById("inputbox");
}
function createArrayMap() {
str = input.value;
for (var i = 0; i < str.length; i++) {
if (arrMapKey.find(str.charAt(i)) == undefined) {
arrMapKey.push(str.charAt(i));
arrMapValue.push(1);
}
}
}
function keyPressHandler() {
createArrayMap();
console.log(arrMapKey);
console.log(arrMapValue);
}
function prepareEventHandlers() {
input.onfocus = function() {
if (this.value == "Start typing here!") {
this.value = "";
}
};
input.onblur = function() {
if (this.value == "") {
this.value = "Start typing here!";
}
};
input.onkeyup = keyPressHandler;
}
window.onload = function() {
initDocElements();
prepareEventHandlers();
};
The problem is not with String.charAt(), but with Array.find().
The first argument to find is a callback, but the result of str.charAt(i) is a character and not a callback function.
To search for an element in your array, you could use Array.indexOf() as #adeneo already suggested in a comment
function createArrayMap() {
var str = input.value;
for (var i = 0; i < str.length; i++) {
if (arrMapKey.indexOf(str.charAt(i)) == -1) {
arrMapKey.push(str.charAt(i));
arrMapValue.push(1);
}
}
}
See JSFiddle
You're not going about things in the most efficient manner... What if you changed it to look like this so you are continually updated with each keypress?
var keyMap = {};
...
input.onkeyup = keyPressHandler;
function keyPressHandler(e) {
var char = String.fromCharCode(e.keyCode);
if(!(char in keyMap))
keyMap[char] = 1;
else
keyMap[char]++;
}
This has been answered, but here's my version of your problem JSBIN LINK (also has an object option in addition to the array solution).
I moved some variables around so you'll have less global ones, added comments, and mocked with the output so it'll show it on the page instead of the console.
besides the Array.find() issues, you weren't initializing your arrays on the build method, and so, you would have probably ended with the wrong count of letters.
HTML:
<div id="container">
<textArea id="inputbox"></textArea></div>
<p id="output">output will show here</p>
JS:
var input, // Global variables
container, //
output; //
/**
* Initialize components
*/
function initDocElements() {
container = document.getElementById("container");
input = document.getElementById("inputbox");
output = document.getElementById("output");
}
/**
* Creates the letters frequency arrays.
* Note that every time you click a letter, this is done from scratch.
* Good side: no need to deal with "backspace"
* Bad side: efficiency. Didn't try this with huge texts, but you get the point ...
*/
function createArrayMap() {
var index, // obvious
tempChar, // temp vars for: char
tempStr = input.value, // string
len = tempStr.length, // for loop iteration
arrMapKey = [], // our keys
arrMapValue = []; // our values
for (var i = 0 ; i <len ; i++) {
// These 2 change each iteration
tempChar = tempStr.charAt(i);
index = arrMapKey.indexOf(tempChar);
// If key exists, increment value
if ( index > -1) {
arrMapValue[index]++;
}
// Otherwise, push to keys array, and push 1 to value array
else {
arrMapKey.push(tempChar);
arrMapValue.push(1);
}
}
// Some temp output added, instead of cluttering the console, to the
// a paragraph beneath the text area.
output.innerHTML = "array keys: "+arrMapKey.toString() +
"<br/>array values:"+arrMapValue.toString();
}
function keyPressHandler() {
createArrayMap();
}
function prepareEventHandlers() {
input.onfocus = function() {
if (this.value == "Start typing here!") {
this.value = "";
}
};
input.onblur = function() {
if (this.value === "") {
this.value = "Start typing here!";
}
};
input.onkeyup = keyPressHandler;
}
window.onload = function() {
initDocElements();
prepareEventHandlers();
};
BTW, as the comments suggest, doing this with an object will is much nicer and shorter, since all you care is if the object has the current char as a property:
/**
* Same as above method, using an object, instead of 2 arrays
*/
function createObject() {
var index, // obvious
tempChar, // temp vars for: char
tempStr = input.value, // string
len = tempStr.length, // for loop iteration
freqObj = {}; // our frequency object
for (var i = 0 ; i <len ; i++) {
tempChar = tempStr.charAt(i); // temp char value
if (freqObj.hasOwnProperty(tempChar))
freqObj[tempChar]++;
else
freqObj[tempChar] = 1;
}
}
Related
I'm working on a simon game and is doing a sequence of 3 at level 2 instead of doing just 2 at level 2. I've looked all over. and I've trying output to console, but I guess I've been staring at this for too long. If someone can find the bug, please share. thanks for the help.
here's the pen
https://codepen.io/zentech/pen/XaYygR
//variables
userSeq = [];
simonSeq = [];
const NUM_OF_LEVELS = 5;
var id, color, level = 0;
var strict = false;
var error = false;
var boardSound = [
"http://www.soundjay.com/button/sounds/button-4.mp3", //green
"http://www.soundjay.com/button/sounds/button-09.mp3", //red
"http://www.soundjay.com/button/sounds/button-10.mp3", //yellow
"http://www.soundjay.com/button/sounds/button-7.mp3" //blue
];
//1- start board sequence
$(document).ready(function() {
$(".start").click(function() {
strict = false;
error = false;
level++;
simonSeq = userSeq = [];
simonSequence();
})
//user pad listener
$(".pad").click(function() {
id = $(this).attr("id");
color = $(this).attr("class").split(" ")[1];
userSequence();
});
//strict mode listener
$(".strict").click(function() {
level = 0;
level++;
simonSeq = userSeq = [];
strict = true;
simonSequence();
})
})
//user sequence
function userSequence() {
userSeq.push(id);
console.log(id+" "+color);
addClassSound(id, color);
//check user sequence
if(!checkUserSeq()) {
//if playing strict mode reset everything lol
if(strict) {
console.log("strict");
simonSeq = [];
level = 1;
}
displayError();
userSeq = [];
error = true;
console.log("start simon error")
simonSequence();
}
//checking end of sequence
else if(userSeq.length == simonSeq.length && userSeq.length < NUM_OF_LEVELS) {
level++;
userSeq = [];
error = false;
console.log("start simon")
simonSequence();
}
//checking for winners
if(userSeq.length == NUM_OF_LEVELS) {
displayWinner();
resetGame();
}
}
/* simon sequence */
function simonSequence() {
console.log("level "+level);
$(".display").text(level);
if(!error) {
getRandomNum();
}
var i = 0;
var myInterval = setInterval(function() {
id = simonSeq[i];
color = $("#"+id).attr("class");
color = color.split(" ")[1];
console.log(id+" "+color);
addClassSound(id, color);
i++;
if(i == simonSeq.length) {
clearInterval(myInterval);
}
}, 1000);
}
//generate random number
function getRandomNum() {
var random = Math.floor(Math.random() * 4);
simonSeq.push(random);
}
/* add temporary class and sound */
function addClassSound(id, color) {
$("#"+id).addClass(color+"-active");
playSound(id)
setTimeout(function(){
$("#"+id).removeClass(color+"-active");
}, 500);
}
/* checking user seq against simon's */
function checkUserSeq() {
for(var i = 0; i < userSeq.length; i++) {
if(userSeq[i] != simonSeq[i]) {
return false;
}
}
return true;
}
/* display error */
function displayError() {
console.log("error");
var counter = 0;
var myError = setInterval(function() {
$(".display").text("Err");
counter++;
if(counter == 3) {
$(".display").text(level);
clearInterval(myError);
userSeq = [];
counter = 0;
}
}, 500);
}
//display winner
function displayWinner() {
var count = 0;
var winInterval = setInterval(function() {
count++;
$(".display").text("Win");
if(count == 5) {
clearInterval(winInterval);
$(".display").text("00");
count = 0;
}
}, 500);
}
/* play board sound */
function playSound(id) {
var sound = new Audio(boardSound[id]);
sound.play();
}
/* reset game */
function resetGame() {
userSeq = [];
simonSeq = [];
level = 0;
strict = false;
$(".display").text("00");
}
PROBLEM
You have a reference vs copy problem in your initialization code.
$(document).ready(function() {
$(".start").click(function() {
strict = false;
error = false;
level++;
simonSeq = userSeq = []; //PROBLEM !!!!
simonSequence();
})
Arrays are passed by reference, not value.
simonSeq = userSeq = [];
/* Any changes to 'userSeq' will affect 'simonSeq'.
'simonSeq' is referencing 'userSeq' */
SOLUTION
Change all instances of
simonSeq = userSeq = [];
To
simonSeq = [];
userSeq = [];
EXPLINATION
Values in JavaScript can be referred to in 2 ways; by reference and by value.
When you refer to something by value, you are copying it.
var numA = 5;
var numB = numA; //COPY numA over to numB
numA = 12; // Changes to numA will not affect numB because it was copied
console.log(numA); // 12
console.log(numB); // 5
When you refer to something by reference, your are referring/referencing it, not copying it. Any changes made to the original will affect everything that is referencing it.
var original = [1,2,3];
var ref = original; //Any changes made to 'original' will affect 'ref'
original.push('APPLES');
console.log(original); // [1,2,3,'APPLES']
console.log(ref); // [1,2,3,'APPLES']
In the above code ref does not actually contain any values. ref contains the memory location of original.
ref is referencing original.
Arrays and Objects are always passed/refereed to by reference.
Everything else is passed/refereed to by value (they are copied).
I have a script that is searching for duplicated text strings in an array and changing the colors.
function checkDuplicates() {
var values = new Array();
var $input = $('input[type=\'text\']');
var error = 0;
$input.each(function() {
$(this).removeClass('double-error');
var that = this;
if (that.value!='') {
values[that.value] = 0;
$('input[type=\'text\']').each(function() {
if (this.value == that.value) {
values[that.value]++;
}
});
} //endif
});
$input.each(function(key) {
if (values[this.value]>1) {
error++;
$(this).addClass('double-error');
}
});
return (error <= 0); //returns false or true
}
<style type="text/css">
.double-error {
color:red;
border:1px solid red;
}
</style>
This is working fine.
However, I need to count duplicated strings and add keep track of whether they are the first occurence of that word, the second occurence of that word, etc.
For example:
Given john, john, peter, doe, peter, john, the result would be john-1, john-2, peter-1, doe, peter-2, john-3.
This is what I currently have:
function eliminateDuplicates() {
var values = new Array();
var $input = $('input[type=\'text\']');
var error = 0;
$input.each(function() {
$(this).removeClass('double-error');
var that = this;
if (that.value!='') {
values[that.value] = 0;
$('input[type=\'text\']').each(function() {
if (this.value == that.value) {
values[that.value]++;
}
});
}
});
$input.each(function(key) {
if (values[this.value]>1) {
error++;
myArray = values[this.value];
for (var i = 0; i < myArray; i++) {
$(this).parent()
.find('input[type=\'text\']')
.val(this.value + '-' + i);
}
}
});
return error <= 0; //return error > 0 ? false : true;
}
But I got this result:
john-0-1-2, john-0-1-2, peter-0-1, doe, peter-0-1, john-0-1-2
What's wrong?
I make some modification:
function eliminateDuplicates() {
var values = new Array();
var $input = $('input[type=\'text\']');
var error = 0;
$input.each(function() {
$(this).removeClass('double-error');
var that = this;
if (that.value!='') {
values[that.value] = 0;
$('input[type=\'text\']').each(function() {
if (this.value == that.value) {
values[that.value]++;
}
});
}
});
$input.each(function(key) {
if (values[this.value]>1) {
var name=this.value;
var names = values[this.value];
values[this.value]++;
for (var i = 0; i < names; i++){
$(this).parent().find('input[type=\'text\']').val(name + '-' + i);
}
}
});
checkDoubles();
return error <= 0; //return error > 0 ? false : true;
}
now I getting counts in sequence but not from 1.
for example if I have 4 duplicated names (Peter) i getting:
Peter-3, Peter-4, Peter-5, Peter-6.
but I need
Peter-1, Peter-2, Peter-3, Peter-4.
what wrong?
I think you're looking for something like this:
function eliminateDuplicates() {
var repeats = {};
var error = false;
//cache inputs
var $inputs = $("input[type='text']");
//loop through inputs and update repeats
for (i = 0; i < $inputs.length; ++i) {
//cache current element
var cur = $inputs[i];
//remove class
$(cur).removeClass("double-error");
//get text of this element
var text = $(cur).val();
//no text -- continue
if (text === "") {
continue;
}
//first time we've came across this value -- intialize it's counter to 1
if ((text in repeats) === false) {
repeats[text] = 1;
}
//repeat offender. Increment its counter.
else {
repeats[text] = repeats[text] + 1;
}
//update the the value for this one
$(cur).val(text + "-" + repeats[text]);
}
return error; // always returns false since I'm not sure
// when it's supposed to return true.
}
PS: I didn't understand when error was supposed to be true, so it's always false. Nevertheless, this should be enough to get you going.
PPS: Plunker here.
Can this code be chained and the for loop abstracted away? Using map and filter?
function listViewFilter(){ // test
var DateFiltered = containerdata.filter(function (obj){ // remove dates of 010
return !/010/.test(obj.EventDate);
});
var NameFiltered = [];
for (var i = 0; i < DateFiltered.length; i++){ // remove EventNames.Name if empty string
if (DateFiltered[i].EventNames[0].Name == "") continue;
else NameFiltered.push(DateFiltered[i]);
}
Filtered = NameFiltered;
}
Sure, your for loop does nothing but another filter:
function listViewFilter() {
var DateFiltered = containerdata.filter(function(obj) { // remove dates of 010
return !/010/.test(obj.EventDate);
});
var NameFiltered = DateFiltered.filter(function(obj, i) { // remove EventNames.Name if empty string
return obj.EventNames[0].Name != "";
});
Filtered = NameFiltered;
}
or in short
function listViewFilter() {
Filtered = containerdata.filter(function(obj) {
return !/010/.test(obj.EventDate) && obj.EventNames[0].Name != "";
});
}
I'm writing a piece of code to easily save error logs in an object for debugging.
What I'm trying to achieve is to get the Object name from the function it was called from like so:
var MainObject = {
test : function() {
return MainObject.test.caller;
// When called from MainObject.testcaller,
// it should return MainObject.testcaller.
},
testcaller : function() {
return MainObject.test(); // Should return MainObject.testcaller, Returns own function code.
},
anothercaller : function() {
return MainObject.test(); // Should return MainObject.anothercaller, Returns own function code.
}
}
However when I run this code it returns the function code from MainObject.testcaller.
JSFiddle example
Is there any way this is possible?
Update
After looking at Rhumborl's answer, I discovered that assigning the value through another function would lead it to point back at the function name without the object itself.
Code:
(function (name, func) {
MainObject[name] = func;
})('invalid', function() {
return MainObject.test("blah");
});
// This now points at invalid() rather than MainObject.invalid()
Updated fiddle
There is a non–standard caller property of functions that returns the caller function, however that is a pointer to a function object and doesn't tell you the object it was called as a method of, or the object's name. You can get a reference to the function through arguments.callee.
There is also the obsolete arguments.caller, but don't use that. It also provides a reference to the calling function (where supported).
Once you have a reference to the calling function (if there is one), you then have the issue of resolving its name. Given that Functions are Objects, and objects can be referenced by multiple properties and variables, the concept of a function having a particular name is alluvial.
However, if you know that the function is a property of some object, you can iterate over the object's own enumerable properties to find out which one it is.
But that seems to be a rather odd thing to do. What are you actually trying to do? You may be trying to solve a problem that can be worked around in a much more robust and simpler way.
Edit
You can do what you want in a very limited way using the method described above for the case in the OP, however it is not robust or a general solution:
var mainObject = {
test : function() {
var obj = this;
var caller = arguments.callee.caller;
var global = (function(){return this}());
var fnName, objName;
for (var p in global) {
if (global[p] === obj) {
objName = p;
}
}
for (var f in obj) {
if (obj[f] === caller) {
fnName = f;
}
}
return objName + '.' + fnName;
},
testcaller : function() {
return mainObject.test();
},
anothercaller : function() {
return mainObject.test();
}
}
console.log(mainObject.testcaller()); // mainObject.testcaller
console.log(mainObject.anothercaller()); // mainObject.anothercaller
but it's brittle:
var a = mainObject.anothercaller;
console.log(a()); // mainObject.anothercaller
var b = {
foo : mainObject.anothercaller
}
console.log(b.foo()); // mainObject.anothercaller
Oops.
You can use this trick at http://www.eriwen.com/javascript/js-stack-trace/ which throws an error, then parses the stack trace.
I have updated it for the latest versions of Firefox, Chrome and IE. Unfortunately it doesn't work well on my IE9 (and I haven't tested it on Opera).
function getStackTrace() {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist += 0; //doesn't exist- that's the point
} catch (e) {
if (e.stack) { //Firefox/Chrome/IE11
var lines = e.stack.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
var line = lines[i].trim();
if (line.match(/^at [A-Za-z0-9\.\-_\$]+\s*\(/)) {
// Chrome/IE: " at Object.MainObject.testcaller (url:line:char)"
var entry = line.substring(3, line.indexOf('(') - 1);
// Chrome appends "Object." to the front of the object functions, so strip it off
if (entry.indexOf("Object.") == 0) {
entry = entry.substr(7, entry.length);
}
callstack.push(entry);
} else if (line.match(/^[A-Za-z0-9\.\-_\$]+\s*#/)) {
// Firefox: "MainObject.testcaller#url:line:char"
callstack.push(line.substring(0, lines[i].indexOf('#')));
}
}
//Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
} else if (window.opera && e.message) { //Opera
var lines = e.message.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i + 1]) {
entry += lines[i + 1];
i++;
}
callstack.push(entry);
}
}
//Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE9 and Safari
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
return callstack;
}
var MainObject = {
test: function (x) {
// first entry is the current function (test), second entry is the caller
var stackTrace = getStackTrace();
var caller = stackTrace[1];
return caller + "()";
},
testcaller: function () {
return MainObject.test(1, null);
}
}
function SomeFunction() {
return MainObject.test("blah");
}
document.body.innerHTML += '<b style="color: red">' + MainObject.testcaller() + '</b>';
document.body.innerHTML += '<div>Calling SomeFunction() returns: <b style="color: red">' + SomeFunction() + '</b></div>';
MainObject.test() should return: <b style="color: blue">MainObject.testcaller()</b>
<hr />
MainObject.test() returns:
Updated fiddle here
I've made some new objects with object methods and I'm having trouble returning the information.
I intend for allPages to be a 2d array:
var allPages = [[]];
function textbox(type)
{
this.type=type;
this.getInfo = function () { return ( this.type ); };
}
function addTextbox(dropdown)
{
var myindex = dropdown.selectedIndex;
var SelValue = dropdown.options[myindex].value;
if(SelValue == "String")
{
var tb = new textbox("string");
allPages[allPages.length-1].push(tb);
var string = "";
for (i = 0;i < allPages.length;i++)
{
for(j = 0;j < allPages[i].length;j++)
{
string = string + allPages[i][j].getInfo;
}
}
<!-- Problem here: prints "function () { return this.type; }"-->
document.write(string);
}
}
}
You are not calling the function, you are referencing it
allPages[i][j].getInfo;
should be
allPages[i][j].getInfo();
3 lines above where you state the problems exists, it should be:
string = string + allPages[i][j].getInfo(); // mind the () at the end.