How to remove ALL stop words from text? - javascript

I'm trying to using this JavaScript code:
var aStopWords = new Array ("a", "the", "blah"...);
(code to make it run, full code can be found here: https://jsfiddle.net/j2kbpdjr/)
// sText is the body of text that the keywords are being extracted from.
// It's being separated into an array of words.
// remove stop words
for (var m = 0; m < aStopWords.length; m++) {
sText = sText.replace(' ' + aStopWords[m] + ' ', ' ');
}
to get the keywords from a body of text. It works quite well, however, the issue I'm having is that it only seems to iterate through and ignore one instance of the words in the array aStopWords.
So if I have the following body of text:
how are you today? Are you well?
And I put var aStopWords = new Array("are","well") then it seems it will ignore the first instance of are, but still show the second are as a keyword. Whereas it will completely remove / ignore well from the keywords.
If anyone can help ignore all instances of the words in aStopWords from the keywords, I'd greatly appreciate it.

You can easily do this like this.
First, it splits the text into keywords. Then, it goes through all the keywords. While going through, it checks if it is a stopword. If so, it will be ignored. If not, the occurrence number of this keyword in the result object will be increased.
Then, the keywords are in a JavaScript object in the following form:
{ "this": 1, "that": 2 }
Objects are not sortable in JavaScript, but Arrays are. So, a remapping to the following structure is necessary:
[
{ "keyword": "this", "counter": 1 },
{ "keyword": "that", "counter": 2 }
]
Then, the array can be sorted by using the counter attribute. With the slice() function, only the top X values can be extracted from the sorted list.
var stopwords = ["about", "all", "alone", "also", "am", "and", "as", "at", "because", "before", "beside", "besides", "between", "but", "by", "etc", "for", "i", "of", "on", "other", "others", "so", "than", "that", "though", "to", "too", "trough", "until"];
var text = document.getElementById("main").innerHTML;
var keywords = text.split(/[\s\.;:"]+/);
var keywordsAndCounter = {};
for(var i=0; i<keywords.length; i++) {
var keyword = keywords[i];
// keyword is not a stopword and not empty
if(stopwords.indexOf(keyword.toLowerCase()) === -1 && keyword !== "") {
if(!keywordsAndCounter[keyword]) {
keywordsAndCounter[keyword] = 0;
}
keywordsAndCounter[keyword]++;
}
}
// remap from { keyword: counter, keyword2: counter2, ... } to [{ "keyword": keyword, "counter": counter }, {...} ] to make it sortable
var result = [];
var nonStopKeywords = Object.keys(keywordsAndCounter);
for(var i=0; i<nonStopKeywords.length; i++) {
var keyword = nonStopKeywords[i];
result.push({ "keyword": keyword, "counter": keywordsAndCounter[keyword] });
}
// sort the values according to the number of the counter
result.sort(function(a, b) {
return b.counter - a.counter;
});
var topFive = result.slice(0, 5);
console.log(topFive);
<div id="main">This is a test to show that it is all about being between others. I am there until 8 pm event though it will be late. Because it is "cold" outside even though it is besides me.</div>

Related

How to loop over an Array from an API where some properties has null as value, without modifying it

I am practising with vainilla JS, fetching some data from a public API, and manipulating the DOM with them.
I am finding a problem i am not sure how to solve.
The object has some null values in some fields , but other times that field contain some information.
When I run a loop over it, it always breaks when it reaches one field with a null value.
I would like to know what the best approach should be.
One might be to "clean" the null values from the object, and store the "new cleaned" object in a variable . But guess that´s not the way to go if the API were big.
My second idea would be to include an if condition in the loop, saying to jump to the next one, if the value found is === null , something like this :
function createSomeCards(myObject) {
for (let i=0; i < myObject.lenght; i++) {
if (value === null) { i = i+1 } else { i = i }
here would go the rest of the code to build the cards
i don`t know if that is the approach (even if my code is waz wrong), or should be different.
The object has an structure similar to this :
myObject = [
{
"Habitat Impacts": "Area closures ",
"Image Gallery": [
{
"src": "anImage.jpg",
"alt": "some text",
"title": "more text"
},
{
"src": null,
"alt": "additional text",
"title": "a title here"
},
{
"src": "otherImg.jpg",
"alt": "imgInfor",
"title": null
}
],
"Management": null,
"Scientific Name": "Urophycis tenuis",
"Species Illustration Photo": {
"src": null,
"alt": "Illustration",
"title": ""
},
"Ecosystem Services": null,
"Servings": "1",
"last_update": "05/19/2021 - 13:04"
}]
I update the comment with the JS code in which I am finding the issue with the Null values
function createCards(myObject) {
let cardContainer = document.getElementById('cardContainer');
for (let i = 0; i < myObject.length; i++) {
aInfoButtonLeft.onclick = function () {
let divSlidAct = document.getElementById('sliderImg')
let imgSlidAct= document.createElement('img');
imgSlidAct.setAttribute('src', myObject[i]['Image Gallery'][i]['src']);
imgSlidAct.append(divSliderActive);
}
}
}
I am going to leave my orignal answer under this one because I think it still relevant.
you have an array in an array and you are not looping over the second array "Image Gallery". Adding a second loop lets you get all the images.
I added a check to see if the src was null. this should stop any errors you get about src being null.
function createCards(myObject) {
let cardContainer = document.getElementById('cardContainer');
for (let i = 0; i < myObject.length; i++) {
for (let x=0; x < myObject[i]["Image Gallery"].length; x++) {
if (myObject[i]['Image Gallery'][x]["src"] === null) {
// the source is empty do nothing
}else{
aInfoButtonLeft.onclick = function () {
let divSlidAct = document.getElementById('sliderImg')
let imgSlidAct= document.createElement('img');
imgSlidAct.setAttribute('src', myObject[i]['Image Gallery'][x]["src"]);
imgSlidAct.append(divSliderActive);
}
}
}
}
}
ORIGNAL ANSWER
You have 2 1 issue.
you have an array in an array and you are not looping over the second array "Image Gallery"
2. the 'src' field is an attribute of an object and not an index of an array.
The fact that you have null values doesn't matter. the loop will continue.
See the code below with comments.
let myObject = [
{
"Habitat Impacts": "Area closures ",
"Image Gallery": [
{
"src": "anImage.jpg",
"alt": "some text",
"title": "more text"
},
{
"src": null,
"alt": "additional text",
"title": "a title here"
},
{
"src": "otherImg.jpg",
"alt": "imgInfor",
"title": null
}
],
"Management": null,
"Scientific Name": "Urophycis tenuis",
"Species Illustration Photo": {
"src": null,
"alt": "Illustration",
"title": ""
},
"Ecosystem Services": null,
"Servings": "1",
"last_update": "05/19/2021 - 13:04"
}];
//loop over my object
for (let i=0; i < myObject.length; i++) {
//loop over the image gallery
for (let x=0; x < myObject[i]["Image Gallery"].length; x++) {
console.log("src: " + myObject[i]["Image Gallery"][x].src); //notice it .src and not ["src"]
console.log("alt: " +myObject[i]["Image Gallery"][x].alt);
console.log("title: " +myObject[i]["Image Gallery"][x].title);
//I commented out this section so the snippet would work
/*
aInfoButtonLeft.onclick = function () {
let divSlidAct = document.getElementById('sliderImg')
let imgSlidAct= document.createElement('img');
imgSlidAct.setAttribute('src', myObject[i]['Image Gallery'][x].src);
imgSlidAct.append(divSliderActive);
*/
}
}
Please change the question title, because you're not looping over an object where there's some null values, but you're looping over an array that only has non-null values in your example. The item of that loop can have a property that is sometimes null. That's a different issue.
You asked about the best approach.
"clean" the null values and store the "new cleaned" object in a variable
This is not necessary and yes, this would blow up your memory usage and would consume time
include an if condition in the loop, saying to jump to the next one, if the null value found
if (item.property === null) {
// jumping to the next item of the loop
continue;
}
// do the processing
You can either jump to the next (continue) or you don't do the processing for that item (you do the processing only if not null) like this:
if (item.property !== null) {
// do the processing
}
You could even be safer if checking properties by hasOwnProperty() like here: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
I recommend using functional programming and filtering the array by using https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter like this - this results in a much clearer code:
// for this you could also use myObject.forEach(), but that's not the question here
for (let i = 0; i < myObject.length; i++) {
// filtering image gallery for items that ...
myObject[i]['Image Gallery'] //
// have a non-null src
.filter(item => item.src !== null) //
// and do for each item that following...
.forEach(item => {
console.log('src: ' + item.src); //notice it .src and not ["src"]
console.log('alt: ' + item.alt);
console.log('title: ' + item.title);
});
}

Javascript loop works the first time ran, but gives NaN the second time without logic

I'm developing a javascript game and am altering values from a JSON file through a loop. The loop however occasionally replaces the values it should alter with "NaN" and grabs a random letter from the prefix word of the array. I tried debugging the values and putting in fixed creature values, but that has made me none the wiser.
The code works THE FIRST TIME RAN in JSFiddle: https://jsfiddle.net/ezwad5mL/2/ but whenever you run it a second time, it overwrites the values in the loop with NaN and the letter. I think it's because the function random_int needs 2 values but it only inputs 1 the second time you run it, which is somehow the same value from the previous input (which it altered in the second For loop). What I don't understand is how this code doesn't reset the storedDungeon if it fires the second time.
I understand the problem I think, but I have no clue what's wrong with what I wrote and why it does okay the first time, but screws up the second time.
function random_item(items){
return items[Math.floor(Math.random()*items.length)];
}
function random_int(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
var storedDungeon = []
const jsonCreatures = {
"easy": [
{ "name": "Scorchbird", "hp": [6,13], "prefix": ["Weak", "Small", "Young", "Wild"],
"damage": [1,5], "droprateCommon": [0,60], "droprateRare": [70, 90]},
{ "name": "Reanimated Corpse", "hp": [8,15], "prefix": ["Weak", "Festering"], "damage":
[3,5], "droprateCommon": [0,40], "droprateRare": [50, 80]}
]}
var randNumber = 2
for (let i = 0; i < randNumber; i++) {
let randomObject = random_item(jsonCreatures.easy)
storedDungeon.push(randomObject)
}
for (let o = 0; o < storedDungeon.length; o++) {
storedDungeon[o].hp = random_int(storedDungeon[o].hp[0], storedDungeon[o].hp[1])
storedDungeon[o].damage = random_int(storedDungeon[o].damage[0],storedDungeon[o].damage[1])
storedDungeon[o].prefix = random_item(storedDungeon[o].prefix)
}
console.log(storedDungeon)
To understand the problem we need to understand how arrays work.
The following example may open your eyes to the problem.
const creatures = [
{
name: 'Scorchbird'
}
]
const dungeon = []
dungeon.push(creatures[0])
dungeon[0].name = 'Reference to the object!'
console.log(creatures)
// [
// {
// name: 'Reference to the object!'
// }
// ]
When we add a creature (object) to our dungeon array
dungeon.push(creatures[0])
we actually add a reference to the original object, not a copy of it.
This means that when you do
storedDungeon[o].hp = random_int(
storedDungeon[o].hp[0],
storedDungeon[o].hp[1]
)
you alter the original creatures objects and their properties.
In this case the array hp: [6, 13] is replaced with a random (single!) number, for example hp: 8
And when your code runs the second time there is now hp array to do hp[0] and hp[1], just a single number. This is why the function random_int returns NaN (not a number).
The same effect happens with the damage and with the prefix. However because prefix is a string the random_item function will return a random character in this string. That's because the characters of a string can be accessed by their index like in an array: "im a string"[1] = "m"
I think Nico Gräf's explanation is correct. To fix this, you can create a clone of the object and have it pushed to the storedDungeon.
storedDungeon.push({...randomObject})
Please note that spread syntax doesn't deep clone your object. So it only works to the first level of the object, which should be fine with your current design.

Making a Search Filter with JQuery?

So I have a Table made from some json data...
{
"AKH":{
"name": "Amonkhet",
"code": "AKH"
"cards": [
{
"artist": "Izzy",
"cmc": 3,
"colorIdentity": [
"W"
],
"colors": [
"White"
],
"id": "df3a6e0336684c901358f3ff53ec82ff5d7cdb9d",
"imageName": "gideon of the trials",
"layout": "normal",
"loyalty": 3,
"manaCost": "{1}{W}{W}",
"multiverseid": 426716,
"name": "Gideon of the Trials",
"number": "14",
"rarity": "Mythic Rare",
"subtypes": [
"Gideon"
],
"text": "+1: Until your next turn, prevent all damage target permanent would deal.\n0: Until end of turn, Gideon of the Trials becomes a 4/4 Human Soldier creature with indestructible that's still a planeswalker. Prevent all damage that would be dealt to him this turn.\n0: You get an emblem with \"As long as you control a Gideon planeswalker, you can't lose the game and your opponents can't win the game.\"",
"type": "Planeswalker — Gideon",
"types": [
"Planeswalker"
]
},
The Table row ends up looking like this for each of the cards. at the moment I only Attach the ID, Card name, and Mana Cost to each row
<td><a href="#" onclick="showInfo(this.id)"
id="df3a6e0336684c901358f3ff53ec82ff5d7cdb9d">Gideon of the Trials</a></td>
Now I want to search through these cards. (Keep in mind there are over 17,000 different cards that will be on this list) I can get it to find the things.. But I'm having several different issues... Either it finds them all but doesn't hide the rest of the list, or it hides the whole list and only displays one of the found cards.
So question A... What am I missing to make the search work correctly?
$(document).on('change', 'input[type=checkbox]', function() {
var lis = $('.cardsRow')
$('input[type=checkbox]').filter(':checked').each(function(){
filterKeyB = $(this).attr('id')
filterKeyA = $(this).attr('name')
$.each(json, function(setCode, setListing) {
$.each(setListing.cards,function(cardNum, cardListing){
var x = Object.keys(cardListing)
var y = Object.keys(cardListing).map(function (key){
return cardListing[key]
})
for (i = 0; (i < x.length); i++) {
if(x[i] === filterKeyA){
if (y[i] instanceof Array){
var holder = y[i]
var valueArr =[]
for(var k = 0; k < holder.length; k++){
valueArr = holder.join('|').toLowerCase().split('|')
var foundIt = valueArr.includes(filterKeyB)
}
}else{
var stringy = y[i]
var stringyA= stringy.toLowerCase().replace(/\s/g, '')
if (stringyA === filterKeyB){
var foundIt = true
}
}
if(foundIt === true){
$winner = cardListing.name
for (k = 0; (k < lis.length); k++){
if (lis[k].innerText.indexOf($winner) != -1) {
$(lis[k]).show()
}
}
}
}
}
})
Question B... Since you are already here... Would it be better practice to attach the data that can be searched to the element itself? Maybe just the most searched (Like Name and Mana) and have more advanced queries go through the data again?
I don't understand why the code isn't working or even how it works, it looks like it references some functions that aren't defined in the sample. But I can share with you a really simple/intuitive way to filter stuff, I hope you find it useful.
Native filter method is so useful for what you're trying to do, it takes a callback that takes current element as an arg and returns true or false, if true, the element is included in the new array it produces.
But filter only takes one function, and you have many filters, so let's make a function that combines many filter Fns together into one fn, so you can pass them in all at once:
const combineFilters = (...fns) => val => fns.reduce((prev, curr) => prev || curr(val), false);
OK, how about storing the names of the filter functions as keys in an object so we can reference them using a string? That way we could give each checkbox an ID corresponding to the name of the filter function they are supposed to apply, and makes things really easy to implement (and read):
const filterFns = {
startsWithG(card) {
return card.name[0] === 'G';
},
//etc.
};
OK, time to get the IDs of all the checkboxes that are clicked, then map them into an array of functions.
const filters = $('input[type=checkbox]')
.filter(':checked')
.map((e, i) => $(i).attr('id'))
.get()
.map(fnName => filterFns[fnName])
(Assume the relevant data is stored in a var called...data.) We can use combineFilters combined with filters (array of Fns) to activate all of the relevant filters, then map the resulting array of matching objects into the HTML of your choosing.
const matches = data.cards
.filter(combineFilters(...filters))
.map(card => `<div>${card.name}</div>` );
Then time to update DOM with your matches!
As others have noted, if you need to do any more complicated filtering on objects or arrays, lodash library is your friend!

Javascript searching made easy

See Katana314's answer below
After spending days to figure out how to use regex for BBcode translation I have decided to reinvent the wheel and here I am.
I wrote a script that search for BBcode tags like [b][/b] or any other [] in the text.
I'm looking for an easy solution to compare the tags I found in the text and match them with a table, "array" if you prefer. I want to do it like that because eventually I will use a database to insert and remove BBcodes.
I kinda like the way I do the replacement cause its easy to populate. and there is no need for a Regex.
The replace fonction with it's array:
function bbToHtml(s) {
var p, pairs = [
{ "in": "[b]", "out": '<span style="color:red;">' },
{ "in": "[/b]", "out": '</span>' },
];
for (p in pairs) {
s = s.replace(pairs[p]["in"], pairs[p]["out"]);
}
return s;
}
Now that's fairly simple. What i would like to do is to compare my BBcode with the "in" values.
Lets say i have var BBCode = "[we]";
How do i proceed to see if it matchs one of my array value;
If the result is true then i can just do bbToHtml(BBCode); and if not i skip it or trow an error.
I love ES5 array functions.
function isBB(str) {
return !pairs.every(function(s) {
return s.in !== str;
});
}
every means "return true if this function returns true for all values of this array." This is assuming you are only looking for "[b]" and not "[b] " or any slight variation.
possible solution to your question:
var pairs = [
{ "in": "[b]", "out": '<span style="color:red;">' },
{ "in": "[/b]", "out": '</span>' },
];
function bbToHtml(s)
{
for (var p in pairs) {
s = s.replace(pairs[p]["in"], pairs[p]["out"]);
}
return s;
}
var search = '[we]';
if (pairs[search]) bbToHtml(search);

Javascript, check if current part object is inside other part object

I am trying to loop over an object and check if a certain property is inside any other certain parts of the object. I am trying to see if the first level.parentSearch is inside any of the other firstlevel.filters array (of objects).
So my object looks like this for example :
var currentfilters = [{
"id": "topics",
"name": "Topics",
"filters": [{
"id": "isSubTopic",
"label": "isSubTopic",
"search": "isSubTopic",
"num": 15
}]
}, {
"id": "isSubTopic",
"name": "isSubTopic",
"parentSearch": "isSubTopic",
"filters": [{
"id": "subtopicFilter",
"label": "subtopicFilter",
"search": "subtopicFilter",
"num": 2
}, {
"id": "subtopicFilter1",
"label": "subtopicFilter1",
"search": "subtopicFilter1",
"num": 2
}]
}, {
"id": "notSubTopic",
"name": "notSubTopic",
"parentSearch": "uniueParentSearch",
"filters": [{
"id": "notSubTopic1",
"label": "notSubTopic1",
"search": "notSubTopic1",
"num": 5
}]
}
];
So what I am trying to achieve is to loop over this object and modify it a little bit (if necessary) and return it. What I am trying to do is check the first level if the .parentSearch property is inside any of the other objects .filter array as a .search property. So in this example isSubTopic would be what I am looking for, because it is inside the Topics filters array.
This is my first time trying these kind of problem, so if I am missing anything please let me know. I figured since I want to return a modified object, it would be good to reduce this object and check inside. The part I am struggling with is the bit that checks if my current parentSearch (in the reduce loop) is in any other objects filter array (under the .search property). I have lodash to mess around with so I have tried both _.find and _.has, but I think I am not approaching this correctly. Any/all input would be greatly appreciated!
Here is what I was trying it with : https://jsfiddle.net/0fttkyey/32/
function checkIfSubtopic(memo, value, key) {
if(value.parentSearch) {
//check if value.parentSearch is in any of the value.filters
console.log("find?", _.find(currentfilters, value.parentSearch));
if(_.find(currentfilters, value.parentSearch)){
console.log("is subtopic?");
} else {
console.log("not sub topic");
}
}
return memo;
}
Interestingly, your currentfilters is not the same in the jsfiddle you provide, so it gives no result.
If I understand correctly, what you try to achieve is:
For each "filter" in currentfilters array, check if parentSearch member is truthy.
If so, loop through all other filters.
For each of these filters, loop through its filters array member.
If the search member of one of the objects in this filters array is equal to parentSearch value, keep the current "filter".
So you have 3 nested loops.
The 2 outer loops iterate over the same currentfilters array, except that the 2nd (inner) one skips the filter which parentSearch value is being searched for.
As soon as parentSearch value is found, break loops 2 and 3 (the 2 inner-most) and go to next item of 1st (outer-most) loop.
Without using lodash and building a new result array (instead of modifying currentfilters array in place which may yield unexpected results), you would have for example:
var i = 0,
j,
ifiltermax = currentfilters.length,
currentParentSearch,
currentFiltersArray,
k,
result = [];
for (; i < ifiltermax; i += 1) { // Loop 1.
currentParentSearch = currentfilters[i].parentSearch;
if (currentParentSearch) { // If `parentSearch` is truthy.
loop_j: for (j = 0; j < ifiltermax; j += 1) { // Loop 2.
if (j != i) { // Skip current filter which `parentSearch` is being searched for.
currentFiltersArray = currentfilters[j].filters;
for (k = 0; k < currentFiltersArray.length; k += 1) { // Loop 3.
if (currentFiltersArray[k].search === currentParentSearch) {
result.push(currentfilters[i]); // Keep current "filter".
console.log("Found " + currentParentSearch + " (from item #" + i + ") in item #" + j + ", filter #" + k);
break loop_j; // Break loops 2 and 3.
}
}
console.log("Did not find " + currentParentSearch + " (from item #" + i + ") in any other filter.");
}
}
}
}
console.log(result);
Updated jsfiddle: https://jsfiddle.net/0fttkyey/71/ (with currentfilters from the question above, instead of the one in jsfiddle version 32).

Categories

Resources