Skip to content

Commit

Permalink
Merge pull request #21807 from Yoast/4417-fix-out-of-memory-errors-fo…
Browse files Browse the repository at this point in the history
…r-hebrew-site

analysis-worker.js consumes memory until the browser tab crashes on a Hebrew site.
  • Loading branch information
mhkuu authored Nov 22, 2024
2 parents ed272a2 + c359efb commit fb289e4
Showing 1 changed file with 55 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,26 @@ const adjustPosition = function( title, position ) {
};

/**
* Creates a cartesian product of the given arrays.
* This function is taken from: https://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript
*
* Creates a cartesian product of the given arrays using a lazy approach.
* @param {array} arrays The arrays to create the cartesian product of.
*
* @returns {array} The cartesian product of the given arrays.
*/
function cartesian( ...arrays ) {
return arrays.reduce( ( a, b ) => a.flatMap( d => b.map( e => [ d, e ].flat() ) ) );
function* lazyCartesian( ...arrays ) {
const lengths = arrays.map( arr => arr.length );
const indices = Array( arrays.length ).fill( 0 );

while ( true ) {
yield indices.map( ( index, position ) => arrays[ position ][ index ] );

let i = arrays.length - 1;
while ( i >= 0 && ++indices[ i ] === lengths[ i ] ) {
indices[ i ] = 0;
i--;
}
if ( i < 0 ) {
break;
}
}
}

/**
Expand All @@ -98,55 +109,51 @@ function cartesian( ...arrays ) {
* @returns {object} The new result object containing the results of the analysis.
*/
function findExactMatch( matchesObject, keyphrase, result, prefixedFunctionWordsRegex, title, locale ) {
let matchedPrefixedFunctionWords = [];
/*
For each matched word of the keyphrase, get the prefixed function word.
For example, for the matches array [ "القطط" ,"والوسيمة" ], the `matchedPrefixedFunctionWords` array will be [ "ال", "وال" ].
*/
matchesObject.matches.forEach( match => {
const { prefix: prefixedFunctionWord } = stemPrefixedFunctionWords( match, prefixedFunctionWordsRegex );
matchedPrefixedFunctionWords.push( prefixedFunctionWord );
} );
// For each matched word of the keyphrase, get the prefixed function word, and remove any duplicates.
// For example, for the matches array [ "القطط" ,"والوسيمة" ], the `matchedPrefixedFunctionWords` array will be [ "ال", "وال" ].
// We add an empty string to the array to account for the case where the word is not prefixed.
const matchedPrefixedFunctionWords = uniq(
matchesObject.matches.map( match => stemPrefixedFunctionWords( match, prefixedFunctionWordsRegex ).prefix ).concat( [ "" ] )
);

// Split the keyphrase into words. For example, the keyphrase "قطط وسيمة" will be split into [ قطط", "وسيمة" ].
const splitKeyphrase = keyphrase.split( " " );
let keyphraseVariations = [];

// Add an empty string to the array to account for the case where the word is not prefixed, remove duplicates.
matchedPrefixedFunctionWords = uniq( matchedPrefixedFunctionWords.concat( [ "" ] ) );
/*
Create an array of arrays, where each array contains each word of the keyphrase with function word prefixes attached.
For example, when the split keyphrase is [ "قطط", "وسيمة" ] and the matchedPrefixedFunctionWords is [ "ال", "وال", "" ],
the array would be: [ [ "والقطط","القطط", "قطط" ], [ "والوسيمة" ,"الوسيمة", "وسيمة" ] ].
*/
const arrays = [];
splitKeyphrase.forEach( word => {
arrays.push( matchedPrefixedFunctionWords.map( prefixedFunctionWord => prefixedFunctionWord + word ) );
} );
/*
Create the cartesian product of the created arrays: to create all possible combinations of the previously created arrays.
For example, the cartesian product of [ [ "والقطط","القطط", "قطط" ], [ "والوسيمة" ,"الوسيمة", "وسيمة" ] ] will be:
...[ [ "والقطط", "والوسيمة" ], [ "والقطط", "الوسيمة" ], [ "والقطط", "وسيمة" ], [ "القطط", "والوسيمة" ]]
*/
keyphraseVariations = cartesian( ...arrays );
// Turn the keyphrase combination array into strings. For example, [ "والقطط", "والوسيمة" ] will be turned into "والقطط والوسيمة".
keyphraseVariations = keyphraseVariations.map( variation => Array.isArray( variation ) ? variation.join( " " ) : variation );
keyphraseVariations.forEach( variation => {
// Check if the exact match of the keyphrase combination is found in the SEO title.
const foundMatch = wordMatch( title, variation, locale, false );
if ( foundMatch.count > 0 ) {
result.exactMatchFound = true;
// Adjust the position of the matched keyphrase if it's preceded by non-prefixed function words.
result.position = adjustPosition( title, foundMatch.position );
// Create an array of arrays, where each array contains word of the keyphrase with function word prefixes attached
// and the word is present in the SEO title.
// For example, when the split keyphrase is [ "قطط", "وسيمة" ] and the matchedPrefixedFunctionWords is [ "ال", "وال", "" ],
// the array would be: [ [ "والقطط","القطط", "قطط" ], [ "والوسيمة" ,"الوسيمة", "وسيمة" ] ].
const arrays = splitKeyphrase.map(
word => matchedPrefixedFunctionWords
.map( prefixedFunctionWord => prefixedFunctionWord + word )
.filter( keyphraseWord => wordMatch( title, keyphraseWord, locale, false ).count > 0 )
);

// Check if any of the arrays is empty, which means that the keyphrase is not found in the SEO title.
if ( ! arrays.find( array => array.length === 0 ) ) {
// Loop over the cartesian product (i.e., all possible combinations) of the created arrays.
// For example, the cartesian product of [ [ "والقطط","القطط", "قطط" ], [ "والوسيمة" ,"الوسيمة", "وسيمة" ] ] will be:
// ...[ [ "والقطط", "والوسيمة" ], [ "والقطط", "الوسيمة" ], [ "والقطط", "وسيمة" ], [ "القطط", "والوسيمة" ]]
for ( const variation of lazyCartesian( ...arrays ) ) {
// Join the keyphrase combination into a string.
const variationStr = Array.isArray( variation ) ? variation.join( " " ) : variation;

// Check if the exact match of the keyphrase combination is found in the SEO title.
const foundMatch = wordMatch( title, variationStr, locale, false );
if ( foundMatch.count > 0 ) {
result.exactMatchFound = true;
// Adjust the position of the matched keyphrase if it's preceded by non-prefixed function words.
result.position = adjustPosition( title, foundMatch.position );
break;
}
}
} );
/*
This check if to account for the case where an exact match of the keyphrase is not found in the SEO title,
but it's found in the position is 0.
*/
}

// This check handles the case where the match is found at position 0.
if ( matchesObject.position === 0 ) {
result.position = 0;
}

return result;
}

Expand Down Expand Up @@ -215,7 +222,7 @@ function checkIfAllWordsAreFound( paper, researcher, keyword, result, prefixedFu
* (2) whether all (content) words from the keyphrase were found in the SEO title,
* (3) at which position the exact match was found in the SEO title.
*
* @param {Object} paper The paper containing SEO title and keyword.
* @param {Paper} paper The paper containing SEO title and keyword.
* @param {Researcher} researcher The researcher to use for analysis.
*
* @returns {KeyphraseInSEOTitleResult} An object containing the information on whether the keyphrase was matched in the SEO title and how.
Expand Down

0 comments on commit fb289e4

Please sign in to comment.