Index an array as leading keywords [closed] - javascript

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have an array like the following:
[
'Main interaction point of this Map.',
'#param {*} Key',
'#param {*} Val',
'#returns {Map}'
]
I'd like to index it starting with a description tag, and then index the following parameters from #param, #returns, etc. The output should be something like this:
{
description: "Main iteration point of this Map.",
param: [
"{*} Key",
"{*} Val"
],
returns: "{Map}"
}
But, when I have leading no-tags, the last tag should apply:
[
'Sorts all the elements in the Map and returns it.',
'#param {Function} [cfn] Specifies a function that defines the sort order.',
'If omitted, the Collection is sorted according to each character\'s Unicode point value,',
'according to the string conversion of each element.',
'#param {*} [thisv] Value to use as `this` when executing functions.',
'#returns {Map}'
]
This should return:
{
description: "Sorts all the elements in the Map and returns it.",
param: [
"{Function} [cfn] Specifies a function that defines the sort order. If omitted, the Collection is sorted according to each character's Unicode point value, according to the string conversion of each element.",
"{*} [thisv] Value to use as `this` when executing functions."
],
returns: "{Map}"
}
Notice how the lines without a tag aren't a new param? I haven't figured out how to do this, could anybody give an example? Thanks

I'd suggest keeping a state/status variable keyword with the last tag seen, defaulting to "description". Then you would use recognizable tags like "#param" to update the status, and use the status to correctly write the rest of the line to your object.
// pseudocode
let output = {};
let keyword = "description";
for (const line of lines) {
// search for "#" at beginning, followed by a keyword, followed by a space and more content
const match = line.match(/^#(param|returns) (.*)$/);
let content;
if (match) {
keyword = match[1]; // "param" or "returns"
content = match[2]; // content is text following the tag
} else {
content = line; // content is the whole line
}
switch (keyword) {
// your logic
}
}

Maybe something like this?
const result = {
description: "",
param: [],
returns: ""
};
let last_tag = "";
for(text of test) {
switch(true) {
case text.startsWith("#param"):
last_tag = "#param";
result.param.push(text.replace("#param", "").trim());
break;
case text.startsWith("#returns"):
last_tag = "#returns";
result.returns += text.replace("#returns", "").trim();
break;
default:
switch(last_tag) {
case "#param":
result.param[result.param.length -1] += text;
break;
case "#returns":
result.returns += text;
break;
default:
result.description += text;
}
result.description = text;
}
}

Related

Can I refresh the values of (an array of) objects as the variable they refer to changes?

I am trying to make a generator of simple sentences by combining data from two arrays. One array has the nouns/subjects and the other the verbs/actions.
Obviously, some nouns are singular and some plural. To make this work, I make the nouns objects (and a class), eg: { txt: "Jason", snglr: true }, { txt: "The kids" }.
Then, I create a function that forms the verb according to whether the noun is singular or plural (snglr: true or false/undefined) and apply it as a method of the noun class.
I call the method from within the string of the verb/action, as in: { txt: `${curr_noun.verb("go", "goes")} to music class.`}
My problem is that all the verbs get their values from the first noun used. When I change the noun, the verbs still refer to the plural or singular form called by the first noun.
Is there a way to refresh the actions objects, so that each time their values refer to the current noun?
(Or if I'm totally missing an easier way, can you please let me know?)
Thanks. Here is the whole code:
class noun {
constructor(text, singular) {
this.txt = text; // The subject of the sentence
this.snglr = singular; // Whether the subject is singular (true) or plural (undefined/false)
this.verb = function (plur, sing) { //this function is called from within the string containing the verb (see "actions" array, below)
if (sing == undefined) { // for regular verbs, we call it with one argument, eg. .verb("walk"), it creates "walks" by adding "s."
sing = plur + "s";
}
if (this.snglr) { // for irregular verbs, we call it with two arguments, eg. .verb("go", "goes")
return sing;
} else {
return plur;
}
}
}
}
var curr_noun = {};
var nouns = [new noun("Jason", true), new noun("The kids")];
curr_noun = nouns[0]; // We pick "Jason" as the sentence's subject.
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.`},
{ txt: `${curr_noun.verb("visit")} London.`}
];
curr_action = actions[1];
console.log(`${curr_noun.txt} ${curr_action.txt}`);
// All good, so far.
// My problem is that the above values of actions[0].txt and actions[1].txt are given based on Jason.
// When I later change the curr_noun to "The kids," I still get singular verb forms.
curr_noun = nouns[1];
curr_action = actions[0];
curr_noun.verb();
console.log(`${curr_noun.txt} ${curr_action.txt}`);
I think we could simplify this code a bit. You could create a function to create the sentences. This function would take a noun object and verb object and the grammatical object which is a string.
This way we can remove the class and reduce the number of lines that we write. With this approach, we can also ensure the nouns and verbs match in terms of plurality. Here is an example:
const nouns = [
{
text: 'Jason',
isSingular: true,
},
{
text: 'The kids',
isSingular: false,
},
];
// in grammar, the noun at the end of the sentence is called object
const verbs = [
{
// "to" is actually part of the verb here(prepositional verb)
singular: 'goes to',
plural: 'go to',
},
{
singular: 'visits',
plural: 'visit',
},
];
// in grammar, the noun at the end of the sentence is called object
const objects = ['music class.', 'London.'];
const createSentence = (noun, verb, object) => {
const myVerb = noun.isSingular === true ? verb.singular : verb.plural;
return `${noun.text} ${myVerb} ${object}`;
};
console.log(createSentence(nouns[0], verbs[1], objects[1]));
console.log(createSentence(nouns[1], verbs[0], objects[0]));
Note: I am not saying that this is how it should be done, but just pointing out where the problem is.
You define your var actions based on curr_noun = nouns[1];, i.e. "Jason", here:
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.` },
{ txt: `${curr_noun.verb("visit")} London.` }
];
This never changes. So when referring to actions[0] later, they are still based on "Jason".Try to console.log(this.txt) inside the .verb-method to see where it goes wrong.
When reassigning them again to "The kids" before logging, it works fine:
curr_noun = nouns[1];
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.` },
{ txt: `${curr_noun.verb("visit")} London.` }
];
curr_action = actions[0];
console.log(`${curr_noun.txt} ${curr_action.txt}`);
// -> The kids go to music class.

Optimizing my KnockOutJS function - From Ugly Code to Clean, How?

Function Description
This function is supposed to simply replace an item in my observable array.
I am accepting 3 parameters:
findBy = Whether to locate the source index by "id" or "name" property.
cmpVal = The value we are searching the array for
objNewItem = The new item. This case ex, { id: "12" , name: "NewName" }
Then I am deleting that index from the array, and finally pushing the new object into the array.
I had no luck using the replace function from Knockout, so I had to write my own.
I realize this may be the ugliest code on the internet. That's why I am deferring to you professionals here. :)
/* Update A Station
*
* #param findBy string Property to find ( "id" or "name")
* #param cmpVal string Value to compare against
* #param objNewItem object The new object replacing old
* */
self.modifyStation = function(findBy, cmpVal, objNewItem){
var sourceIndex;
var oldId;
/* Find Index Of Old Station */
var c = -1;
ko.utils.arrayForEach(this.station(), function(item) {
c++;
switch (findBy) {
case "id":
var value = item.id();
if(value == cmpVal){
sourceIndex = c;
oldId = item.id();
}
break;
case "name":
var value = item.name();
if(value == cmpVal){
sourceIndex = c;
oldId = item.id();
}
break;
}
});
/* Remove Old */
self.station().splice(sourceIndex,1);
/* Insert New Station
* [For Now] not allowing updating of ID. Only
* can update the other properties (yes, I realize that
* only leaves "name", but more will be added )
*/
objNewItem.id = oldId; // Put old ID back in
self.station.push(objNewItem);
}
Note: I am not allowing them to edit the ID for now.
Can anyone help me clean this up? I am smart enough to know its not efficient, but I don't know how else to optimize it.
Any advice would be appreciated. Thank you!
John
Ok so...
First of we will create a function that will contain all the logic, from this example on you can do anything with it. The most proper way would be to extend your 'observableArray' directly on knockout, but i am not going to get this one this far now :P
function ReplaceInObservableArray(obsArray, prop, cmpVal, newItem){
// use the fact that you can get the value of the property by treating the object as the dictionary that it is
// so you do not need to program for specific properties for different array model types
var foundItems = obsArray().filter(function(i){
return ko.utils.unwrapObservable(i[prop]) == cmpVal;
});
if(foundItems.length > 1)
{
// handle what happens when the property you are comparing is not unique on your list. More than one items exists with this comparison run
// you should throw or improve this sample further with this case implementation.
} else if(foundItems.length == 0)
{
// handle what happens when there is nothing found with what you are searching with.
} else {
// replace at the same index rather than pushing, so you dont move any other items on the array
// use the frameworks built in method to make the replacement
obsArray.replace(foundItems[0], newItem);
}
}
var demoArray = ko.observableArray([{ id : 1, name : 'test1' },{id : 2, name : 'test2' },{ id : 3, name : 'test3'},{id : 4, name : 'test4'}]);
ReplaceInObservableArray(demoArray,'id', 1, {id : 1, name : 'test111'});
ReplaceInObservableArray(demoArray,'name', 'test3', {id : 3, name : 'test3333'});
console.log(demoArray());

Remove element from named values in javascript

I have the following function, which is called when a google forms is submitted. I'm trying to concatenate all answers into a single array that's gonna be used latter:
function onFormSubmit(e) {
var respostas = e.namedValues;
for(item in respostas){
rp = rp.concat(respostas[item]);
}
}
But I would like to drop the timestamp that comes together with the answers. I can access it with respostas['Timestamp'], but I can't find a way to drop or ignore it. The documentation didn't help much.
var cp = [];
function onSubmitForm(e) {
var respostas = e.namedValues;
for (var name in respostas) {
if (respostas.hasOwnProperty(name) {
if (name !== 'Timestamp') {
cp.push(respostash[name]);
}
}
}
}
This is what I would suggest. Using concat to add an item is overkill, you can just push it. Also is a good practice when you are looping over object properties to make sure that they are its own properties of that object, not inherited from prototype. You can read more about it here
You can check the name of the property before concatenate it with the rest.
If the key item equals Timestamp (the undesired property) just skip the current loop.
for(item in respostas) {
if (item === 'Timestamp') {
continue;
}
rp = rp.concat(respostas[item]);
}
EDIT: Based on comments, OP attests that item in the for..in loop is a integer, but, unless his/her code differs radically from the docs, the variable should hold strings, not numbers.
var respostas = {
'First Name': ['Jane'],
'Timestamp': ['6/7/2015 20:54:13'],
'Last Name': ['Doe']
};
for(item in respostas) {
console.log(item);
}
e.namedValues returns a JSON Object with custom keys.
var jsonObj = e.namesValues;
/* e.namedValues returns data like this...
{
"test1": "testval1",
"test2": "testval2",
"test3": "testval3",
}
*/
for(item in respostas){
Logger.log(item); //Key
Logger.log(respostas[item]); //Value
}
This should let you access the key or value on the items in respostas.
The accepted answer is better as it does more to help the user to fix their exact problem, however, I will leave this here for future users in case they want to understand how to access the variables in the object that Google Apps Scripts returns.

How to extract the value of an object inside an incomplete JSON array in Javascript?

I have a node app from which I get some data. I have gone through other questions asked on SO..but Im unable to figure this out. I need to access the value of result . The code below shows the exact response I get from server. None of the methods described in other answers like JSON.parse() etc seem to work.
[{
query: {
"parameter1": "12",
"parameter2": "13",
"parameter3": 25
}
result: 6.58443
}]
EDIT : As mentioned in the comments below, unfortunately I cant fix this on the server side(Comes from an external source). I have to deal with this broken JSON on my end and extract the value of result.
EDIT 2 : Yes, there are multiple arrays like this. The content and comma part doesnt change. They are listed one after the other.
Despite the fact you can't receive that data though any library function that expects JSON like jQuery $.ajax() with dataType='json' option (you should use dataType="text" in this case to avoid premature error being triggered I mean).
...you obviously need to fix JSON syntax before parsing it (as I understood you already know).
If it is what you are asking for, your best bet is a regular expression search and replace.
If you know you won't get things such as '{bracket: "}"}' it is pretty simple:
Example:
var wrong = `
[{
"query": {
"parameter1": "12",
"parameter2": "13",
"parameter3": 25
}
"result": 6.58443
}]
`;
var good = wrong.replace(/}(?!\s*[,}\]])/g, '},');
var json = JSON.parse(good);
console.log(json);
This is the simplest example that fixes the input you provided.
Even though it does not fix the same problem after an end of array (']') and, most importantly, if it were fixed it (or simply the string ended with '}' instead of ']' it would added an extra ',' at the end messing up things again.
A more polite approach solving beferementioned issues is to replace the var good = ... row in previous code with this one:
var good = wrong.replace(/(}|])(?!\s*[,}\]])/g, '$1,')
.replace(/,\s*$/, '')
;
Now, you have a valid json object so accessing any property in it is pretty obvious. For example, json[0].result is what you asked for.
On the other hand, if you can have brackets inside literal strings, it will be much more difficult (even not impossible). But I figure out it would hardly be the case...
what you can do is to encapsulate your result with back-ticks to have a (valid) string literal, then get result with any method you want, for example a matching regex :
var arr = `[{
"query": {
"parameter1": "12",
"parameter2": "13",
"parameter3": 25
}
"result": 6.58443
}]`;
var match = arr.match(/("result": )(\d*.\d*)/);
console.log(match[2]);
The suggestions provided above..all point to a hacky way of solving this. The only way to solve this issue was with the help of good old Regex expressions. To my surprise..even though there are lots of libraries to handle JSON parsing etc, to solve edge cases(Common when dealing with small clients or unreliable data source), there is no library which can handle this scenario.
#Bitifet's answer is what solves this problem..with regex.
Purely for illustrative purposes, the below code "rewrites" JSON that is missing commas into JSON that has commas in the appropriate places. The advantage of using this over a replace or a regular expression is that this code guarantees that string literals are handled correctly:
const LEX_EXPR = (
'('
+ '"(?:\\\\(?:["\\\\/bfnrt]|u[a-fA-F0-9]{4})|[^"])*"|'
+ '-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?|'
+ '(?:true|false|null)'
+ ')|'
+ '([{\\[])|'
+ '([}\\]])|'
+ '([:,])|'
+ '(\\s+)|'
+ '(.)'
)
function lex(string) {
let tokens = []
let expr = new RegExp(LEX_EXPR, 'mguy')
let match = expr.exec(string)
while(match !== null) {
let [
value,
atom,
begin, end, sep,
whitespace,
junk
] = match
let type
if (atom != null) {
type = "atom"
} else if (begin != null) {
type = "begin"
} else if (end != null) {
type = "end"
} else if (sep != null) {
type = "sep"
} else if (whitespace != null) {
type = "whitespace"
} else {
// junk. ignore or raise exception
throw `Invalid character: ${junk}`
}
tokens.push({ type, value })
match = expr.exec(string)
}
return tokens
}
function shouldInsertComma(prev, cur) {
if (!prev || !cur) {
return false
}
if (prev.type == "begin" || prev.type == "sep") {
return false
}
return cur.type == "begin" || cur.type == "atom"
}
function rewrite(tokens) {
let out = []
let prevNonWhitespace = null
for (let i = 0; i < tokens.length; i++) {
let cur = tokens[i]
if (cur.type !== "whitespace") {
if (shouldInsertComma(prevNonWhitespace, cur)) {
out.push({ type: "sep", value: "," })
}
prevNonWhitespace = cur
}
out.push(cur)
}
return out
}
function joinTokens(tokens) {
return tokens.map(({ value }) => value).join('')
}
const invalid = `
{
"foo": {
"bat": "bing}"
"boo": "bug"
}
"result": "yes"
}
`
const rewritten = joinTokens(rewrite(lex(invalid)))
console.log(JSON.parse(rewritten)) // { foo: { bat: 'bing}', boo: 'bug' }, result: 'yes' }

Backbone.js - Filter a Collection based on an Array containing multiple keywords

I'm using Backbone.js/Underscore.js to render a HTML table which filters as you type into a textbox. In this case it's a basic telephone directory.
The content for the table comes from a Collection populated by a JSON file.
A basic example of the JSON file is below:
[{
"Name":"Sales and Services",
"Department":"Small Business",
"Extension":"45446",
},
{
"Name":"Technical Support",
"Department":"Small Business",
"Extension":"18800",
},
{
"Name":"Research and Development",
"Department":"Mid Market",
"Extension":"75752",
}]
I convert the text box value to lower case and then pass it's value along with the Collection to this function, I then assign the returned value to a new Collection and use that to re-render the page.
filterTable = function(collection, filterValue) {
var filteredCollection;
if (filterValue === "") {
return collection.toJSON();
}
return filteredCollection = collection.filter(function(data) {
return _.some(_.values(data.toJSON()), function(value) {
value = (!isNaN(value) ? value.toString() : value.toLowerCase());
return value.indexOf(filterValue) >= 0;
});
});
};
The trouble is that the function is literal. To find the "Sales and Services" department from my example I'd have to type exactly that, or maybe just "Sales" or "Services". I couldn't type "sal serv" and still find it which is what I want to be able to do.
I've already written some javascript that seems pretty reliable at dividing up the text into an array of Words (now updated to code in use).
toWords = function(text) {
text = text.toLowerCase();
text = text.replace(/[^A-Za-z_0-9#.]/g, ' ');
text = text.replace(/[\s]+/g, ' ').replace(/\s\s*$/, '');
text = text.split(new RegExp("\\s+"));
var newsplit = [];
for (var index in text) {
if (text[index]) {
newsplit.push(text[index]);
};
};
text = newsplit;
return text;
};
I want to loop through each word in the "split" array and check to see if each word exists in one of the key/values. As long as all words exist then it would pass the truth iterator and get added to the Collection and rendered in the table.
So in my example if I typed "sal serv" it would find that both of those strings exist within the Name of the first item and it would be returned.
However if I typed "sales business" this would not be returned as although both the values do appear in that item, the same two words do not exist in the Name section.
I'm just not sure how to write this in Backbone/Underscore, or even if this is the best way to do it. I looked at the documentation and wasn't sure what function would be easiest.
I hope this makes sense. I'm a little new to Javascript and I realise I've dived into the deep-end but learning is the fun part ;-)
I can provide more code or maybe a JSFiddle if needed.
Using underscore's any and all make this relatively easy. Here's the gist of it:
var toWords = function(text) {
//Do any fancy cleanup and split to words
//I'm just doing a simple split by spaces.
return text.toLowerCase().split(/\s+/);
};
var partialMatch = function(original, fragment) {
//get the words of each input string
var origWords = toWords(original + ""), //force to string
fragWords = toWords(fragment);
//if all words in the fragment match any of the original words,
//returns true, otherwise false
return _.all(fragWords, function(frag) {
return _.any(origWords, function(orig) {
return orig && orig.indexOf(frag) >= 0;
});
});
};
//here's your original filterTable function slightly simplified
var filterTable = function(collection, filterValue) {
if (filterValue === "") {
return collection.toJSON();
}
return collection.filter(function(data) {
return _.some(_.values(data.toJSON()), function(value) {
return partialMatch(value, filterValue);
});
});
};
Note: This method is computationally pretty inefficient, as it involves first looping over all the items in the collection, then all the fields of each item, then all words in that item value. In addition there are a few nested functions declared inside loops, so the memory footprint is not optimal. If you have a small set of data, that should be OK, but if needed, there's a number of optimizations that can be done. I might come back later and edit this a bit, if I have time.
/code samples not tested

Categories