Emulate string split() from Javascript Object Oriented Programing Stoyan Stefanov book - javascript

Im learning JS OOP from Stoyan Stefanov's book. I got problem with exercise 4 in chapter 4:
Imagine the String()constructor didn't exist. Create a constructor
function MyString()that acts like String()as closely as possible.
You're not allowed to use any built-in string methods or properties,
and remember that String()doesn't exist. You can use this code to test
your constructor:
Below is my attempt for creating String split() like method. Could you guide me how to make it work ?
function MineString(string){
this.lengthS = string.length;
//this[1] = "test";
for(var i = 0; i < string.length;i++){
this[i] = string.charAt(i);
}
this.toString = function(){
return string;
}
this.split = function(char){
var splitedArray = [];
var foundedChar = [];
var string2 = [];
for (var i = 0; i < this.lengthS ; i++){
foundedChar.push(string[i].indexOf(char));
}
for (var j = 0; j < foundedChar.length; j++){
if(foundedChar[j] === -1){
//splitedArray[0] += string.charAt(j);
//splitedArray[j] = string.charAt(j);
string2 += string.charAt(j);
//splitedArray.push(string2);
splitedArray[foundedChar.length] = string2;
}else if (foundedChar[j] === 0){
//splitedArray.push(string.charAt(j));
}
}
return splitedArray;
}
}
var text = new MineString("hello");
text.split("e");
So text.split("e"); should display something like this:
var text = new MineString("hello");
text.split("e");
["h","llo"]

Your split method looks somehow overly complicated. I simplified it and rewrote the other parts of your class so that they adhere to the task of not using string methods. See jsfiddle or the code below.
New JS-Code:
function MineString(str){
this.str = str;
this.addChar = function(c) {
this.str += c;
}
this.length = function() {
return this.str.length;
}
this.toString = function(){
return this.str;
}
this.split = function(char){
var out = [],
current = new MineString(""),
addCurrent = function() {
if (current.length() > 0) {
out.push(current.toString());
current = new MineString("");
}
};
for (i = 0; i < this.str.length; i++) {
if (this.str[i] == char) {
addCurrent();
} else {
current.addChar(this.str[i]);
}
}
addCurrent();
return out;
}
}
var text = new MineString("hello");
console.log(text.split("e"));
Outputs:
["h", "llo"]

Related

Why is this constructor (using "new Function") breaking?

I'm brand new to JS and am trying to build a template function (assignment in a MOOC) that basically returns a function that returns the rendered template based on an input string and delimiter.
Anyway, this is the code that I have so far and I have no idea why it is breaking.. I've really tried everything that I can think of!
var template = function(stringToParse, options) {
// find out if custom delimiters are being used
if (typeof options != 'undefined') {
var openDelim = options.open;
var closeDelim = options.close;
} else {
var openDelim = '*(';
var closeDelim = ')*';
}
// get the length of the closing delim for parsing later
var delimCharLen = closeDelim.length;
// helper function
function parseOutFiller(_array) {
// get an array of the indices of each closing delim in the string
var closingDelims = [];
for (i=0; i < _array.length; i++) {
closingDelims.push(_array[i].indexOf(closeDelim));
}
// remove the filler text leading up to the closing dim in each substring
for (i = 0; i < _array.length; i++) {
if (closingDelims[i] > 0) {
_array[i] = _array[i].slice(closingDelims[i] + delimCharLen)
}
}
return _array
}
// split array, get the closing indices, and parse out the filler text
var splitArray = stringToParse.split(openDelim);
var parsedString = parseOutFiller(splitArray);
return new Function("var locParsedString = [" + parsedString + "];\
var inputCopy = [];\
for (i=0; i < arguments.length-1; i++) {\
inputCopy.push(arguments[i])\
}\
var templateString = '';\
for (i=0; i < inputCopy.length; i++) {\
templateString += locParsedString[i];\
templateString += inputCopy[i];\
}\
templateString += locParsedString[locParsedString.length-1];\
nRepeat = arguments[arguments.length-1];\
for (i=0; i < nRepeat; i++) {\
console.log(templateString);\
}"
)
}
Then when I run it...
var string = "Is <<! thing !>> healthy to <<! action !>>?";
var logResult = template(string, {open: '<<!', close: '!>>'});
logResult('this', 'eat', 3)
/*
Which should print:
"Is this healthy to eat?"
"Is this healthy to eat?"
"Is this healthy to eat?"
*/
Thanks in advance!
Instead of using new Function(), just use return function () { }.
That way, there is no need to create locParserString inside the function. You can use parsedString directly:
var template = function(stringToParse, options) {
// find out if custom delimiters are being used
if (typeof options != 'undefined') {
var openDelim = options.open;
var closeDelim = options.close;
} else {
var openDelim = '*(';
var closeDelim = ')*';
}
// get the length of the closing delim for parsing later
var delimCharLen = closeDelim.length;
// helper function
function parseOutFiller(_array) {
// get an array of the indices of each closing delim in the string
var closingDelims = [];
for (i=0; i < _array.length; i++) {
closingDelims.push(_array[i].indexOf(closeDelim));
}
// remove the filler text leading up to the closing dim in each substring
for (i = 0; i < _array.length; i++) {
if (closingDelims[i] > 0) {
_array[i] = _array[i].slice(closingDelims[i] + delimCharLen)
}
}
return _array
}
// split array, get the closing indices, and parse out the filler text
var splitArray = stringToParse.split(openDelim);
var parsedString = parseOutFiller(splitArray);
return function () {
var inputCopy = [];
for (i=0; i < arguments.length-1; i++) {
inputCopy.push(arguments[i])
}
var templateString = '';
for (i=0; i < inputCopy.length; i++) {
templateString += parsedString[i];
templateString += inputCopy[i];
}
templateString += parsedString[parsedString.length-1];
nRepeat = arguments[arguments.length-1];
for (i=0; i < nRepeat; i++) {
console.log(templateString);
}
};
}

creating a new array out of an object

I've created a JavaScript object to get the number of times a character repeats in a string:
function getFrequency(string) {
// var newValsArray =[];
var freq = {};
for (var i=0; i<string.length;i++) {
var character = string.charAt(i);
if (freq[character]) {
freq[character]++;
} else {
freq[character] = 1;
}
}
return freq;
}
Now, I'm trying to construct a new string composed of the keys & their properties (the letters) & numbers of times the letters repeat if the number (property) is more than one but I keep getting undefined and I don't know why:
function newString(freq){
var newValsArray = [];
for (var prop in freq) {
if (freq[prop]>1){
newValsArray.push(prop + freq[prop]);
}
else if (freq[prop] < 2){
newValsArray.push(prop);
}
}
return newValsArray;
}
I feel like my syntax is off or something... if anyone has any suggestions, I'd really appreciate it...
You aren't explicitly returning anything from newString(), so it will return undefined. It sounds like you want something like this:
return newValsArray.join('');
at the end of newString() to construct an actual string (instead of returning an array). With that change, newString(getFrequency("Hello there") will return 'He3l2o thr'.
function getFrequency(string) {
// var newValsArray =[];
var freq = {};
for (var i = 0; i < string.length; i++) {
var character = string.charAt(i);
if (freq[character]) {
freq[character] ++;
} else {
freq[character] = 1;
}
}
return freq;
}
function newString(freq) {
var newValsArray = [];
for (var prop in freq) {
if (freq[prop] > 1) {
newValsArray.push(prop + freq[prop]);
} else if (freq[prop] < 2) {
newValsArray.push(prop);
}
}
return newValsArray.join("");
}
var mystring = "Here are some letters to see if we have any freq matches and so on.";
var results = newString(getFrequency(mystring));
var elem = document.getElementById("results");
elem.innerHTML = results;
<div id="results"></div>
You are not returning anything from the newString function. Add return newString; as the last line of the newString function. Adding that line does result in something being returned, though I can't tell if it is what you expected.
var text = "asdfjhwqe fj asdj qwe hlsad f jasdfj asdf alhwe sajfdhsadfjhwejr";
var myFreq = getFrequency(text);
show(myFreq);
var myNewValsArray = newString(myFreq);
show(myNewValsArray);
function getFrequency(string) {
// var newValsArray =[];
var freq = {};
for (var i=0; i<string.length;i++) {
var character = string.charAt(i);
if (freq[character]) {
freq[character]++;
} else {
freq[character] = 1;
}
}
return freq;
}
function newString(freq){
var newValsArray = [];
for (var prop in freq) {
if (freq[prop]>1){
newValsArray.push(prop + freq[prop]);
}
else if (freq[prop] < 2){
newValsArray.push(prop);
}
}
return newValsArray; // ******** ADD THIS LINE
}
function show(msg) {
document.write("<pre>" + JSON.stringify(msg, null, 2) + "</pre>");
}

Split array by tag and delete all similar element

I have some html page with text and need to output all inner HTML from tag b by alphabetical order in lower case. I'm just a begginer, so don't be strict.
My code is here (text is just for example): http://jsfiddle.net/pamjaranka/ebeptLzj/1/
Now I want to: 1) save upper case for inner HTML from tag abbr; 2) delete all similar element from the array (as MABs).
I was trying to find the way to split the array by tag, but all that I've done is:
for(var i=0; i<allbold.length; i++){
labels[i] = allbold[i].innerHTML;
}
var searchTerm = ['abbr'];
var abbr = [];
var keywordIndex;
$.each(labels, function(i) {
$.each(searchTerm, function(j) {
var rSearchTerm = new RegExp('\\b' + searchTerm[j] + '\\b','i');
if (labels[i].match(rSearchTerm)) {
keywordIndex = i;
for(var j=0; j<labels.length; j++){
abbr[i] = labels[i];
}
}
});
});
Vanilla JS solution (no library required, see jsFiddle):
var allbold = document.querySelectorAll("b"),
words = document.querySelector("#words"),
labels = {}, i, word, keys, label;
// first, collect all words in an object (this eliminates duplicates)
for(i = 0; i < allbold.length; i++) {
word = allbold[i].textContent.trim();
if (word === 'Labels:') continue;
labels[word.toLowerCase()] = word;
}
// then sort the object keys and output the words in original case
keys = Object.keys(labels).sort();
for(i = 0; i < keys.length; i++){
label = document.createTextNode("SPAN");
label.textContent = labels[keys[i]];
words.appendChild(label);
// add a comma if necessary
if (i < keys.length - 1) {
words.appendChild(document.createTextNode(", "));
}
}
with one helper:
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, "");
};
jQuery solution (see jsFiddle):
$(".content b").map(function () {
return $("<span>", {text: $.trim(this.textContent)})[0];
}).unique(function () {
return lCaseText(this);
}).sort(function (a, b) {
return lCaseText(a) < lCaseText(b) ? -1 : 1;
}).appendTo("#words");
with two helpers:
$.fn.extend({
unique: function (keyFunc) {
var keys = {};
return this.map(function () {
var key = keyFunc.apply(this);
if (!keys.hasOwnProperty(key)) {
keys[key] = true;
return this;
}
});
}
});
function lCaseText(element) {
return element.textContent.toLowerCase();
}
use the mapping element Is THIS FIDDLE for all upper case else this fiddle after your comment what you need
var maplabels = [];
for(var i=0; i<allbold.length; i++){
if (allbold[i].innerHTML != "Labels:") {
if(maplabels.indexOf(allbold[i].innerHTML) == -1){
maplabels.push(allbold[i].innerHTML);
labels.push('<i>' + allbold[i].innerHTML.toUpperCase() + '</i>');
}
}
}

Object overwriting not working

My JavaScript object is:
var MyObject = {
DOM: function(tagName){
if(!this.isElement){
var found = document.getElementsByTagName(tagName);
this.isElement = true;
for(i in found)
this[i] = found[i];
} else{
var found = this[0].getElementsByTagName(tagName);
for(i in found)
this[i] = found[i];
}
return this;
}
}
It works perfectly:
MyObject.DOM("div");
The problem is that when I log the object again:
MyObject.DOM("div");
console.log(MyObject);
The problem is that it logs:
> Object {0: div#lower.cl, 1: div.higher, find: function, isElement: true}
But actually I want it to log:
> Object {find: function}
So I don't want it to keep the found elements when I run the MyObject again.
So really I just want to reload the object every time I use it.
Here's one way you can implement this. It's best if you stick to keeping your objects as immutable as possible. You're trying to use one instance of one object to do everything, and that won't work:
function MyObject() {
this.length = 0;
}
MyObject.prototype.DOM = function (tagName) {
var found = new MyObject(),
batch,
toSearch,
i,
j,
z = 0;
if (this === MyObject) {
toSearch = [document];
} else {
toSearch = Array.prototype.slice.call(this);
}
for (i = 0; i < toSearch.length; i += 1) {
batch = toSearch[i].getElementsByTagName(tagName);
for (j = 0; j < batch.length; j += 1) {
found[found.length] = batch[j];
found.length += 1;
}
}
return found;
}
MyObject.DOM = MyObject.prototype.DOM;
http://jsfiddle.net/Sygdm/
You can add sort of 'private' fields to your classes like so:
var MyObject = (function() {
var instance = {};
return {
DOM: function(tagName){
if(!instance.isElement){
var found = document.getElementsByTagName(tagName);
instance.isElement = true;
for(i in found)
instance[i] = found[i];
} else{
var found = instance[0].getElementsByTagName(tagName);
for(i in found)
instance[i] = found[i];
}
return this;
}
}
})();
Not sure if that is what you want, though.

Splitting a string only when the delimeter is not enclosed in quotation marks

I need to write a split function in JavaScript that splits a string into an array, on a comma...but the comma must not be enclosed in quotation marks (' and ").
Here are three examples and how the result (an array) should be:
"peanut, butter, jelly"
-> ["peanut", "butter", "jelly"]
"peanut, 'butter, bread', 'jelly'"
-> ["peanut", "butter, bread", "jelly"]
'peanut, "butter, bread", "jelly"'
-> ["peanut", 'butter, bread', "jelly"]
The reason I cannot use JavaScript's split method is because it also splits when the delimiter is enclosed in quotation marks.
How can I accomplish this, maybe with a regular expression ?
As regards the context, I will be using this to split the arguments passed from the third element of the third argument passed to the function you create when extending the jQuery's $.expr[':']. Normally, the name given to this parameter is called meta, which is an array that contains certain info about the filter.
Anyways, the third element of this array is a string which contains the parameters that are passed with the filter; and since the parameters in a string format, I need to be able to split them correctly for parsing.
What you are asking for is essentially a Javascript CSV parser. Do a Google search on "Javascript CSV Parser" and you'll get lots of hits, many with complete scripts. See also Javascript code to parse CSV data
Well, I already have a jackhammer of a solution written (general code written for something else), so just for kicks . . .
function Lexer () {
this.setIndex = false;
this.useNew = false;
for (var i = 0; i < arguments.length; ++i) {
var arg = arguments [i];
if (arg === Lexer.USE_NEW) {
this.useNew = true;
}
else if (arg === Lexer.SET_INDEX) {
this.setIndex = Lexer.DEFAULT_INDEX;
}
else if (arg instanceof Lexer.SET_INDEX) {
this.setIndex = arg.indexProp;
}
}
this.rules = [];
this.errorLexeme = null;
}
Lexer.NULL_LEXEME = {};
Lexer.ERROR_LEXEME = {
toString: function () {
return "[object Lexer.ERROR_LEXEME]";
}
};
Lexer.DEFAULT_INDEX = "index";
Lexer.USE_NEW = {};
Lexer.SET_INDEX = function (indexProp) {
if ( !(this instanceof arguments.callee)) {
return new arguments.callee.apply (this, arguments);
}
if (indexProp === undefined) {
indexProp = Lexer.DEFAULT_INDEX;
}
this.indexProp = indexProp;
};
(function () {
var New = (function () {
var fs = [];
return function () {
var f = fs [arguments.length];
if (f) {
return f.apply (this, arguments);
}
var argStrs = [];
for (var i = 0; i < arguments.length; ++i) {
argStrs.push ("a[" + i + "]");
}
f = new Function ("var a=arguments;return new this(" + argStrs.join () + ");");
if (arguments.length < 100) {
fs [arguments.length] = f;
}
return f.apply (this, arguments);
};
}) ();
var flagMap = [
["global", "g"]
, ["ignoreCase", "i"]
, ["multiline", "m"]
, ["sticky", "y"]
];
function getFlags (regex) {
var flags = "";
for (var i = 0; i < flagMap.length; ++i) {
if (regex [flagMap [i] [0]]) {
flags += flagMap [i] [1];
}
}
return flags;
}
function not (x) {
return function (y) {
return x !== y;
};
}
function Rule (regex, lexeme) {
if (!regex.global) {
var flags = "g" + getFlags (regex);
regex = new RegExp (regex.source, flags);
}
this.regex = regex;
this.lexeme = lexeme;
}
Lexer.prototype = {
constructor: Lexer
, addRule: function (regex, lexeme) {
var rule = new Rule (regex, lexeme);
this.rules.push (rule);
}
, setErrorLexeme: function (lexeme) {
this.errorLexeme = lexeme;
}
, runLexeme: function (lexeme, exec) {
if (typeof lexeme !== "function") {
return lexeme;
}
var args = exec.concat (exec.index, exec.input);
if (this.useNew) {
return New.apply (lexeme, args);
}
return lexeme.apply (null, args);
}
, lex: function (str) {
var index = 0;
var lexemes = [];
if (this.setIndex) {
lexemes.push = function () {
for (var i = 0; i < arguments.length; ++i) {
if (arguments [i]) {
arguments [i] [this.setIndex] = index;
}
}
return Array.prototype.push.apply (this, arguments);
};
}
while (index < str.length) {
var bestExec = null;
var bestRule = null;
for (var i = 0; i < this.rules.length; ++i) {
var rule = this.rules [i];
rule.regex.lastIndex = index;
var exec = rule.regex.exec (str);
if (exec) {
var doUpdate = !bestExec
|| (exec.index < bestExec.index)
|| (exec.index === bestExec.index && exec [0].length > bestExec [0].length)
;
if (doUpdate) {
bestExec = exec;
bestRule = rule;
}
}
}
if (!bestExec) {
if (this.errorLexeme) {
lexemes.push (this.errorLexeme);
return lexemes.filter (not (Lexer.NULL_LEXEME));
}
++index;
}
else {
if (this.errorLexeme && index !== bestExec.index) {
lexemes.push (this.errorLexeme);
}
var lexeme = this.runLexeme (bestRule.lexeme, bestExec);
lexemes.push (lexeme);
}
index = bestRule.regex.lastIndex;
}
return lexemes.filter (not (Lexer.NULL_LEXEME));
}
};
}) ();
if (!Array.prototype.filter) {
Array.prototype.filter = function (fun) {
var len = this.length >>> 0;
var res = [];
var thisp = arguments [1];
for (var i = 0; i < len; ++i) {
if (i in this) {
var val = this [i];
if (fun.call (thisp, val, i, this)) {
res.push (val);
}
}
}
return res;
};
}
Now to use the code for your problem:
function trim (str) {
str = str.replace (/^\s+/, "");
str = str.replace (/\s+$/, "");
return str;
}
var splitter = new Lexer ();
splitter.setErrorLexeme (Lexer.ERROR_LEXEME);
splitter.addRule (/[^,"]*"[^"]*"[^,"]*/g, trim);
splitter.addRule (/[^,']*'[^']*'[^,']*/g, trim);
splitter.addRule (/[^,"']+/g, trim);
splitter.addRule (/,/g, Lexer.NULL_LEXEME);
var strs = [
"peanut, butter, jelly"
, "peanut, 'butter, bread', 'jelly'"
, 'peanut, "butter, bread", "jelly"'
];
// NOTE: I'm lazy here, so I'm using Array.prototype.map,
// which isn't supported in all browsers.
var splitStrs = strs.map (function (str) {
return splitter.lex (str);
});
var str = 'text, foo, "haha, dude", bar';
var fragments = str.match(/[a-z]+|(['"]).*?\1/g);
Even better (supports escaped " or ' inside the strings):
var str = 'text_123 space, foo, "text, here\", dude", bar, \'one, two\', blob';
var fragments = str.match(/[^"', ][^"',]+[^"', ]|(["'])(?:[^\1\\\\]|\\\\.)*\1/g);
// Result:
0: text_123 space
1: foo
2: "text, here\", dude"
3: bar
4: 'one, two'
5: blob
If you can control the input to enforce that the string will be enclosed in double-quotes " and that all elements withing the string will be enclosed in single-quotes ', and that no element can CONTAIN a single-quote, then you can split on , '. If you CAN'T control the input, then using a regular expression to sort/filter/split the input would be about as useful as using a regular expression to match against xhtml (see: RegEx match open tags except XHTML self-contained tags)

Categories

Resources