2
\$\begingroup\$

I wrote this to be able to compare two different arrays to figure out how many words were in common as a percentage value.

The use case is that I have array a which is a list of the most commonly known and used words in a language (normally something like 2000 different words). In array b I have the text.

The end result is the percentage usage of commonly used words as a means to figure out total readability of the text.

Note: The intersect_array function is used elsewhere in the full fledged application.

The full functioning CodePen can be viewed here: http://codepen.io/MarkBuskbjerg/pen/rWWRbX?editors=0010

The JS goes like this:

tinymce.init({
  selector: '#myTextArea',
  height: 300,
  setup: function(ed) {
    ed.on('keyup', run);
  },
  init_instance_callback: "run"
});

// helper function that matches two arrays with each other
function intersect_arrays(a, b) {
  var sorted_a = a.concat().sort();
  var sorted_b = b.concat().sort();
  var common = [];
  var a_i = 0;
  var b_i = 0;

  while (a_i < a.length && b_i < b.length) {
    if (sorted_a[a_i] === sorted_b[b_i]) {
      common.push(sorted_a[a_i]);
      a_i++;
      b_i++;
    } else if (sorted_a[a_i] < sorted_b[b_i]) {
      a_i++;
    } else {
      b_i++;
    }
  }
  return common;
}

// simple addition function
function add(a, b) {
  return a + b;
}

function run(inst) {
  var sum;
  var percentageUsage;
  var words = ["store", "javascript", "hammertime"];
  var result = {};
  var text = tinyMCE.get('myTextArea').getContent({
      format: 'text'
  })
    .replace(/<([^>]+)>|[\.\+\?,\(\)"'\-\!\;\:]/ig, "")
    .toLowerCase()
    .split(/[\s]/)
    .filter(Boolean);
  var textLength = text.length;
  var match = intersect_arrays(text, words);

  for (var y = 0; y < match.length; y++) {
    for (var i = 0; i < text.length; ++i) {
       if(text[i] === match[y]) {
         if (!result[text[i]])
            result[text[i]] = 0;
            ++result[text[i]];
       }
    }
  }

 sum = Object
    .values(result)
    .reduce(add, 0);

   percentageUsage = textLength / sum;

   document.getElementById('totalWords').innerHTML = textLength;
   document.getElementById('totalPercentage').innerHTML = percentageUsage;
}

Any ideas as to how I can handle the code more effective, easier or just in a more beautiful way is more than welcome. I'm quite new at JS so still wanna learn a lot about best practice and stuff.

\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

I'm assuming you have already broken your text down into its individual words and stripped the punctuation marks, etc, in your text array?

If so, the percentage of words from the given text array that match your word-list should be pretty easy to find.

Something along the lines of:

function matchPerc(wordList, text){
  var matches = text.filter((word)=>{return wordList.includes(word)}).length
  return (matches / text.length * 100).toFixed(2) + '%'
}
\$\endgroup\$
0
\$\begingroup\$

You should use Set as result data structure for intersect_arrays:

function intersect_arrays(a, b) {
    var sorted_a = a.concat().sort(); //are you using `concat()` to create a new array?
    var sorted_b = b.concat().sort();
    var common = new Set();
    ...
}

now you can calculate the sum as follow:

var sum = 0;
for (var i = 0; i < text.length; ++i) {
    if(match.has(text[i]))
        sum++;
    }
}

EDIT: if you can't change intersect_arrays you can transform the result array to a Set

EDIT2: if you don't mind to use something else, than intersect_arrays you can just add all the words to a Set and completely skip intersect_arrays and directly calculate sum using that Set

\$\endgroup\$
0
\$\begingroup\$

If I get this correctly, you just want to count how many occurrences of the frequent words appear in the main text and get a percentage in relation to the total number of words in the text.

If that's the case you're doing a lot more work than necessary because you don't need to keep track of which words are there, just how many.

What you can do is have an object (well, actually a map), so you can usually consider the access to be O(1) and just count the occurrences. This way you have a total O(n) running time instead of n log(n) plus another n log(n) because of the two sorts you're doing right now.

Something like this:

function frequentPercentage(mainText, frequentWords) {
  count = 0;
  mainText.forEach(function(word) {
    if (word in frequentWords) {
        count++;
    }
  });
  return (count * 100) / mainText.length;
}

frequentWords = {"store": 0, "javascript": 0, "hammertime": 0};
mainText = "Indsæt din tekst her. Det her er bare en helt almindelig tekst, der ikke besidder de store armbevægelser. Den har dog eksempelvis en meget lang sætning, der mest af alt er lang, fordi der simpelthen skal være en rigtig lang sætning, der kan udløse en alarm for lange sætninger, når dette fantastiske stykke værktøj begynder at virke. Så nu er det bare i gang med at skrue på javascript knapper, så skal der nok ske noget. Sig det så. Sig hammertime. Hammertime. Hammertime.".replace(/<([^>]+)>|[\.\+\?,\(\)"'\-\!\;\:]/ig, "").toLowerCase().split(/[\s]/);

console.log(frequentPercentage(mainText, frequentWords));

If you really, really want to have the common elements between the two, you could do something like:

var common = mainText.filter(function(word) {
    return (word in frequentWords);
}));

If you have two arrays instead of an array and an object, you can use:

var frequentWordsArr = ["store", "javascript", "hammertime"];

console.log(mainText.filter(function(word) {
    return (frequentWordsArr.indexOf(word) != -1);
}));

Since you say that intersect is also used elsewhere, maybe you want to be more generic. In that case you can add an intersect function to the Array prototype:

Array.prototype.intersect = function(secondArray) {
  return this.filter(function(item) {
      return (secondArray.indexOf(item) != -1);
  });
}

var common = mainText.intersect(frequentWordsArr);

EDIT: Since this may not be clear enough, doing an intersection this way (using only arrays) is going to be O(N^2), because indexOf is going to be O(n) instead of O(1), so be careful if you use this approach on big data sets.

\$\endgroup\$
2
  • \$\begingroup\$ complexity of your 2 last examples should be worse, than what he already has: 2*n*log(n) + 2n -> n^2/2 \$\endgroup\$
    – JohnnyAW
    Jan 5, 2017 at 16:40
  • \$\begingroup\$ Yes, I was wondering if I had to specify that, and I guess so. I'll edit the answer to be more clear on that point, thanks. \$\endgroup\$
    – ChatterOne
    Jan 5, 2017 at 21:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.