# Programming Fundamentals

## 1 Random Number Guesser

Write a program that asks the user to enter two integers representing the start and the end of a range. The program should then generate a random number within this range (inclusively) and ask the user to guess numbers until they guess the randomly generated number. Once the user guesses the random number, the program should tell them how many attempts it took them to guess it.

Your program needs to ensure that the range of numbers given is valid. For example, if the user enters a number for the end of the range that is less than the start of the range your program needs ask them to enter a valid number. Your program must also handle any other errors that might occur, like the user entering a string instead of an integer.

Note: You may assume the start of the range will never be negative (i.e. you don’t need to handle negative values).

Your program must use the same prompts and output as shown in the sample output below.

### Sample Output 1

``````Enter the start of the range: 1
Enter the end of the range: 5
Guess a number: 2
Guess a number: 3
You guessed the number in 2 attempts
``````

### Sample Output 2

``````Enter the start of the range: 5
Enter the end of the range: 4
Enter the end of the range: 7
Guess a number: 6
You guessed the number in 1 attempt
``````

### Sample Output 3

``````Enter the start of the range: hello
Enter the start of the range: 8
Enter the end of the range: 4
Enter the end of the range: 20
Guess a number: 6
Guess a number: 7
Guess a number: hello
Guess a number: 9
You guessed the number in 3 attempts
``````

SOLUTION

``````import random

start = input('Enter the start of the range: ')
while not start.isdigit():
start = input('Enter the start of the range: ')

end = input('Enter the end of the range: ')
while not end.isdigit() or int(end) < int(start):
end = input('Enter the end of the range: ')

random_number = random.randint(int(start), int(end))

guess = None
attempts = 0

while guess != random_number:
guessed_number = input("Guess a number: ")
if not guessed_number.isdigit():
continue

attempts += 1
guess = int(guessed_number)

suffix = ""
if attempts > 1:
suffix = "s"

print(f'You guessed the number in {attempts} attempt{suffix}')

``````

## 2 Caesar Cipher

Write a function that accepts a string and returns the caesar cipher encoding of that string according to a secondary input parameter named offset.

The caesar cipher encoding of a string involves shifting each character in the string a set number of positions previous in the alphabet. For example, if you were performing a caesar cipher of the string “tim” with offset = 2 you would get “rgk”. “t” is shifted two positions to “r”, “i” is shifted two positions to “g” and “m” is shifted two positions to “k”.

In the situation where the shift of a character results in it being a position before “a” the positions wrap and the next character should be “z”. For example, the caesar cipher of “ab” with offset = 2 would be “yz”.

offset will always be a positive integer that is no greater than 26 and the input string will only contain lowercase letters.

### Sample Input #1

`string = "hello" offset = 3`

### Sample Output #1

`"ebiil"`

### Sample Input #2

`string = "apple" offset = 5`

### Sample Output #2

`"vkkgz"`

Solution

``````def caesar_cipher(string, offset):
alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

encoded_string = ''

for character in string:
position = alphabet.index(character)
offset_position = position - offset
# No need to handle negative positions because of negative indexing
encoded_character = alphabet[offset_position]
encoded_string += encoded_character

return encoded_string
``````

3 Sort Employees

Write a function that accepts a list of lists that contain the name, age and salary of specific employees and also accepts a string representing the key to sort employees by. Your function should return a new list that contains the lists representing each employee sorted in ascending order by the given key.

The string parameter named sort_by will always be equal to one of the following values: “name”, “age” or “salary”.

See the sample input and output below for a detailed example.

Sample Input #1

``````employees =
[
["John", 33, 65000],
["Sarah", 24, 75000],
["Josie", 29, 100000],
["Jason", 26, 55000],
["Connor", 25, 110000]
]
sort_by = "age"
``````

Sample Output #1

``````[
["Sarah", 24, 75000],
["Connor", 25, 110000],
["Jason", 26, 55000],
["Josie", 29, 100000],
["John", 33, 65000]
]
``````

Sample Input #2

``````employees =
[
["John", 33, 65000],
["Sarah", 24, 75000],
["Josie", 29, 100000],
["Jason", 26, 55000],
["Connor", 25, 110000]
]
sort_by = "salary"
``````

Sample Output #2

``````[
["Jason", 26, 55000],
["John", 33, 65000],
["Sarah", 24, 75000],
["Josie", 29, 100000],
["Connor", 25, 110000]
]
``````

Sample Input #3

``````employees =
[
["John", 33, 65000],
["Sarah", 24, 75000],
["Josie", 29, 100000],
["Jason", 26, 55000],
["Connor", 25, 110000]
]
sort_by = "name"
``````

Sample Output #3

``````[
["Connor", 25, 110000],
["Jason", 26, 55000],
["John", 33, 65000],
["Josie", 29, 100000],
["Sarah", 24, 75000]
]
``````

Solution 1

``````def sort_employees(employees, sort_by):
sort_indices = ["name", "age", "salary"]
sort_index = sort_indices.index(sort_by)

sorted_employees = []
# Copy the employees list so we don't modify it
employees_copy = employees[:]

while len(employees_copy) > 0:
smallest_employee_index = 0

for i, employee in enumerate(employees_copy):
current_smallest_value = employees_copy[smallest_employee_index][sort_index]
if employee[sort_index] < current_smallest_value:
smallest_employee_index = i

# After looking through all remaining employees we will have found the employee
# with the smallest sort_by value so we add it to the sorted list
next_sorted_employee = employees_copy[smallest_employee_index]
sorted_employees.append(next_sorted_employee)
employees_copy.pop(smallest_employee_index)

return sorted_employees
``````

Solution 2

``````def sort_employees(employees, sort_by):
sort_indices = ["name", "age", "salary"]
sort_index = sort_indices.index(sort_by)

sorted_employees = sorted(employees, key=lambda x: x[sort_index])

return sorted_employees
``````

## 4 Longest Unique Words

Write a function that accepts a list of strings that represent words and a positive integer n, representing the number of words to return. Your function should return a new list containing the n longest unique words from the input list. Words are unique if they only appear one time in the input list.

There will always be exactly n words to return and you may return the words in any order.

Note: all strings in the input list will not contain any special characters or spaces.

See the sample input and output below for a detailed example.

### Sample Input #1

`words = [ 'Longer', 'Whatever', 'Longer', 'Ball', 'Rock', 'Rocky', 'Rocky' ] n = 3`

### Sample Output #1

``````
[ 'Whatever', 'Ball', 'Rock' ]
``````

Solution 1

``````ef get_n_longest_unique_words(words, n):
unique_words = get_unique_words(words)
longest_words = []

while len(longest_words) < n:
current_longest = ""
for word in unique_words:
if len(word) > len(current_longest):
current_longest = word

unique_words.remove(current_longest)
longest_words.append(current_longest)

return longest_words

def get_unique_words(words):
unique_words = []
for word in words:
if words.count(word) == 1:
unique_words.append(word)

return unique_words

``````

Solution 2

``````def get_n_longest_unique_words(words, n):
unique_words = get_unique_words(words)
sorted_words = sorted(unique_words, key=len, reverse=True)
return sorted_words[:n]

def get_unique_words(words):
unique_words = []
for word in words:
if words.count(word) == 1:
unique_words.append(word)

return unique_words
``````

## 5 Pairs Sum To Target

Write a function that accepts two lists (list1 and list2) of integers and a target integer named target. Your function should return all pairs of indices in the form [x, y] where list1[x] + list2[y] == target. In other words, return the pairs of indices where the sum of their values equals target.

list1 and list2 will always have the same number of elements and you may return the pairs in any order.

### Sample Input #1

``````list1 = [1, -2, 4, 5, 9]
list2 = [4, 2, -4, -4, 0]
target = 5
``````

### Sample Output #1

``````[
[0, 0],  # list1[0] = 1, list2[0] = 4, 1 + 4 = 5
[3, 4],  # list1[3] = 5, list2[4] = 0, 5 + 0 = 5
[4, 2],  # list1[4] = 9, list2[2] = -4, 9 + -4 = 5
[4, 3]   # list1[4] = 9, list2[3] = -4, 9 + -4 = 5
]
``````

Solution

``````def pairs_sum_to_target(list1, list2, target):
pairs = []

for i, value1 in enumerate(list1):
for j, value2 in enumerate(list2):
if value1 + value2 == target:
pairs.append([i, j])

return pairs
``````

6 Create Strings From Characters
Write a function that accepts a dictionary called frequencies and two strings named string1 and string2. The frequencies dictionary contains character keys and integer values, the value associated with each character represents its frequency. Your function should return 0, 1 or 2 according to the cases below.

Your function should return 2 if the frequency of characters in the dictionary is sufficient to create both string1 and string2 without reusing any characters.
Your function should return 1 if the frequency of characters in the dictionary is sufficient to create either string1 or string2 without reusing any characters.
Your function should return 0 if the frequency of characters in the dictionary is not sufficient to create either string1 or string2 without reusing any characters.
Sample Input #1

``````frequencies =
{
"h": 2,
"e": 1,
"l": 1,
"r": 4,
"a": 3,
"o": 2,
"d": 1,
"w": 1
}
string1 = "hello"
string2 = "world"
``````

Sample Output #1

``````1  # The string "world" can be created but "hello" cannot be.
``````

Sample Input #2

``````frequencies =
{
"h": 2,
"e": 1,
"l": 2,
"r": 4,
"a": 3,
"o": 2,
"d": 1,
"w": 1
}
string1 = "hello"
string2 = "world"
``````

Sample Output #2

``````1  # The string "world" and "hello" can be created but they cannot both be created without reusing any characters.
# This is because there is not enough "l"'s.
``````