All files / owid-grapher/gridLang GrammarUtils.ts

96.84% Statements 92/95
80.95% Branches 17/21
100% Functions 4/4
96.84% Lines 92/95

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 1211x 9x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 75x 75x 75x 75x 75x 75x 75x 75x 75x 75x       75x 2x 2x 2x 1x 1x 1x 75x 75x 75x 75x 75x 75x 75x 75x 75x 45x 45x 45x 45x 45x 75x 573x 573x 45x 45x 75x 640x 640x 45x 45x 45x 45x 45x 45x 75x 334x 334x 334x 334x 334x 334x 334x 4409x 4409x 2897x 2897x 2897x 2897x 2897x 2715x 2715x 2715x 2715x 2715x 2715x                                                    
export const isBlankLine = (line: string[] | undefined) =>
    line === undefined ? true : line.join("") === ""
 
// Todo: figure out Matrix cell type and whether we need the double check
export const isEmpty = (value: any) => value === "" || value === undefined
 
// Adapted from: https://github.com/dcporter/didyoumean.js/blob/master/didYouMean-1.2.1.js
export const didYouMean = (
    str = "",
    options: string[] = [],
    caseSensitive = false,
    threshold = 0.4,
    thresholdAbsolute = 20
) => {
    if (!caseSensitive) str = str.toLowerCase()
 
    // Calculate the initial value (the threshold) if present.
    let maximumEditDistanceToBeBestMatch = Math.min(
        threshold * str.length,
        thresholdAbsolute
    )
 
    // Get the edit distance to each option. If the closest one is less than 40% (by default) of str's length, then return it.
    let closestMatch
    const len = options.length
    for (let optionIndex = 0; optionIndex < len; optionIndex++) {
        const candidate = options[optionIndex]
 
        if (!candidate) continue
 
        const editDistance = getEditDistance(
            str,
            caseSensitive ? candidate : candidate.toLowerCase(),
            maximumEditDistanceToBeBestMatch
        )
        if (editDistance < maximumEditDistanceToBeBestMatch) {
            maximumEditDistanceToBeBestMatch = editDistance
            closestMatch = candidate
        }
    }
 
    return closestMatch
}
 
const MAX_INT = Math.pow(2, 32) - 1
const getEditDistance = (stringA: string, stringB: string, maxInt: number) => {
    const aLength = stringA.length
    const bLength = stringB.length
 
    // Fast path - no A or B.
    if (aLength === 0) return Math.min(maxInt + 1, bLength)
    if (bLength === 0) return Math.min(maxInt + 1, aLength)
 
    // Fast path - length diff larger than max.
    if (Math.abs(aLength - bLength) > maxInt) return maxInt + 1
 
    // Slow path.
    const matrix = []
 
    // Set up the first row ([0, 1, 2, 3, etc]).
    for (let bIndex = 0; bIndex <= bLength; bIndex++) {
        matrix[bIndex] = [bIndex]
    }
 
    // Set up the first column (same).
    for (let aIndex = 0; aIndex <= aLength; aIndex++) {
        matrix[0][aIndex] = aIndex
    }
 
    let colMin
    let minJ
    let maxJ
 
    // Loop over the rest of the columns.
    for (let bIndex = 1; bIndex <= bLength; bIndex++) {
        colMin = MAX_INT
        minJ = 1
        if (bIndex > maxInt) minJ = bIndex - maxInt
        maxJ = bLength + 1
        if (maxJ > maxInt + bIndex) maxJ = maxInt + bIndex
        // Loop over the rest of the rows.
        for (let aIndex = 1; aIndex <= aLength; aIndex++) {
            // If j is out of bounds, just put a large value in the slot.
            if (aIndex < minJ || aIndex > maxJ)
                matrix[bIndex][aIndex] = maxInt + 1
            // Otherwise do the normal Levenshtein thing.
            else {
                // If the characters are the same, there's no change in edit distance.
                if (stringB.charAt(bIndex - 1) === stringA.charAt(aIndex - 1))
                    matrix[bIndex][aIndex] = matrix[bIndex - 1][aIndex - 1]
                // Otherwise, see if we're substituting, inserting or deleting.
                else
                    matrix[bIndex][aIndex] = Math.min(
                        matrix[bIndex - 1][aIndex - 1] + 1, // Substitute
                        Math.min(
                            matrix[bIndex][aIndex - 1] + 1, // Insert
                            matrix[bIndex - 1][aIndex] + 1
                        )
                    ) // Delete
            }
 
            // Either way, update colMin.
            if (matrix[bIndex][aIndex] < colMin) colMin = matrix[bIndex][aIndex]
        }
 
        // If this column's minimum is greater than the allowed maximum, there's no point
        // in going on with life.
        if (colMin > maxInt) return maxInt + 1
    }
    // If we made it this far without running into the max, then return the final matrix value.
    return matrix[bLength][aLength]
}
 
const delimitedToMatrix = (
    delimited: string,
    rowDelimiter = "\n",
    columnDelimiter = "\t"
) => delimited.split(rowDelimiter).map((line) => line.split(columnDelimiter))
 
export const tsvToMatrix = delimitedToMatrix