Technical Exercises (2) from a Software Engineering Internship Application Process
Tags: javascript, programming, algorithms, technicalexercises
Published: 2023-02-26 17:30:17

```javascript
/* Contents:
    - the instructions
    - my Exercise 1 solution
    - someone else's Exercise 1 solution
    - my Exercise 2 solution
*/


/* Instructions
    EXERCISE 1
    Enumerate all uppercase/lowercase permutations for any letter specified in $rule_char_set.

    Input:
    word = "medium-one"
    rule_char_set = "dn"

    Output:
    solutions = ["medium-one", "meDium-one", "medium-oNe", "meDium-oNe"]

    If the character in $rule_char_set appears more than once in $word, treat them as independent
    variables. For example:

    Input:
    word = "medium-one"
    rule_char_set = "m"

    Output:
    solutions = ["medium-one", "Medium-one", "mediuM-one", "MediuM-one"]

    EXERCISE 2
    Find the median number of all values from two very large arrays of sorted integers. Assume
    both arrays have the same large length = N.
    I'd like to see a solution of runtime faster than O(N).

    Input:
    array_int1 = 10, 20, 30, 40, 51, 61, 71
    array_int2 = 15, 25, 31, 86, 600, 700, 900

    Output: median = 45.5
*/


// Exercise 1 (my solution; is recursive and creates duplicates; is not as concise as I'd like)

const listPerms = (wordStr, charSetStr, input) => {
    let indexesOfChars = []
    wordStr.split('').forEach((wordLett, i) => {
        charSetStr.split('').forEach(charSetLett => {
            if (wordLett === charSetLett) {
                indexesOfChars.push(i)
            }
        })
    })
    // console.log(indexesOfChars)

    let wordWithMaxCaps = []
    wordStr.split('').forEach((lett, i) => {
        indexesOfChars.forEach(num => {
            if (i === num) {
                lett = lett.toUpperCase()
            }
        })
        wordWithMaxCaps.push(lett)
    })
    // console.log(wordWithMaxCaps)

    // console.log(input)
    if (input.includes(wordWithMaxCaps.join(''))) {
        return []
    } else {
        if (input.length === 0) {
            input.push(wordStr.split(''))
            // console.log(input)
        } else {
            input = input.map(wStr => wStr.split(''))
            // console.log(input)
        }

        let newArr = []

        input.forEach(word => {
            word.forEach((lett, i) => {
                indexesOfChars.forEach(num => {
                    if (i === num && lett !== lett.toUpperCase()) {    
                        newArr.push(word.slice(0, i).concat(lett.toUpperCase()).concat(word.slice(i + 1)).join(''))        
                    }
                })
            })
        })

        // console.log(newArr)
        input = newArr.filter((wStr, i) => newArr.indexOf(wStr) === i) // removing newArr's duplicate strings
        // console.log(input)

        const solution = listPerms(wordStr, charSetStr, input)
        solution.unshift(...input) // this occurs after the recursive function call has returned
        solution.unshift(wordStr) // wordStr could have been added by returning '[wordStr]' instead of '[]' when the base case was met. However, if this were done, then wordStr wouldn't be at the beginning of the 'solution' array, given that one line above this one is 'solution.unshift(...input)' and not 'solution.push(...input)'. If 'push' were used instead, then returning '[wordStr]' at the base case would put wordStr at the beginning of 'solution', but then the order of all the non-wordStr strings would no longer be correct.
        return solution.filter((wStr, i) => solution.indexOf(wStr) === i) // removing solution's duplicate wordStr strings
    }
}

console.log(listPerms('medium-one', 'm', []))
console.log(listPerms('medium-one', 'dn', []))
console.log(listPerms('medium-one', 'md', []))
console.log(listPerms('medium-one', 'mdn', []))
console.log(listPerms('medium-one', 'dnm', []))
console.log(listPerms('medium-one', 'edon', []))
console.log(listPerms('medium-one', 'medon', []))

console.log(listPerms('medium-one-man', 'mdn', []))


// Exercise 1 (someone else's solution; isn't recursive and doesn't create duplicates; is very concise)

const getPerms = (word, charSet) => {
    let result = ['']

    for (let i in word) { // loop over each character in 'word'
        let char = word[i]

        let temp = []

        result.forEach(str => { // loop over each string that is, so far, in 'result'
            if (charSet.indexOf(char) !== -1) { // if 'char' is in 'charSet'...
                // ...add both cases of 'char'
                temp.push(str + char.toUpperCase())
                temp.push(str + char.toLowerCase())
            } else { // if 'char' is not in 'charSet'...
                temp.push(str + char) // ...add the existing case of 'char'
            }
        })

        result = temp
        // console.log(result) // how 'result' progresses with each iteration
    }

    return result
}

console.log(getPerms('medium-one', 'm'))
console.log(getPerms('medium-one', 'dn'))
console.log(getPerms('medium-one', 'md'))
console.log(getPerms('medium-one', 'mdn'))
console.log(getPerms('medium-one', 'dnm'))
console.log(getPerms('medium-one', 'edon'))
console.log(getPerms('medium-one', 'medon'))

console.log(getPerms('medium-one-man', 'mdn'))


// Exercise 2 (my solution; I was clued into this approach by being advised to learn about Binary Search)

function getMedian(arr1, arr2) { // arr1 and arr2 are sorted
    if (arr1.length !== 2) { // since arr1 and arr2 are the same length, either can be used here...and this comment applies to several lines below
        let middleFloored = Math.floor(arr1.length / 2)
        // console.log(middleFloored)

        let m1
        let m2
        if (arr1.length % 2 !== 0) {
            m1 = arr1[middleFloored]
            m2 = arr2[middleFloored]
        } else {
            m1 = (arr1[middleFloored] + arr1[middleFloored - 1]) / 2
            m2 = (arr2[middleFloored] + arr2[middleFloored - 1]) / 2
        }
        // console.log(m1)
        // console.log(m2)

        if (m1 === m2) {
            return m1 // Note: m1 or m2 can be returned here. Why? Because either will be the median for the merged array. How do we know this? Because: The merged array will always be even in length, m1 and m2 will always be the two middle numbers, and the average of two identical numbers is either number.
        } else if (m1 > m2) {
            if (arr1.length % 2 !== 0) {
                return getMedian(arr1.slice(0, middleFloored + 1), arr2.slice(middleFloored))
            } else {
                return getMedian(arr1.slice(0, middleFloored + 1), arr2.slice(middleFloored - 1))
            }
        } else if (m1 < m2) {
            if (arr1.length % 2 !== 0) {
                return getMedian(arr1.slice(middleFloored), arr2.slice(0, middleFloored + 1))
            } else {
                return getMedian(arr1.slice(middleFloored - 1), arr2.slice(0, middleFloored + 1))
            }
        } else {
            return -1
        }
    } else {
        return (Math.max(arr1[0], arr2[0]) + Math.min(arr1[1], arr2[1])) / 2
    }
}

console.log(getMedian([10, 20, 30, 40, 51, 61, 71], [15, 25, 31, 86, 600, 700, 900])) // 45.5

// console.log(getMedian([3, 5, 8, 12, 15, 76, 85, 101, 354], [8, 9, 14, 17, 28, 52, 83, 95, 411])) // 22.5
// console.log(getMedian([3, 5, 8, 12, 15, 76, 85, 101], [8, 9, 14, 17, 28, 52, 83, 95])) // 16
// console.log(getMedian([15, 25, 31, 86, 600, 700, 900], [10, 20, 30, 40, 51, 61, 71])) // 45.5
// console.log(getMedian([3, 5, 8, 12, 15, 76, 85], [8, 9, 14, 17, 28, 52, 83])) // 14.5
// console.log(getMedian([1, 12, 15, 26, 38], [2, 13, 17, 30, 45])) // 16
// console.log(getMedian([-10, -4, 2, 5], [-8, 1, 4, 7])) // 1.5
// console.log(getMedian([], [])) // -1

```