Multiple words search and calculation algorithm (Angular/Javascript) - javascript

I'm loading json file from database with two fields words and grade. Each word is graded for example true has 1 while lie has -1. Then i take input from text filed and i need to grade it based on grades from JSON file and then calculate score by summarizing the grades, but i just can't seem to find the way to do that. Words that are not in file are not being calculated.
I tried string.search match but it's to complicated and in the end i couldn't get result the way i wanted. I tried array searches same thing. I searched for on line solution, but no one has done anything similar so i can't copy it.
JSON
[
{"word":"true","grade":1},
{"word":"hate","grade":-1},
{"word":"dog","grade":0.8},
{"word":"cat","grade":-0.8}
]
String
"Dogs are wonderful but i prefer cats, cats, i can not lie although dog is a true friend".

The first thing I'd do is turn your JSON data into a map which can easily be searched - key would be the word, and value the grade:
var json = [
{"word":"true","grade":1},
{"word":"hate","grade":-1},
{"word":"dog","grade":0.8},
{"word":"cat","grade":-0.8}
];
var map = json.reduce(function(p,c){
p.set(c.word.toLowerCase(),c.grade);
return p;
}, new Map());
console.log(...map);
Then, its just a case of splitting your string, whilst also calculating the total score - again reduce can be used
var json = [
{"word":"true","grade":1},
{"word":"hate","grade":-1},
{"word":"dog","grade":0.8},
{"word":"cat","grade":-0.8}
];
var map = json.reduce(function(p,c){
p.set(c.word.toLowerCase(),c.grade);
return p;
}, new Map());
var input = "Dogs are wonderful but i prefer cats cats i can not lie although dog is a true friend";
var score = input.split(' ').reduce(function(p,c){
var wordScore = map.get(c.toLowerCase()) || 0;
return p + wordScore;
},0);
console.log(score);
Note that I have manually removed punctuation in the above input - I'll leave that as an exercise for you.
Also note that "cats" != "cat" so some of your words wont be found!

Let's first think of the algorithm. Two options:
Search and count the input string as many times as number of words in your JSON, or
Check each word in your input string against the JSON contents.
Since the JSON length is known and (I presume) shorter than the possible input string, I would tend to prefer option 2.
Now, after selecting option 2, you need to split the input string into words and create an array containing one word each entry of the array.
You can achieve this using the mystring.split(" ") method. This, of course, does not take into account punctuations, but you can handle this using the same method.
Now, you can add to each entry in your JSON a field to count the number of appearances of each entry in the JSON within the string.
Finally, you sum the product of the counters and the grade.

console.log((function(rules, str) {
var sum = 0;
Array.prototype.forEach.call(rules, function(rule) {
var match = str.match(rule.regexp);
match && (sum += str.match(rule.regexp).length * rule.grade);
console.log([rule.regexp, match&&match.length, rule.grade, match&&match.length * rule.grade, sum]);
});
return sum;
})([{
"regexp": /true/g,
"grade": 1
}, {
"regexp": /hate/g,
"grade": -1
}, {
"regexp": /dog/g,
"grade": 0.8
}, {
"regexp": /cat/g,
"grade": -0.8
}], "Dogs are wonderful but i prefer cats, cats, i can not lie although dog is a true friend"));
i use regexp rather than string, u can use string and convert to regex at run time, hope this would help

Related

Javascript foreach question: how to pick out the number element to form new array

I have some tasks to handle in my daily jobs, so I need to do it in a automatic way. My task is:
there will be some messages sent to my IM, and I need to append the first, second & third number to each links with a "|".
if there only 2 numbers in the number line, a 0 is needed in the first place.
For example, in the cleanResult example, I need it to be done like:
finalResult = ["https://www.example.com/firstlink|500",
"https://www.example.com/firstlink|150",
"https://www.example.com/firstlink|30",
"https://www.exmaple.com/secondlink|600",
"https://www.exmaple.com/secondlink|150",
"https://www.exmaple.com/secondlink|30",
"https://www.example.com/thirdlink|500",
"https://www.example.com/thirdlink|150",
"https://www.example.com/thirdlink|30",
"https://www.example.com/forthlink|600",
"https://www.example.com/forthlink|100",
"https://www.example.com/forthlink|20",
"https://www.example.com/fithlink|0",
"https://www.example.com/fithlink|200",
"https://www.example.com/fithlink|50"
]
Here's the codes I had done so far:
const urlRegex = /(https?\:\/\/)?([^\.\s]+)?[^\.\s]+\.[^\s]+/gi;
const digitRegex = /^(?=.*\d)[\d ]+$/;
cleanResult = ["https://www.example.com/firstlink",
"https://www.exmaple.com/secondlink",
"https://www.example.com/thirdlink",
"500 150 30",
"https://www.example.com/forthlink",
"600 100 20",
"https://www.example.com/fithlink",
"200 50"
]
cleanResult.forEach((item, index) => {
if (item.match(digitRegex)) {
//codes I don't know how to do...
}
})
Are elements in cleanResult always either a URL or a number? In that case, you could just check the first character of the string to see if it's a number (basically a non-url). If it's not a URL, then we know it's numbers, and we can do something with the URL, which should the the previous element:
// If it's a URL, we will store it here for future use
let currentURL = ''
cleanResult.forEach((item, index) => {
// Get the first character of this string
const first = item[0]
if (!Number.isInteger(first)) {
// This is NOT a number, so must be a URL,
// let's store it in our variable to use in the next loop
currentURL = item
} else {
// This IS a number, which means we need to do a few things:
// 1. Split into separate numbers
// 2. Create a new URL pattern from each number
// 3. Push to finalResult
// 1. Split by tab delimiter (?)
// splits by tab, and returns an array
const numbers = item.split('\t')
// 2. Create a new URL pattern from each number
numbers.forEach((n) {
// This should now give you the URL + | + the number:
// ex: https://example.com/firstlink|500
const newURL = currentURL + '|' + n
// 3. push to the finalResult array
finalResult.push(newURL)
})
}
})
I haven't tested it, but this is the process that I generally use: break it into smaller tasks and take it one step at a time. I also didn't use regex, just to make it easier. We're assuming that you will receive either a URL or a list of numbers separated by a tab. This means you can afford to keep it a bit simple.
I'm sure there are way more efficient ways to do it and in a lot fewer lines, but if you're just learning JS or programming, there is nothing wrong with being extra verbose so that you can understand early concepts.

Dynamic from fields using reactive forms in Angular?

I have a scenario like Need to edit the single quotes values (only single quotes values),
So I extracted the single quotes values using regex and prepare the reactive dynamic form.
onclick of performing edit button will show old step name above, new step name below, submit step will replace the step name in the original array.
WOrking fine as expected in few scenarios according to my approach, but in scenarios, I realized whatever algorithm I am following does not fulfill my requirement.
Below are the test cases
Test case 1:
Step Name: "Then I should hire an employee using profile '1' for 'USA'",
// Here --> '1', 'USA' values are editable
Test case 2: "And Employee should be hired on '01' day of pay period '01' of 'Current' Fiscal"
// '01', '01', 'Current'
Issues: in test case 2 if I tried to edit second 01 it is editing the first 01
I try to solve the perform edit function with help of indexof, substring functions
this.replaceString = this.selectedStep.name;
this.metaArray.forEach((element: any) => {
var metaIndex = this.replaceString.indexOf(element.paramValue);
if (metaIndex !== -1) {
const replaceValue = this.stepEditForm.controls[element['paramIndex']].value;
this.replaceString = this.replaceString.substring(0, metaIndex) + replaceValue + this.replaceString.substring(metaIndex + (element.paramValue.length));
}
});
but in indexof always find the first occurrence of a value in a string. So I realized my approach is wrong on performed it function
please find the attachment for the code
https://stackblitz.com/edit/angular-reactive-forms-cqb9hy?file=app%2Fapp.component.ts
So Can anyone please suggest to me how to solve this issue,
Thanks in advance
I added a function called matchStartingPositions that returns the starting position indexes of each match. Using this method you can then perform your edit by replacing the string just as you do, but we'll find the proper match to be replaced at the given position.
So in your line
var metaIndex = this.replaceString.indexOf(element.paramValue);
we can then add a second parameter to indexOf, that is the starting point:
var metaIndex = this.replaceString.indexOf(element.paramValue, startingPositions[element.paramIndex]);
The function for getting the index positions just looks for those single quotes in a given string:
matchStartingPositions(str) {
let count = 0;
let indices = [];
[...str].forEach((val, i) => {
if (val === "'") {
if (count % 2 == 0) {
indices.push(i);
}
count++;
}
});
return indices;
}
Here it is in action:
https://angular-reactive-forms-xhkhmx.stackblitz.io
https://stackblitz.com/edit/angular-reactive-forms-xhkhmx?file=app/app.component.ts

Is it possible to use code to transform a list in format 'list': "translation"?

I would like to be able to change a list of words in my coding, e.g.
var words= {
AFK Away from the keyboard 4U For you B4N By for now BBL Be back later BDAY Birthday CBA Can't be asked }
changed to this format:
var words= {
'AFK': "Away from the keyboard", '4U': "For you", 'B4N': "By for now", 'BBL': "Be back later", 'BDAY': "Birthday", 'CBA': "Can't be asked",
}
without having to change each word into the format manually using HTML/JavaScript. However I understand that this might not be possible, but I thought I'd see if anyone had an I idea on how to do it anyway. From what I read it looks as if I'll have to use Python and a Database, but I don't know anything about python what so ever really so I was hoping (probably vainly) that there is some HTML/JavaScript code that I haven't seen that solves this!
I found a similar question here, but it wasn't really what I wanted as it uses python: [turning data into a list
The thing I want to do is change all words in this format: e.g
AFK Away from the Keyboard
to a format with
'AFK': "Away from the keyboard",
the aim of this code is to translate text abbreviations to real English words which it is already doing, but in order to get a decent amount of words to translate I need to get words in the above format which would take forever if I formated each one individually. here is the rest of the code if that helps:
function replacer() {
var text= document.getElementById("textbox1").value;
for (var modifiers in translationwords){
text = text.replace(new RegExp('\\b' + modifiers + '\\b', 'gi'), translationwords[modifiers]); }
document.getElementById("textbox2").value=text;
document.getElementById("add").onclick= function storage() {
if(!document.cookie) document.cookie = "";
document.cookie = document.cookie +"<li>"+document.getElementById("textbox1").value+ ":"+"</li>";
document.cookie = document.cookie +"<li>" + document.getElementById("textbox2").value+ "</li>";
document.getElementById("array").innerHTML= document.cookie;
}
}
function textdelete(x) {
if (x.value=="ENTER TRANSLATION HERE"){
x.value="";
};
}
Thank You
If your words were in a string to begin with, then it would be arguably possible to translate it into an object the way you want it, but it might not be the most accurate translation.
Since it looks like all the abbreviations are all upper case and without spaces, we can look through the string, and set the all caps/numbers 'words' as properties, with the following string of text as the value. In javascript, something like this would work.
//set words as string
var string = "AFK Away from the keyboard 4U For you B4N By for now BBL Be back later BDAY Birthday CBA Can't be asked";
// create empty dictionary for storage
var dictionary = {};
// use regex to find all abbreviations and store them in an array
var abbreviations = string.match(/[A-Z0-9]+(?![a-z])\w/g);
// returns ["AFK", "4U", "B4N", "BBL", "BDAY", "CBA"]
// use regex to replace all abbreviations with commas...
englishWords = string.replace(/[A-Z0-9]+(?![a-z])\w/g, ',');
// Edit (see below):
englishWords = englishWords.replace(/\W,\W/g,',');
// End edit
// then split string into array based on commas
englishWords = englishWords.split(',').slice(1);
// finally loop over all abbreviations and add them to the dictionary with their meaning.
for(var i = 0; i < abbreviations.length; i++){
dictionary[abbreviations[i]] = englishWords[i];
}
Edit: the above solution still might have white space at the beginning or end of each english string. You can add this line of code just before splitting the string to remove the white space.
englishWords = englishWords.replace(/\W,\W/g,',');
Assuming you have the data as a list in python, or as a string, or that you can get it into that format:
>>> lst = 'AFK Away from the keyboard 4U For you B4N By for now BBL Be back later BDAY Birthday CBA Can\'t be asked'.split()
>>> lst
['AFK', 'Away', 'from', 'the', 'keyboard', '4U', 'For', 'you', 'B4N', 'By', 'for', 'now', 'BBL', 'Be', 'back', 'later', 'BDAY', 'Birthday', 'CBA', "Can't", 'be', 'asked']
Further assuming that the keywords are always in all-uppercase and that the values are never in all uppercase, and that there are no duplicate keys, you can create the following dictionary:
>>> keys = [x for x in lst if x.upper() == x]
>>> {keys[i]:' '.join(lst[lst.index(keys[i]):lst.index(keys[i+1])]) for i in range(len(keys)-1)}
{'BDAY': 'BDAY Birthday', 'BBL': 'BBL Be back later', '4U': '4U For you', 'B4N': 'B4N By for now', 'AFK': 'AFK Away from the keyboard'}
Let's say that this is your list of words:
["AFK","Away","from","the","keyboard","4U","For","you","B4N","By","for","now","BBL","Be","back","later","BDAY","Birthday","CBA","Can't","be","asked"]
Now, we can change that into an object simply by using regular JavaScript without any database or back-end langauge like Python.
var list = ["AFK","Away","from","the","keyboard","4U","For","you","B4N","By","for","now","BBL","Be","back","later","BDAY","Birthday","CBA","Can't","be","asked"] /*What we have*/;
var code = {} /*What we want*/;
var currKey = null /*Our current key*/, hasBeenSet = false /*Whether or not the property at our current key has been set*/;
for (var i = 0; i < words.length; i++) {
//If the current word is an abbreviation...
if (words[i] === words[i].toUpperCase()) {
currKey = words[i]; //We set currKey to it
hasBeenSet = false; //We make hasBeenSet false
}
//Otherwise, if the property at current key has been set, we add on to it.
else if (hasBeenSet) code[currKey] += " "+words[i];
//Otherwise, the property at current key hasn't been set...
else {
code[currKey] = words[i]; //We set the property to the current word
hasBeenSet = true; //We make hasBeenSet true
}
}
code //{ AFK: "Away from the keyboard", 4U: "For you", B4N: "By for now", BBL: "Be back later", BDAY:"Birthday", CBA:"Can't be asked" }

How to make a simple encoder and decoder?

I have the following code:
<DOCTYPE HTML>
<html>
<body>
<script type="text/javascript">
//Chat Encoder
//Made by Hducke aka Hunter Ducker
//VARS
var userInputA = "";
var userInputB = "";
var result = userInputB.split("");
//FUNCTIONS
var encodeMessage = function(){
var output = "";
userInputB = prompt("Type your message here:", "PLEASE TYPE YOUR MESSAGE IN LOWER CASE!");
for(var i = 0; i <= result.length; i++){
switch(result[i]){
case("a"):
result[i] = "1";
break;
case("b"):
result[i] = "2";
break;
case("c"):
result[i] = "3";
break;
}
var tempStr = "";
result[i] + tempStr;
}
return tempStr;
}
var decodeMessage = function(){
}
var promptUser = function(){
var tempBool = true;
while(tempBool){
userInputA = prompt("Type '1' to encode a message and '2' to decode a message!", "Type '1' or '2' here.");
switch(userInputA){
case("1"):
encodeMessage();
tempBool = false;
break;
case("2"):
decodeMessage();
tempBool = false;
break;
default:
alert("Try again. Please type a '1' or a '2'.");
}
}
}
var printMessage = function(){
alert(encodeMessage);
}
//LOGIC
promptUser();
printMessage();
</script>
</body>
</html>
Info: The way it is atm is it takes in the users input userInputB and parses it into separate characters. Then it sets the characters to a different character (scambles the character). Then it outputs the string to the user. My goal is to have it to where you can enter a message I love this website! and turn it into 1 2324 5654 7503947. Then another user can enter the encoded message and the decodeMessage function will decode the message and output it to the user.
First issue: It won't currently work as is.*
*EDIT: Now when I run the code after fixing the result[i].
Output I Get Now
Second issue: How can I do this (IE. Is there a better way of doing this)
Any tips can help. I'm kinda a noob at javascript. Thanks!
The first problem is with your input; result is not going to be updated when the user enters the prompt.
function encodeMessage() {
var output = '';
userInputB = prompt(...);
result = userInputB.split('');
...
}
The second problem is the encoding itself. Instead of using a giant switch, create an algorithm to perform the encoding. In your case, you have a simple 1:1 mapping of a character to a number, conveniently in the natural order.
Did you know that your computer stores those letters as numbers? 'a' is 97, 'b' is 98, etc. so you could simply subtract 96 from the character to get a=1, b=2, etc.
However, this poses a problem for your decoder once you reach 'j'. "java" is going to be turned into "101221", and if you simply perform the reverse of your encoder on that, you'll end up with "a`abba".
One option would be to return to your encoding scheme and the ASCII table. '1' is character 49; perhaps you could subtract 48 from your characters instead? 'a' would then become '1' (not much different from 1) and so on. 'j' becomes ':', and if you encode "java" you get ":1F1".
Once you're doing that, the reverse of your encoding scheme will become your decoder. Iterate over the encoded string, and add 48 instead of subtracting it.
UPDATE
I've updated the http://jsfiddle.net/ntyt4/5/ with a sample scenario of the encoding and decoding process. It should give you enough to get started.
A simpler way would be to use a dictionary with keys and their translated values. You could store this in an object literal as:
var translation = {
"a": 1,
"b": 2,
"c": 3,
"d": "A"
};
I've included a jsfiddle to show you an example. Just change the textbox value to one value to see the translation.
One thing to keep in mind though is that you will have to add every single character that needs to be translated to the object literal. For example "a" will not translate the upper case version "A" for you because it is a different character.
I don't know very well the javascript but you have three options:
Mapping the chars with two Vector
If you want to include the simple characters you need 25(a-z)+25(A-Z)+9(0-9)=59 conversion.
You can do this with an algoritm that if found a letter in the first vector for example at index "6", take the value from the corrispondent position in the second vector.
Decoding is the same way, only take from second vector and convert it in the equivalent for the first vector.
ASCII Table
The char letter '0' casted in integer is 48.
From 48-57 you have the number, in the range 65-90 you have the Upper char and in the range 97-122 you have the lower char. If you try to subtrack for example two your text is encoded in a simple system.
MD5 Algoritm
You can use/create a function that generate the md5 hash of a text, for example "encoding" in md5 is "84bea1f0fd2ce16f7e562a9f06ef03d3". If you want use an encript system for encript an area this is the better way.

Finding keywords in texts

I have an array with incidents that has happened, that are written in free text and therefore aren't following a pattern except for some keywords, eg. "robbery", "murderer", "housebreaking", "car accident" etc. Those keywords can be anywhere in the text, and I want to find those keywords and add those to categories, eg. "Robberies".
In the end, when I have checked all the incidents I want to have a list of categories like this:
Robberies: 14
Murder attempts: 2
Car accidents: 5
...
The array elements can look like this:
incidents[0] = "There was a robbery on Amest Ave last night...";
incidents[1] = "There has been a report of a murder attempt...";
incidents[2] = "Last night there was a housebreaking in...";
...
I guess the best here is to use regular expressions to find the keywords in the texts, but I really suck at regexp and therefore need some help here.
The regular expressions is not correct below, but I guess this structure would work?
Is there a better way of doing this to avoid DRY?
var trafficAccidents = 0,
robberies = 0,
...
function FindIncident(incident) {
if (incident.match(/car accident/g)) {
trafficAccidents += 1;
}
else if (incident.match(/robbery/g)) {
robberies += 1;
}
...
}
Thanks a lot in advance!
The following code shows an approach you can take. You can test it here
var INCIDENT_MATCHES = {
trafficAccidents: /(traffic|car) accident(?:s){0,1}/ig,
robberies: /robbery|robberies/ig,
murder: /murder(?:s){0,1}/ig
};
function FindIncidents(incidentReports) {
var incidentCounts = {};
var incidentTypes = Object.keys(INCIDENT_MATCHES);
incidentReports.forEach(function(incident) {
incidentTypes.forEach(function(type) {
if(typeof incidentCounts[type] === 'undefined') {
incidentCounts[type] = 0;
}
var matchFound = incident.match(INCIDENT_MATCHES[type]);
if(matchFound){
incidentCounts[type] += matchFound.length;
};
});
});
return incidentCounts;
}
Regular expressions make sense, since you'll have a number of strings that meet your 'match' criteria, even if you only consider the differences in plural and singular forms of 'robbery'. You also want to ensure that your matching is case-insensitive.
You need to use the 'global' modifier on your regexes so that you match strings like "Murder, Murder, murder" and increment your count by 3 instead of just 1.
This allows you to keep the relationship between your match criteria and incident counters together. It also avoids the need for global counters (granted INCIDENT_MATCHES is a global variable here, but you can readily put that elsewhere and take it out of the global scope.
Actually, I would kind of disagree with you here . . . I think string functions like indexOf will work perfectly fine.
I would use JavaScript's indexOf method which takes 2 inputs:
string.indexOf(value,startPos);
So one thing you can do is define a simple temporary variable as your cursor as such . . .
function FindIncident(phrase, word) {
var cursor = 0;
var wordCount = 0;
while(phrase.indexOf(word,cursor) > -1){
cursor = incident.indexOf(word,cursor);
++wordCount;
}
return wordCount;
}
I have not tested the code but hopefully you get the idea . . .
Be particularly careful of the starting position if you do use it.
RegEx makes my head hurt too. ;) If you're looking for exact matches and aren't worried about typos and misspellings, I'd search the incident strings for substrings containing the keywords you're looking for.
incident = incident.toLowerCase();
if incident.search("car accident") > 0 {
trafficAccidents += 1;
}
else if incident.search("robbery") > 0 {
robberies += 1;
}
...
Use an array of objects to store all the many different categories you're searching for, complete with an appropiate regular expression and a count member, and you can write the whole thing in four lines.
var categories = [
{
regexp: /\brobbery\b/i
, display: "Robberies"
, count: 0
}
, {
regexp: /\bcar accidents?\b/i
, display: "Car Accidents"
, count: 0
}
, {
regexp: /\bmurder\b/i
, display: "Murders"
, count: 0
}
];
var incidents = [
"There was a robbery on Amest Ave last night..."
, "There has been a report of an murder attempt..."
, "Last night there was a housebreaking in..."
];
for(var x = 0; x<incidents.length; x++)
for(var y = 0; y<categories.length; y++)
if (incidents[x].match(categories[y].regexp))
categories[y].count++;
Now, no matter what you need, you can simply edit one section of code, and it will propagate through your code.
This code has the potential to categorize each incident in multiple categories. To prevent that, just add a 'break' statement to the if block.
You could do something like this which will grab all words found on each item in the array and it will return an object with the count:
var words = ['robbery', 'murderer', 'housebreaking', 'car accident'];
function getAllIncidents( incidents ) {
var re = new RegExp('('+ words.join('|') +')', 'i')
, result = {};
incidents.forEach(function( txt ) {
var match = ( re.exec( txt ) || [,0] )[1];
match && (result[ match ] = ++result[ match ] || 1);
});
return result;
}
console.log( getAllIncidents( incidents ) );
//^= { housebreaking: 1, car accident: 2, robbery: 1, murderer: 2 }
This is more a a quick prototype but it could be improved with plurals and multiple keywords.
Demo: http://jsbin.com/idesoc/1/edit
Use an object to store your data.
events = [
{ exp : /\brobbery|robberies\b/i,
// \b word boundary
// robbery singular
// | or
// robberies plural
// \b word boundary
// /i case insensitive
name : "robbery",
count: 0
},
// other objects here
]
var i = events.length;
while( i-- ) {
var j = incidents.length;
while( j-- ) {
// only checks a particular event exists in incident rather than no. of occurrences
if( events[i].exp.test( incidents[j] ) {
events[i].count++;
}
}
}
Yes, that's one way to do it, although matching plain-words with regex is a bit of overkill — in which case, you should be using indexOf as rbtLong suggested.
You can further sophisticate it by:
appending the i flag (match lowercase and uppercase characters).
adding possible word variations to your expression. robbery could be translated into robber(y|ies), thus matching both singular and plural variations of the word. car accident could be (car|truck|vehicle|traffic) accident.
Word boundaries \b
Don't use this. It'll require having non-alphanumeric characters surrounding your matching word and will prevent matching typos. You should make your queries as abrangent as possible.
if (incident.match(/(car|truck|vehicle|traffic) accident/i)) {
trafficAccidents += 1;
}
else if (incident.match(/robber(y|ies)/i)) {
robberies += 1;
}
Notice how I discarded the g flag; it stands for "global match" and makes the parser continue searching the string after the first match. This seems unnecessary as just one confirmed occurrence is enough for your needs.
This website offers an excellent introduction to regular expressions
http://www.regular-expressions.info/tutorial.html

Categories