# there are no results.
        if isinstance(subtrie, str):
            return [subtrie] if query[i:] == subtrie[i:len(subtrie)] else []
        if query[i] not in subtrie.children:
            return []
        subtrie = subtrie.children[query[i]]
    
    # subtrie now contains all completions of the query string.
    results = []
    # Maintain a list of nodes we have not yet examined. This functions as a
    # stack; we could use recursion instead.
    unprocessedSubtries = [subtrie]
    while unprocessedSubtries:
        subtrie = unprocessedSubtries.pop()
        if isinstance(subtrie, str):
            # If this is a shortcut node, just add the string to the results.
            results.append(subtrie)
        else:
            # Otherwise, push all child nodes onto the stack to be examined.
            unprocessedSubtries.extend(subtrie.children.values())
    
    return results

# This function exists to sort the results of autocomplete (making it easier to automatically verify the results).
# The problem statement does not specify any particular ordering for the returned strings, but we would like to be
# able to specify a list as the expected result and then compare it directly to the actual result.
def autocompleteTestHarness(query, dictionary):
    return sorted(autocomplete(query, dictionary))

testFunction(autocompleteTestHarness, testCases)
# Implementation without division; O(n) time, O(n) space
# (note that any solution must use at least O(n) space as that much is needed to store the output)
def calculateProducts(factors):
    # Each element of beforeProducts contains the product of all factors BEFORE that index.
    # For convenience, the special case of the first index (which has no factors before it) is assigned to 1.
    beforeProducts = [1 for _ in factors]
    # Similarly, afterProducts contains the product of all factors AFTER each index,
    # and the last index is assigned to 1.
    afterProducts = [1 for _ in factors]

    # Fill in the lists of beforeProducts and afterProducts.
    for i in range(0, len(factors) - 1):
        beforeProducts[i+1] = beforeProducts[i] * factors[i]

    for i in range(len(factors) - 1, 0, -1):
        afterProducts[i-1] = afterProducts[i] * factors[i]

    # Then, the output for each index is simply the product of all factors before that index
    # multiplied by the product of all factors after that index, both of which we already know!
    return [beforeProducts[i] * afterProducts[i] for i in range(len(factors))]

testFunction(calculateProducts, testCases)

# Note: The implementation with division would calculate the product of all the factors,
# and then for each index, divide by the factor at that index to get the output.
# The implementation is trivial, but breaks if any factor is 0 (due to division by 0).
# Special cases could be constructed to get around this issue, but the end result would
# likely be more complex than the no-division solution without providing any advantage
# in space or time complexity.
def cons(a, b):
    def pair(f):
        return f(a, b)

    return pair


# car solution
def car(pair):
    return pair(lambda a, b: a)


# cdr solution
def cdr(pair):
    return pair(lambda a, b: b)


# Testing code


def carTestHarness(a, b):
    return car(cons(a, b))


def cdrTestHarness(a, b):
    return cdr(cons(a, b))


testFunction(carTestHarness, carTestCases)
testFunction(cdrTestHarness, cdrTestCases)
Exemplo n.º 4
0
        # that index.
        while arr[i] > 0 and arr[i] <= len(arr) and arr[i] != i + 1:
            if arr[arr[i] - 1] == arr[i]:
                # Duplicate number, and one instance is already in the right place.
                # Break (otherwise infinite loop...)
                break
            arr[arr[i] - 1], arr[i] = arr[i], arr[arr[i] - 1]

    # Find the first un-populated index. In the case that the array is perfectly filled
    # from 1 to the size of the array, i will simply run all the way to len(arr) and
    # len(arr) + 1 will be returned at the end.
    i = 0
    while i < len(arr):
        if arr[i] != i + 1:
            break
        i = i + 1

    return i + 1


def fmiTestHarness(arr):
    # The problem statement requires that the solution run in constant space,
    # but may modify the input array in-place. Therefore, make a working copy
    # *before* calling the actual function so that the original input array
    # from the test case isn't modified and the output displays correctly.
    workingArr = arr[:]
    return firstMissingInteger(workingArr)


testFunction(fmiTestHarness, testCases)
Exemplo n.º 5
0
    ((17, [10, 15, 3, 7]), True),
    ((0, []), False),
    ((17, [5, 20, -3, 16]), True),
    ((17, [5, 20, -4, 16]), False),
]


# Naive implementation: O(n^2) time, O(1) space (n = length of numbers list)
def doTheyAddUp(k, numbers):
    for i in range(0, len(numbers) - 1):
        for j in range(i, len(numbers)):
            if (numbers[i] + numbers[j] == k):
                return True

    return False


# One-pass implementation: O(n) time (assuming constant-time dictionary access), O(n) space (assuming reasonable dictionary implementation)
def doTheyAddUp_OnePass(k, numbers):
    seen = {}
    for num in numbers:
        seen[num] = True
        if (k - num in seen):
            return True

    return False


testFunction(doTheyAddUp, testCases)
testFunction(doTheyAddUp_OnePass, testCases)
Exemplo n.º 6
0
        if not s[i] in currentLetterCounts:
            numUniqueLetters += 1
            currentLetterCounts[s[i]] = 1
        else:
            currentLetterCounts[s[i]] += 1
        # If we've exceeded k, pull up the the other end behind us until we
        # drop a unique character again.
        # Note that while this is a nested loop, runtime is still O(n) because
        # the inner loop examines each index of the string at most once
        # globally.
        while numUniqueLetters > k:
            if currentLetterCounts[s[currentStart]] == 1:
                # Deleting letter counts as they reach zero allows us to
                # operate in O(k) space, since the dict will have at most
                # k+1 entries.
                del currentLetterCounts[s[currentStart]]
                numUniqueLetters -= 1
            else:
                currentLetterCounts[s[currentStart]] -= 1
            currentStart += 1
        # Now we know the length of the longest substring that contains at most
        # k distinct characters and ends at index i.
        currentLength = i - currentStart + 1
        if currentLength > longestLength:
            longestLength = currentLength

    return longestLength


testFunction(longestSubstring, testCases)
Exemplo n.º 7
0
#      case that we take the new character as part of a 2-character encoding).
#   3) There are no ways to interpret the new character (i.e. it is 0).
#      Although the problem statement excludes the possibility that 0 appears
#      at the left of the full message, which would render it invalid, 0 may
#      still appear anywhere else in the message. Because we already need to
#      track the "previous" value of N for case #2, it is convenient to say
#      that N for the new message is 0. Then if we see a 1 or a 2 on the next
#      iteration, the formula for case 2 still holds.


def numDecodes(message):
    # N may start as 1, because we are guaranteed that the message is valid so
    # there must be at least one way to decode it.
    N = 1
    prevN = 0
    prevChar = ''

    for c in reversed(message):
        if c >= '3' and c <= '9' or c == '2' and prevChar >= '7' and prevChar <= '9':
            # N stays the same, and update prevN
            prevN = N
        elif c == '1' or c == '2':
            N, prevN = N + prevN, N
        elif c == '0':
            N, prevN = 0, N

    return N


testFunction(numDecodes, testCases)
    # Correct handling of empty list
    (([],), 0),
    # Correct handling of all negative numbers
    (([-5, -1, -1, -5],), 0),
]

# O(n) time; O(1) space
def largestSum(numbers):
    # Note that the smallest sum we should ever return is 0, which we would
    # obtain simply by choosing no numbers (in the case they were all negative).
    largestSumWithoutPrevious = 0
    largestSumWithPrevious = 0
    largestSumWithCurrent = 0

    for number in numbers:
        # For each number we encounter, we can choose to use it or not.
        # If we choose to use it, then the largest sum we can get is the
        # current number plus the largest sum we could obtain without using
        # the previous number (since it is adjacent). If we choose not to use
        # it, then the largest sum we can get is simply the largest sum we can
        # get from all the numbers not including the current one.
        largestSumWithCurrent = max(largestSumWithoutPrevious + number, largestSumWithPrevious)

        # Now we simply shift the largest sum values backwards to set up for
        # the next iteration.
        largestSumWithoutPrevious, largestSumWithPrevious = largestSumWithPrevious, largestSumWithCurrent

    return largestSumWithCurrent

testFunction(largestSum, testCases)
    # Test full unival tree
    ((Node(0, Node(0), Node(0, Node(0, Node(0), Node(0)), Node(0))), ), 7),
]


def numUnivalSubtrees_RecursiveHelper(root):
    # Returned tuple fields:
    # - Number of unival subtrees
    # - Whether this node is the root of a unival subtree
    if root == None:
        return (0, True)

    (leftSubtrees, leftIsUnival) = numUnivalSubtrees_RecursiveHelper(root.left)
    (rightSubtrees,
     rightIsUnival) = numUnivalSubtrees_RecursiveHelper(root.right)

    if leftIsUnival and rightIsUnival and (
            root.left == None or root.left.value == root.value) and (
                root.right == None or root.right.value == root.value):
        return (leftSubtrees + rightSubtrees + 1, True)

    return (leftSubtrees + rightSubtrees, False)


def numUnivalSubtrees_Recursive(root):
    (numSubtrees, _) = numUnivalSubtrees_RecursiveHelper(root)
    return numSubtrees


testFunction(numUnivalSubtrees_Recursive, testCases)