How to simplify javascript program? - javascript

I would love to minimize the program.
Maybe putting p1-16 in one line of code, same with count and gefunden?
Since my language skills are minimal I can't find the right information.
It would also be great if there was a way to minimize the if else statements in search hits pdf.
Right now I do the code by hand to add new pdfs, as in search hits pdf1 to pdf2. Any easier way would greatly help me.
function Suche(str){
p1=document.getElementById('pdf1').innerHTML;
p2=document.getElementById('pdf2').innerHTML;
p3=document.getElementById('pdf3').innerHTML;
p4=document.getElementById('pdf4').innerHTML;
p5=document.getElementById('pdf5').innerHTML;
p6=document.getElementById('pdf6').innerHTML;
p7=document.getElementById('pdf7').innerHTML;
p8=document.getElementById('pdf8').innerHTML;
p9=document.getElementById('pdf9').innerHTML;
p10=document.getElementById('pdf10').innerHTML;
p11=document.getElementById('pdf11').innerHTML;
p12=document.getElementById('pdf12').innerHTML;
p13=document.getElementById('pdf13').innerHTML;
p14=document.getElementById('pdf14').innerHTML;
p15=document.getElementById('pdf15').innerHTML;
p16=document.getElementById('pdf16').innerHTML;
p17=document.getElementById('pdf17').innerHTML;
gefunden1=0;
gefunden2=0;
gefunden3=0;
gefunden4=0;
gefunden5=0;
gefunden6=0;
gefunden7=0;
gefunden8=0;
gefunden9=0;
gefunden10=0;
gefunden11=0;
gefunden12=0;
gefunden13=0;
gefunden14=0;
gefunden15=0;
gefunden16=0;
gefunden17=0;
count1=0;
count2=0;
count3=0;
count4=0;
count5=0;
count6=0;
count7=0;
count8=0;
count9=0;
count10=0;
count11=0;
count12=0;
count13=0;
count14=0;
count15=0;
count16=0;
count17=0;
searchstring=str;
//Search Hits PDF1
endsearch=p1.length;
weiter=1;
if(p1.indexOf(str)>-1){
gefunden1=1;
pos1=p1.indexOf(str)+searchstring.length;
count1=count1+1;}
else{weiter=0;}
for(i = 1; i <=10; i++){
if(weiter==1){
if(p1.indexOf(str,pos1)>-1){
pos2=p1.indexOf(str,pos1)+searchstring.length;
if (pos2<=endsearch){
if(count1<10){
count1=count1+1;
pos1=pos2;}
else{
count1="Mehr als 10";
pos1=pos2;}}
else{
weiter=0;}}
else{
weiter=0;}}}
//Search Hits Pdf2
endsearch=p2.length;
weiter=1;
if(p2.indexOf(str)>-1){
gefunden2=1;
pos1=p2.indexOf(str)+searchstring.length;
count2=count2+1;}
else{weiter=0;}
for(i = 1; i <=10; i++){
if(weiter==1){
if(p2.indexOf(str,pos1)>-1){
pos2=p2.indexOf(str,pos1)+searchstring.length;
if (pos2<=endsearch){
if(count1<10){
count2=count2+1;
pos1=pos2;}
else{
count2="Mehr als 10";
pos1=pos2;}}
else{
weiter=0;}}
else{
weiter=0;}}}
and so on....

Why not use an array called p?
const p = []
for (let i=1; i<18; i++) {
p.push(document.getElementById(`pdf${i}`).innerHTML)
}
You can do the same for gefunden and count. The rest of your code, if repetitive, could go in a function and be called in another for loop.

I agree that this should be on code review. But you have a working code and ask how to make it better. So here you go.
replace all those variables that have the format variableN with an array. As soon as you have such a naming format you most of the time either want to use an array or change the name.
And you definitely want to clean up that function that searches for the occurrence of the given string.
Always define variables using const or let. And add comments to the code.
If something reflects a boolean, then use one instead of 0 or 1.
Make use of comments, that will also help others when looking at your code (and it also helps you if you look at your code after a while).
Use variables instead of magic numbers like 10.
and even if your preferred language is not the one used of the programming language, you should stick with the one the programming language use.
So here is a reworked version of your code:
// use an options parameter to make the function more flexible
function search(str , options) {
// destructuring the options into variables
const {maxCount, pdfCount} = options;
const items = [];
for (let i = 1; i <= pdfCount; i++) {
items.push({
p: document.getElementById(`pdf${i}`).innerHTML,
found: false,
count: 0
})
}
items.forEach(item => {
let count = 0;
let currentPosition = 0; // position where to start searching
let foundAtPosition;
// do-while loop to do at least one search
do {
foundAtPosition = item.p.indexOf(str, currentPosition);
// check if we found an occurence
if (foundAtPosition != -1) {
// increase the count
count++;
// set the current position after the found occurence
currentPosition = foundAtPosition + str.length;
}
// if we found more then maxCount we can leave the loop
if (count > maxCount) {
break;
}
// only continue the loop when something was found
// you could move "count > maxCount" to the while condition
// but the main purpose of the while loop is to iterate over the content
} while (foundAtPosition != -1);
// do the composing the information to be set for the item after the for loop,
// that makes many things clearer as it is not part of the searching process
// set found to true or false by checking if count is larger then 0
item.found = count > 0;
if (count > maxCount) {
item.count = `Mehr als ${maxCount}`;
} else {
item.count = count;
}
})
return items;
}
console.dir(search('hey', {maxCount: 10, pdfCount: 3}))
<div id="pdf1">
heyheyaaheyaaahey
</div>
<div id="pdf2">
heyheyaaheyaaahey
heyheyaaheyaaahey
heyheyaaheyaaahey
</div>
<div id="pdf3">
foo
</div>
You could also utelize str.split([separator[, limit]]) as mention here How to count string occurrence in string? and utilize the limit function.
But if you do that you really need to document the item.p.split(str, maxCount+2).length - 1 construct because that's hard to understand otherwise.
// use an options parameter to make the function more flexible
function search(str , options) {
// destructuring the options into variables
const {maxCount, pdfCount} = options;
const items = [];
for (let i = 1; i <= pdfCount; i++) {
items.push({
p: document.getElementById(`pdf${i}`).innerHTML,
found: false,
count: 0
})
}
items.forEach(item => {
// use maxCount + 2 to figure out if we have more then max count subtract 1 from length to get the actual count
const count = item.p.split(str, maxCount+2).length - 1
// set found to true or false by checking if count is larger then 0
item.found = count > 0;
if (count > maxCount) {
item.count = `Mehr als ${maxCount}`;
} else {
item.count = count;
}
})
return items;
}
console.dir(search('hey', {maxCount: 10, pdfCount: 3}))
<div id="pdf1">
heyheyaaheyaaahey
</div>
<div id="pdf2">
heyheyaaheyaaahey
heyheyaaheyaaahey
heyheyaaheyaaahey
</div>
<div id="pdf3">
foo
</div>

You can use arrays.
Arrays, in a simple way, put lots of info into one variable. For example:
var this_is_an_array=[0,1,2,3,4,5]
Arrays count from 0 and onwards, so do take note to start counting from 0
More in detail at w3 schools: https://www.w3schools.com/js/js_arrays.asp

Related

Dynamic variable declaration. Is this even the right method?

A little new to JS so be gentle :)
I'm trying to create a program that holds 5000+ boolean values that dynamically change based on other vars.
const chars = "abcdefghijklmnopqrstuvwxyz0";
const charsC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0"
const maxNum = 48;
const maxTile = 6;
var tile1, tile2, tile3, tile4, tile5, tile6
// test vars
var tile4 = "A27"
var t4a27 = false
// this snippet will be in an interval loop
for (let i = 1; i <= maxTile; ++i) {
for (let n = 0; n < chars.length; ++n) {
for (let j = 1; j <= maxNum; ++j) {
// this obviously doesnt work
var t[i][`${chars[n]}`][j];
// ^ ^ ^
if (tile[i] == `${charsC[n]}${j}`) {
t[i][h][j] = true;
console.log(t4a27)
} else {
t[i][h][j] = false;
}
}
}
}
For clarification a better word than "tile" for the vars could be "sprite" rather because its a point on the sprite.
The basic concept is the tile vars are designed to output their current position as a string value e.g. "A27". Then this loop will take that information and scan each tile subset to be true/false. So if the sprite lower right quadrant is inside "A27" the output would be t4a27 = true
In practice I can do this with just a lot of code (over 20,000 lines) but I figured their has to be an easier way that requires far less code.
This is probably not the right approach for your problem.
If you really need to store this amount of variables, it is probably best to put them in an object like so:
var tiles = {}
var tileName = 'abc'
// Dynamic setting:
tile[tileName] = true
// Dynamic reading:
console.log(tile[tileName])
I am wondering if you really want to store 5000 variables or if there is another way to calculate them at the time you need time, but that requires a bit more knowledge of the problem.
Javascript doesn't have this kind of ability to reflect local variables.
What you can do is attach all those variables to a global object, and proceed with: Object.keys(your_object) and your_object[key_name_here] = ...
I think you should use a 2-dim array for this. Or use a regular array of booleans with the appropriate size and do the index-magic yourself.
As you said, you are running on coordinates. A-27 is the same as row(1)+field(27) -- considering A is 1
If your field is 500x100, you create an Array as such: let gamefield = Array(500*100);
Warning: I have not tested this for syntax errors, but you should get the idea.
let gamefield = Array(500*100);
// optional gamefield.fill(true);
let row = idx => idx * 500;
let posIdx = (r, c) => row(r) + c;
// there is a sprite with a tiles property that returns
// 4 index positions for the sprite's quadrants as [r,c]
let quadrants = sprite.tiles.reportPositions()
// filter the quadrants where the gamefield at r,c is true
// this might also be a good case for some() instead of filter()
let collisions = quadrants.filter(pos => return gamefield[posIdx(...pos)]);
// if there is any of these, you can kill the sprite.
if(collisions.length > 0) sprite.kill();

Using an array of numbers to add up to a specific number

I've been trying to find out an efficient method of finding multiple numbers from an array that add up to a given number. In this instance I'm trying to find 3 numbers that total a target number.
I've got a basic working example below but unfortunately the recursive loop fails, it looks like there's an issue with it constantly looping. Ideally it would find the first possible answer and return it, but when it can't find an answer it gets stuck in the loop and breaks the browser.
Warning: the below code will break due to a memory leak:
let array = [5,6,3,3,6,67,2,2,6,7,7,2,1,3,4,5,67,7,4,2,5,6,3,3,6,67,2,2,6,7,7,2,1,3,4,5,67,7,4,2,5,6,3,3,6,67,2,2,6,7,7,2,1,3,4,5,67,7,4,2];
function findSums(arr, target, count) {
var result = [];
function recurse(start, leftOver, selection) {
if (leftOver < 0) return; // failure
if (leftOver === 0 && selection.length == count) {
result.push(selection); // add solution
return;
}
for (var i = start; i < arr.length; i++) {
recurse(i, leftOver-arr[i], selection.concat(arr[i]));
}
}
recurse(0, target, []);
return result;
}
// Demo
$('#input').on('blur', function(e){
let value = parseInt(e.target.value);
let result = findSums(array, value, 3);
if(result.length > 0){
$('#output').text(result[0]);
} else {
$('#output').text('Nothing found');
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>Input Number Below</h1>
<input id="input" type="number" />
<code id="output" ></code>
Well, it didn't break, when I tested, but still here are some tips:
You should set additional limitation to the count. You are making to much extra calls. When your function deals with really big sum, small numbers and small count it will call itself again until it reaches or overflows the desired sum, and only after that it will check current count. So you should add
if (selection.length > count) return;
Also. As I see there are many duplicates in your array, so I assume, that usage of the same number is allowed, but only if it is taken from another index. In your loop you are calling next recurse with the same start index. I think, you need
for (var i = start; i < arr.length; i++) {
recurse(i + 1, leftOver-arr[i], selection.concat(arr[i]));
}
And finally. This will not influence the recursive part of an algorithm, but maybe you'd like to filter out same results, or filter out your array to remove all duplicates.
Hope this helps.
Edit: sorry, missed the part about first possible solution. Here is the way to achieve this:
function recurse(start, leftOver, selection) {
if (leftOver < 0) return false; // failure
if (selection.length > count) return false;
if (leftOver === 0 && selection.length == count) {
result.push(selection); // add solution
return true;
}
for (var i = start; i < arr.length; i++) {
var res = recurse(i + 1, leftOver-arr[i], selection.concat(arr[i]));
if (res) return true;
}
}

Removing array elements that contain a number

I have seen several answers on Stackoverflow but none have helped me. I have a huge array of nearly 100,000 words, of which I am trying to remove all words that contain a number. I am using the following to do that:
for(var i = 0; i < words.length; i++){
if (hasNumbers(words[i]) {
words.splice(i, 1);
}
function hasNumbers(t)
{ return /\d/.test(t); }
It seems to work, but not all the time because I am still getting words that contain numbers. What can I change to make this remove all words that contain any number at all?
(I am using p5.js with my js)
That is because when you delete a word at index i, the next word will have index i, yet you still increase i, thereby skipping a word which you never inspect.
To solve this you can go backwards through your array:
for(var i = words.length - 1; i >= 0; i--){
// etc.
Here is a shorter way to remove words with digits:
words = words.filter(a => !hasNumbers(a));
Finally, you really should call your second function hasDigits instead of hasNumbers. The words "digit" and "number" have a slightly different meaning.
Here is a snippet, using ES6 syntax, that defines the opposite function hasNoDigits and applies it to some sample data:
let words = ['abcd', 'ab0d', '4444', '-)#', '&9ยต*'];
let hasNoDigits = s => /^\D*$/.test(s);
console.log(words.filter(hasNoDigits));
words = words.filter(a => !hasNumbers(a));
I had started writing this and then trincot answered. His answer is correct, though with the popular and widespread usage of ES5 array functions, I feel like you could simplify this down quite a bit.
window.addEventListener('load', function() {
var data = [
'w3.org',
'google.com',
'00011118.com'
]; //This is supposed to be your data, I didn't have it so I made it up.
var no_nums = data.filter(function(item) {
//Tests each string against the regex, inverts the value (false becomes true, true becomes false)
return !/\d/.test(item);
});
var results = document.getElementById('results');
no_nums.forEach(function(item) {
results.innerHTML += item + '<br />';
//Loops through each of our new array to add the item so we can see it.
});
});
<div id="results">
</div>

How to efficiently group items based on a comparison function?

I have a list of items and a comparison function f(item1, item2) which returns a boolean.
I want to generate groups out of these items so that all items in a same group satisfy the condition f(itemi, itemj) === true.
An item can be included in several groups. There is no mimimum size for a group.
I am trying to write an efficient algorithm in javascript (or other language) for that. I thought it would be pretty easy but I am still on it after a day or so...
Any pointer would be highly appreciated!
(the max size of my items array is 1000, if it helps)
OK, so here's how I did it in the end. I am still unsure this is completely correct but it gives good results so far.
There are two phases, first one is about creating groups that match the condition. Second one is about removing any duplicates or contained groups.
Here's the code for the first part, second part if quite trivial:
for(index = 0; index < products.length; index++) {
existingGroups = [];
seedProduct = products[index];
for(secondIndex = index + 1; secondIndex < products.length; secondIndex++) {
candidateProduct = products[secondIndex];
if(biCondition(seedProduct, candidateProduct)) {
groupFound = false;
existingGroups.forEach(function(existingGroup) {
// check if product belongs to this group
isPartOfGroup = true;
existingGroup.forEach(function(product) {
isPartOfGroup = isPartOfGroup && biCondition(product, candidateProduct);
});
if(isPartOfGroup) {
existingGroup.push(candidateProduct);
groupFound = true;
}
});
if(!groupFound) {
existingGroups.push([candidateProduct]);
}
}
}
// add the product to the groups
existingGroups.forEach(function(group) {
group.push(seedProduct);
if(group.length > minSize) {
groups.push(group);
}
});
}
Instead of items I use products, which is my real use case.
The bicondition tests for f(item1, item2) && f(item2, item1). To speed up and avoid duplication of calculus, I created a matrix of all condition results and I use this matrix in the biCondition function.

Counter array in Javascript

I am trying to make two arrays. the unique array can get the elements (no repeats) from the text array, and the counter one can count the frequency of each elements. but something is wrong with the counter one.
var unique_array=new Array();
var counter_array=new Array();
var unique=true;
for (i=0;i<text_array.length;i++){
if (unique_array.length==0){
unique_array.push(text_array[0]);
counter_array.push(1);
}
else if(unique_array.length>0&&unique_array.length<=text_array.length){
for (j=0; j<unique_array.length;j++){
if (text_array[i]==unique_array[j]){
counter_array[j]=counter_array[j]+1;// something wrong with the
alert(counter_array[j]);
var unique=false;
}
}
if (unique==true){
unique_array.push(text_array[i]);
counter_array.push[1];
}
unique=true;
}
You could also simplify the code down using a hashmap and some ES5 higher-order functions:
var text_array = ["a1","a1","a2","a3","a2","a4","a1","a5"];
var counts = {};
text_array.forEach(function(el) {
counts[el] = counts.hasOwnProperty(el) ? counts[el]+1 : 1;
});
var unique_array = Object.keys(counts);
var counter_array=unique_array.map(function(key) { return counts[key]; })
You can do this much more simply using an object. Let the values be the keys of an object, then just increment the count of each property as you go. At the end, you can get an array of the unique keys and their values:
var text_array = ['foo','bar','foo','fum','fum','foo'];
var i = text_array.length;
var obj = {};
while (i--) {
if (obj.hasOwnProperty(text_array[i])) {
obj[text_array[i]]++;
} else {
obj[text_array[i]] = 1;
}
}
console.log('Unique values: ' + Object.keys(obj)); // Unique values: foo,fum,bar
console.log('Value counts: ' + Object.keys(obj).map(function(v){return obj[v]})); // Value counts: 3,2,1
Note that the sorting of counts in the output is purely coincidental.
As Jasvir posted, you can make it pretty concise:
var obj = {};
text_array.forEach(function(v) {
obj.hasOwnProperty(v)? ++obj[v] : obj[v] = 1;
});
But the first example is a bit easier to digest.
I think the approach is what's making it difficult. A hash table / associative array would be much easier to work with.
With a hash table (an object {} in JS), you can store each word in a key and increment the value of the key when you encounter the word again. Then, at the end, just go through the hash table and gather up all the keys which have small values. Those are your unique words.
function get_unique_words(text_array) {
var hash_table, i, unique_words, keys;
hash_table = {};
for(i = 0; i < text_array.length; i++) {
if(hash_table[text_array[i]] === undefined) {
hash_table[text_array[i]] = 1;
} else {
hash_table[text_array[i]]++;
}
}
// go through the hash table and get all the unique words
unique_words = [];
keys = Object.keys(hash_table);
for(i = 0; i < keys.length; i++) {
if(hash_table[keys[i]] === 1) {
unique_words.push(keys[i]);
}
}
return unique_words.sort();
}
console.log(get_unique_words(
['blah', 'blah', 'blah', 'goose', 'duck',
'mountain', 'rock', 'paper', 'rock', 'scissors']
));
Some issues and suggestions :
Don't use var twice for the same variable.
Browsers deal with it ok, but for clarity you should only be declaring your variables once.
Always localize your loop counters - forgetting a var before your i and j will cause them to become global variables.
This is relevant when you have a page with lots of code - all global variables will show up in the debugger's watch list at all times, making it harder to debug your code.)
Use the array literal notation [] instead of the function form Array.
The function form is longer and it's easier to forget the new. It's also easier to read (IMO).
Use more whitespace (it won't bite), such as before and after an equals sign:
var x = 1;
// vs.
var x=1;
It makes the code easier to read and most people don't overdo it.
Indent your code when it's inside a block (e.g. function, if, else, while, for, etc.).
This makes it easier to read the control flow of the code and will help prevent bugs.
Use three equals signs (===) unless you are using loose equality on purpose.
This will help someone looking at your code later (probably yourself) understand better what the test is supposed to be testing.

Categories