Compare commits

...

20 Commits

Author SHA1 Message Date
0dab59e81e NP-Complete: First working version 2025-12-07 18:00:19 -05:00
d3e7ce3189 Arbitrages: Added README 2025-12-07 17:34:55 -05:00
c6c7e667c3 Arbitrages: First working version 2025-11-10 10:53:00 -05:00
7739e18c7f Graph-Search: Added initial files 2025-10-29 10:52:11 -04:00
38117921ed Railroad-Construction: First working version 2025-10-21 11:02:00 -04:00
97e92b8b14 Railroad Construction: Added initial boilerplate 2025-10-19 20:26:07 -04:00
a730e01e9c Ditched numpy, it's slow. Changed recursive call structure 2025-10-14 18:12:39 -04:00
82ba7de3eb Basic-Graphs: Input grids can now be rectagular, moved to numpy 2025-10-14 09:41:47 -04:00
e1b3928cf4 Basic Graphs: First working solution 2025-10-14 08:20:28 -04:00
1ecef01de4 Basic-Graphs: Added initial files and code 2025-10-13 21:52:44 -04:00
ff22086802 Shuffle: Fixed memoization table filling 2025-10-13 21:52:12 -04:00
6a9630c5ac Graph Search: First working version & Shuffle problem 2025-10-10 14:24:50 -04:00
ac34fbcb83 Graph-Search: Initial scaffolding 2025-10-08 11:08:58 -04:00
555e084620 Greedy Algorithms: Added psuedocode and reflection questions 2025-10-06 21:09:43 -04:00
842ea1d43a Greedy-Algorithms: Knapsack small code cleanup 2025-10-05 22:26:44 -04:00
aaa1fed779 Greedy-Algorithms: Knapsack first working version 2025-10-05 15:31:05 -04:00
31604a4b6d Greed-Algorithms: Added knapsack files 2025-10-03 10:40:05 -04:00
16acd96a27 Backtracking: Dynamic solution first working version 2025-09-24 19:29:44 -04:00
67d08e827d Backtracking: Updated README for Homework 4 2025-09-22 23:38:14 -04:00
040e551203 Palindrome-Partitioning: Pixed line numbers in analysis of dynamic solution 2025-09-21 18:47:07 -04:00
36 changed files with 69861 additions and 12 deletions

79
Arbitrages/README.md Normal file
View File

@@ -0,0 +1,79 @@
# Homework 6: Arbitrages
## Finding an Arbitrage
In currency exchange markets units of one currency, such as US Dollars, can be exchanged for units of a different currency, like Italian Lira. These exchanges are set by the market's exchange rate, which typically fluctuates throughout the day. For example, at the time of this assignment's writing, the exchange rate between the US Dollar (USD) and the Great British Pound (GBP) was 1 USD to 0.77 GBP. This means that $150 is worth $150\cdot0.77=115.5\ \text{GBP}$ and $150\ \text{GPB} = 150 / 0.77 = \$194.81$. The exchange rates are dynamic and constantly change.
This change in currency is based on how trades are progressing through the market, and the changing dynamic nature of the exchange leads to a particular inefficiency in short-time frame based trading. For example, consider the following exchange rates:
- 1 USD = 0.82 Euro
- 1 Euro = 129.7 Japanese Yen
- 1 Japanese Yen = 12 Turkish Lira
- 1 Turkish Lira = 0.0008 USD.
So if we start with 1 USD, and make all of the exchanges above, we get $1\cdot0.82\cdot129.7\cdot12\cdot.0008 = \$1.021$. In other words, by making four exchanges starting from $1.00 we end up with $1.02, a 2% profit. This situation, where a cycle of currency exchanges starting and ending in the same currency leads to a net gain in the starting currency (rather than breaking even) is called arbitrage.
Notice that arbitrage occurs when the product of all of the exchanges in a cycle is greater than 1. Your job is to find and detect arbitrage in a given exchange and determine what sequence of exchanges need to be made.
We are going to reduce this problem to the problem of finding negative weight cycles in a graph. There are two problems to overcome.
1. Our formulation of the problem requires a product of the exchange rates, but shortest path algorithms work with the sum of the weights of a bunch of edges.
2. Negative weight cycles are "smaller" whereas we are attempting to maximize a profit.
The first problem can be overcome using the following trick: the logarithm function is monotonic, which means that log(x) increases when x increases. Furthermore, the logarithm function can be used to convert products into sums because $\log(a\cdot{b}) = \log(a) + \log(b)$. Thus, if we want to maximize $a\cdot{b}$, this is the same as maximizing $\log(a) + \log(b)$. Also, consider that $a\cdot{b} > 1$ if and only if $\log(a) + \log(b) > 0$. Thus a cycle of exchange rates $r1\cdot{r2}\cdot{r3}\cdot...\cdot{rn} > 1$ if and only if $\log(r1) + \log(r2) + ... + \log(rn) > 0$.
But now the second problem is that we want to detect negative weight cycles because Bellman-Ford can do that for us, but above we described a positive weight cycle. Now we can use the following trick--simply negate each log value. $\log(r1) + \log(r2) + ... + \log(rn) > 0$ if and only if $(-\log(r1)) + (-\log(r2)) + ... + (-\log(rn)) < 0$. Now, you should be able to see how to build a graph, with some appropriate weights, and use it to find arbitrage cycles directly!
### Input
The first line of the input will contain a single integer m describing how many exchange rates are in the exchange. The next _m_ lines will be given by _cIn_ _cOut_ _r_ where _cIn_ is the code for the starting currency (like USD or GPB), _cOut_ is the code for the ending currency, and _r_ is the exchange rate (i.e. "USD GBP 0.75" codes for 1 USD = 0.75 GBP).
### Output and Rubrics
This assignment is worth 15 points.
- 9 points (partial) -- code identifies an the presence or absence of an arbitrage correctly on published test cases
- 3 points (partial) -- code identifies an the presence or absence of an arbitrage correctly on hidden test cases
- 2 points (full credit) -- code outputs the actual exchanges correctly and in the format specified on the public test cases
- 1 point (full credit) -- code outputs the actual exchanges correctly and in the format specified on the hidden test cases
For full credit, when an arbitrage is detected, in addition to the output above, your code should output the actual exchanges that need to be made by showing each currency code in the exchange separated by " => " on a second line, and a third line containing the actual change as a multiplicative factor with the format "X factor increase", which should be **formatted to 5 decimal points**.
<table>
<tr>
<td>Sample Input</td>
<td>Partial Credit Output</td>
<td>Full Credit Output</td>
</tr>
<tr>
<td><pre>
4
USD EUR 0.82
EUR JPY 129.7
JPY TRY 12
TRY USD 0.0008</pre></td>
<td><pre>Arbitrage Detected</pre></td>
<td><pre>
Arbitrage Detected
USD => EUR => JPY => TRY => USD
1.02100 factor increase</pre></td>
</tr>
</table>
<table>
<tr>
<td>Sample Input</td>
<td>Sample Output (for both partial and full credit options)</td>
</tr>
<tr>
<td><pre>
2
USD GBP 1.0
GBP USD 1.0</pre></td>
<td><pre>No Arbitrage Detected</pre></td>
</tr>
</table>
### Submission
Submit this assignment as `cs412_arbitrages_a.py` to Gradescope.

View File

@@ -0,0 +1,83 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
import math
from collections import defaultdict
def find_arbitrage(graph: dict[str, dict[str, float]]) -> list[str] | None:
weights: dict[str, float] = {c: math.inf for c in graph}
prev: dict[str, str | None] = {c: None for c in graph}
start: str = list(graph.keys())[0]
weights[start] = 0
for _ in range(len(graph) - 1):
for src in graph:
for dest, rate in graph[src].items():
weight: float = -math.log(rate)
if weights[src] + weight < weights[dest]:
weights[dest] = weights[src] + weight
prev[dest] = src
node: str | None = None
for src in graph:
for dest, rate in graph[src].items():
weight: float = -math.log(rate)
if weights[src] + weight < weights[dest]:
node = dest
prev[dest] = src
break
if node is not None:
break
if node is None:
return
for _ in range(len(graph)):
node = prev[node]
path = [node]
while True:
node = prev[node]
path.append(node)
if node == path[0]:
break
path.reverse()
return path
def main():
n: int = int(input().strip())
graph: dict[str, dict[str, float]] = defaultdict(dict)
for _ in range(n):
vals: list[str] = input().strip().split()
src: str = vals[0]
dest: str = vals[1]
rate: float = float(vals[2])
graph[src][dest] = rate
path = find_arbitrage(graph)
if path is not None:
print("Arbitrage Detected")
print(" => ".join(path))
factor = 1.0
for i in range(len(path) - 1):
factor *= graph[path[i]][path[i + 1]]
print(f"{factor:.5f} factor increase")
else:
print("No Arbitrage Detected")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,5 @@
4
USD EUR 0.82
EUR JPY 129.7
JPY TRY 12
TRY USD 0.0008

View File

@@ -0,0 +1,3 @@
2
USD GBP 1.0
GBP USD 1.0

View File

@@ -1,4 +1,4 @@
# Coding 3: Backtracking
# Homework 3: Coding Backtracking
## Rocket Sections
@@ -114,3 +114,41 @@ as `cs412_hw3_a.py` to Gradescope.
- (10 points) Does the code solve the samples in the write-up?
- (7 point) Does the code solve the private testing samples?
- (3 points) For solving the full credit option.
# Homework 4: Coding Dynamic Programming
Modify your solution to the backtracking version of Coding Homework 3:
Backtracking to utilize dynamic programming in an iterative way rather than
recursive. You should clearly separate the dynamic programming phase of your
algorithm from the solution building phase. As with the previous iterations of
this homework, partial credit may be obtained by solving the optimization
problem but not producing a full solution; however, this time more weight is
given to solving the full credit option.
The input/output samples are the same as for Coding Homework 3: Backtracking.
## Your Task
Develop a dynamic programming solution to the problem. Turn your solution in as
`cs412_rockets_dynamic.py`.
## Coding Requirements
No points will be awarded if any of the following techniques are in your code:
- dictionaries
- copying of lists or other objects
- not allocating the dynamic memory structure in a single (or at more 2 lines)
- Retrieving which parts were used MUST be performed by tracing back through the
dynamic/memoized structure. No points will be awarded for that portion of the
HW if this is performed differently.
Gradescope does not check for these items. Your code will be manually reviewed
after submission.
## Rubric (see notes above for coding requirements)
- (1 point) Does your code pass the pep8 coding guidelines?
- (5 points) Does the code solve the samples in the write-up?
- (4 point) Does the code solve the private testing samples?
- (1 points) instructor points

View File

@@ -0,0 +1,68 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
def construct(sections: list[int], target: int) -> list[tuple[int, int]]:
s: list[int] = sorted(sections, reverse=True)
n: int = len(s)
IMPOSSIBLE: int = target + 1
memo: list[list[int]] = [([IMPOSSIBLE] * (target + 1)) for _ in range(0, n + 1)]
for i in range(n + 1):
memo[i][0] = 0
for i in range(n - 1, -1, -1):
for t in range(1, target + 1):
skip: int = memo[i + 1][t]
t_new: int = t - s[i]
use: int = memo[i][t_new] + 1 if t_new >= 0 else IMPOSSIBLE
memo[i][t] = min(skip, use)
total_sections: list[int] = [0] * n
i, t = 0, target
while i < n or t > 0:
skipped: int = memo[i + 1][t]
t_new2: int = t - s[i]
used: int = memo[i][t_new2] + 1 if t_new2 >= 0 else IMPOSSIBLE
if used <= skipped:
total_sections[i] += 1
t -= s[i]
else:
i += 1
return list(zip(s, total_sections))
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
sections: list[int] = list(map(int, input().split()))
target: int = int(input())
best_sections: list[tuple[int, int]] = construct(sections, target)
total: int = 0
for i, n in sorted(best_sections):
total += n
print(f"{n} of length {i}")
print(f"{total} rocket sections minimum")
if __name__ == "__main__":
main()

104
Basic-Graphs/README.md Normal file
View File

@@ -0,0 +1,104 @@
# Homework 5: Basic Graphs
## Preface
This problem comes directly from a CS 412 student's coding interview with
Amazon. The student was given one hour to solve three different problems and
this was one of the problems. The easiest way to solve this problem quickly with
a minimal amount of code is recursively, which is probably the right way to
knock something like this out under coding interview pressure. This is also a
very slight twist on a very standard coding interview question.
You only need to code up one implementation and turn it in and I don't care if
it's the iterative or the recursive solution; however, I suggest you take the
following approach to this problem:
1. Set aside one hour to approach this problem fresh and attempt to knock out a
complete solution to the problem on your first try in under an hour. You
haven't been practicing this sort of thing (that is, trying to code under a
strict time limit), so its likely that you won't succeed within the hour time
limit. The purpose of this exercise is for you go gauge where you are at.
2. Take your time in coding a solution. I suggest you code both a recursive
solution and an iterative solution. These are the sorts of things you should
be working to get comfortable with coding as second nature. Obviously, the
first time you code it, it will not be second nature yet. You have to do it a
lot before it becomes second nature. Practice, as always, makes perfect here.
As I always tell students: learning to code is much more like learning a
musical instrument than learning a body of facts. You only get better at what
you practice. Class time / instruction only exists to point you towards what
you need to do, but you have to go and do it.
3. Once you've coded solutions to the problem, on a different day, set aside
another hour for yourself and try to code a solution as quickly as possible
without referring to your previous solutions. You've now done the hard work
in step 2 of actually solving, coding, and debugging your first solution.
It's time to reinforce those neural pathways in your mind and make this sort of
problem a part of your repertoire--every professional cellist in the world can
play the prelude to Bach's cello suite in G without preparation (if you don't
know the piece, switch to Spotify, load up one of Yo-Yo Ma's recordings of it,
then come back here), every computer scientist should be able to bang out a
recursive solution to this problem without even thinking about it. But that only
comes with practice. I've coded variations of this probably hundreds of times at
this point, if not thousands, which is why I can live code it in class with you
without having to debug. Y'all are at the point where you have all the knowledge
you need and solidifying it practice is the final piece.
So, with Yo-Yo Ma playing Bach's Prelude to the Cello Suite in G in the
background try to rewrite your solution from scratch as quickly as possible.
## Problem
You have been hired by an elite treasure hunting team to help them find a
treasure that was hidden on a large island in the South Pacific. The team has
obtained LIDAR scans of a large square patch of ocean and has preprocessed the
scans into an n x n array where each array slot is either a 0 or a 1. A 0
denotes ocean while a 1 denotes land. Each array slot corresponds to 1 acre of
scan and adjacent slots in the array (in the vertical or horizontal directions,
but not diagonal) that both contain a 1 are considered part of the same land
mass / island. Your task is to find the size of the largest island in acres.
## Input
The input is given by a line containing a single number _n_ followed by n lines,
each of which has n values of either 0 or 1 separated by spaces representing the
scan of the region.
## Output
The output is a single number which is the size of the largest island in the map
in acres.
<table>
<tr>
<td>Sample Input</td>
<td>Sample Output</td>
</tr>
<tr>
<td><pre>
6
0 1 0 0 0 0
0 1 1 0 0 0
0 0 0 1 1 1
0 0 1 1 1 0
0 0 1 1 0 0
1 0 0 0 0 0</pre></td>
<td><pre>8</pre></td>
</tr>
<tr>
<td><pre>
8
0 1 0 0 0 0 1 1
0 1 1 0 0 1 1 0
0 0 0 1 1 1 1 1
0 0 1 1 1 0 1 1
0 0 1 1 0 1 1 0
1 0 0 0 0 0 1 1
0 0 0 0 0 1 1 0
0 0 0 0 0 0 1 1</pre></td>
<td><pre>24</pre></td>
</tr>
</table>
## Turning it in
Name your program `cs412_islands_a.py` and turn it in on Gradescope.

View File

@@ -0,0 +1,67 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
def explore_island(grid: list[list[int]], start: tuple[int, int]) -> int:
num_rows: int = len(grid)
num_cols: int = len(grid[0])
size: int = 0
def recurse(coord: tuple[int, int]):
x, y = coord
if grid[y][x] == 0:
return
nonlocal size
size += 1
grid[y][x] = 0
if y > 0:
recurse((x, y - 1))
if y < num_rows - 1:
recurse((x, y + 1))
if x > 0:
recurse((x - 1, y))
if x < num_cols - 1:
recurse((x + 1, y))
recurse(start)
return size
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
n: int = int(input())
land_grid: list[list[int]] = [list(map(int, input().split())) for _ in range(0, n)]
num_rows: int = n
num_cols: int = len(land_grid[0])
largest_island: int = -1
for y in range(0, num_rows):
for x in range(0, num_cols):
if land_grid[y][x] == 0:
continue
island_size: int = explore_island(land_grid, (x, y))
if island_size > largest_island:
largest_island = island_size
print(largest_island)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,7 @@
6
0 1 0 0 0 0
0 1 1 0 0 0
0 0 0 1 1 1
0 0 1 1 1 0
0 0 1 1 0 0
1 0 0 0 0 0

View File

@@ -0,0 +1,9 @@
8
0 1 0 0 0 0 1 1
0 1 1 0 0 1 1 0
0 0 0 1 1 1 1 1
0 0 1 1 1 0 1 1
0 0 1 1 0 1 1 0
1 0 0 0 0 0 1 1
0 0 0 0 0 1 1 0
0 0 0 0 0 0 1 1

View File

@@ -0,0 +1,9 @@
8
0 0 1 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 1 1 1 0 0 0
0 1 1 0 1 0 0 0 0 0 0 0 0
0 1 0 0 1 1 0 0 1 0 1 0 0
0 1 0 0 1 1 0 0 1 1 1 0 0
0 0 0 0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 1 1 1 0 0 0
0 0 0 0 0 0 0 1 1 0 0 0 0

86
Graph-Search/README.md Normal file
View File

@@ -0,0 +1,86 @@
# Lab 11: Graph Search - Bus Stops
## Graph Search Basics
The purpose of this lab is to give you practice implementing graph data
structures and basic graph traversal and reachability queries.
You are building an app for a newly created bus system connecting Harrisonburg
to Charlottesville. There are many different bus routes each of which travels
between named stops along its route. Not all of the bus routes cross each other,
so it is possible that there are some pairs of stops where there is no way of
getting on a bus at the first stop and making changes to get to the final stop.
Your task is to implement a graph search function that can determine a sequence
of stops a user can use to get from a starting stop to an ending stop, or to
tell the user that there is no bus route possible between those two stops.
## Input
The bus route segments are described as single stretches of road between two
consecutive stops. All such pairs will be described in one direction, but all
bus routes are bidirectional, since buses travel in both directions along each
route.
The first line of input has an integer n that is the number of road segments
between consecutive stops. The next n lines of input describe the consecutive
stops, each of which is named by a string of alphabetical characters with no
spaces. The line will contain both of the stops of the segment, separated by a
space.
The final line of input is the query, which is two stop names separated by a
space.
## Output
Give a sequence of stop names starting at the starting stop of the query and
ending at the ending stop separated by spaces (if one exists), or output "no
route possible" if none is found.
<table>
<tr>
<td>Sample Input</td>
<td>Sample Output</td>
</tr>
<tr>
<td><pre>
2
Hburg JMU
Charlottesville JMU
Hburg Charlottesville</pre></td>
<td><pre>Hburg JMU Charlottesville</pre></td>
</tr>
<tr>
<td><pre>
4
Hburg JMU
JMU Upark
Upark Hburg
CVO UVA
Hburg UVA</pre></td>
<td><pre>no route possible</pre></td>
</tr>
</table>
Larger example files (the one used on Gradescope) can be downloaded here:
bus_stops_t3_in.txt Download bus_stops_t3_in.txt bus_stops_t3_exp.txt Download
bus_stops_t3_exp.txt Depending how you implement your graph, the order of stops
on the path may not be unique, so, checking your solution requires writing a bit
more code to validate your path (and of course, that is optional).
## Hints:
- Store the stops in a dictionary and use a set for the edge list.
- You will need to implement a method that uses a spanning tree. The lab is
asking you for a path between the locations (not necessarily the shortest
path).
## Submission:
Your code in a file name `cs412_bus_stops_a.py` and submit your code to
gradescope.
## Rubrics:
- (6 points) program solves the examples
- (3 points) solves the hidden examples
- (1 point) instructor review

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -0,0 +1,55 @@
# Class Activity: Exploring MST Characteristics
We will be exploring creating graphs that showcase different aspects of MST
algorithms.
## Task 1:
True/False. A MST can sometimes contain a cycle. Justify your answer with one or
two sentences.
> Answer: False. By definition you can remove an edge from cycle and all
> vertices will still be connected. Therefor if the MST has a cycle in it then
> that would mean it has a redundant edge and is therefore not minimal.
## Task 2:
For the following graph, show the safe data structure after one call to
Borůvka's `AddAllSafeEdges` method. Draw the spanning tree F showing all of the
safe edges that were added to it after this first pass.
![Graph](./assets/graph.png)
## Task 3:
Create a graph with at least 6 nodes that is fully connected (a complete graph)
and has distinct edge weights that Boruvka's algorithm can solve in a single
call to `AddAllSafeEdges`. Submit a picture of the graph and the safe array/list
that is created by the call to `AddAllSafeEdges`.
## Task 4:
Create a graph with at least 6 vertices with distinct edge weights such that the
MST contains the edge with the largest weight. Submit a picture of this graph.
## Task 5:
Create a graph with at least 4 nodes that is fully connected (a complete graph)
and has distinct edge weights that Boruvka's algorithm exhibits its worst case
performance. First, define what the worst case perform is for a single call to
`AddAllSafeEdges`. Next, showcase this performance on your graph clearly showing
how the MST (F) looks after a single call to `AddAllSafeEdges` and showing the
contents of the array/list **safe**.
## Task 6:
Can you create a graph with at least 4 vertices with distinct edge weights such
that the MST does not contain the lightest edge? Explain your answer both
logically (1 to 2 sentences) and using the logic within Boruvka's
`AddAllSafeEdges`.
## Submission
Submit a picture of your answers to this canvas assignment. If you worked with
another person, acknowledge that by placing the names of all people you worked
with at the top of the first page of your submission.

View File

@@ -0,0 +1,66 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
from collections import defaultdict, deque
def whatever_first_search(graph: dict[str, set[str]], start: str, stop: str):
bag: deque[tuple[str | None, str]] = deque()
bag.append((None, start))
visited: set[str] = set()
parent: dict[str, str | None] = {}
while len(bag) != 0:
parent_node, u = bag.pop()
if u in visited:
continue
visited.add(u)
parent[u] = parent_node
if u == stop:
path: list[str] = []
while u is not None:
path.append(u)
u = parent[u]
path.reverse()
return path
for v in graph[u]:
if v not in visited:
bag.append((u, v))
return None
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
n: int = int(input())
graph: dict[str, set[str]] = defaultdict(set)
for _ in range(0, n):
u, v = input().split()
graph[u].add(v)
graph[v].add(u)
target = input().split()
path = whatever_first_search(graph, target[0], target[1])
if path is None:
print("no route possible")
else:
print(" ".join(path))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,4 @@
2
Hburg JMU
Charlottesville JMU
Hburg Charlottesville

View File

@@ -0,0 +1,6 @@
4
Hburg JMU
JMU Upark
Upark Hburg
CVO UVA
Hburg UVA

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
# Greedy Algorithms
The _knapsack_ problem is a well known, that when solved with a backtracking
approach, has **exponential** complexity. In this problem, you are given n
items, each with item having a dollar value and a weight. The knapsack can carry
a maximum weight _W_. The algorithm selects the subset of items with **maximum**
value where the sum of the weights of the items does not exceed W.
## Task 1: Develop pseudocode for 0-1 Knapsack
Develop pseudocode that solves this version of the knapsack problem (commonly
known as the 0-1 knapsack problem, since you either take an item or do not take
an item). This should be a backtracking approach with recursion.
**Submit** this as a text file (`cs412_knapsack_01pseudo.txt`). It is OK if this
code does not compile, but it should be fairly close.
## Task 2: Code fractional knapsack problem
The fractional knapsack problem allows items to be broken into smaller pieces so
that the total value of the items in the knapsack can be maximized as discussed
in class. Your method should run in O(n lg n) time. **Submit** this as a text
file (`cs412_knapsack_fractional.py`).
### Input
Your input will begin with a single line containing a nonnegative integer weight
w. This is followed by a single line contains a nonnegative integer n followed
by exactly n lines each of which contains a triple (String, real, real). The
first String is the item's name, the second is the dollar value of the item and
the third is the item's weight.
### Output
You should output exactly 2 lines. The first line is the list of items in the
knapsack with the value and amount (weight) of each item formatted as shown. The
order of the items should follow the ratio of the most expensive per unit
weight. The second line is the total value of the items in the knapsack.
<table>
<tr>
<td>Sample Input</td>
<td>Sample Output</td>
</tr>
<tr>
<td><pre>
11
3
ring 100 5
gold 50 10
silver 50 5</pre>
</td>
<td>
<pre>
ring(100.00, 5.00) silver(50.00, 5.00) gold(5.00, 1.00)
155.0</pre>
</td>
</tr>
</table>
**Hints:**
- Python's sort function accepts a lambda function to select which item to use
in sorting. So, if you read in the input as a list of lists, it is possible to
run the sort on this with a single line of code.
## Task 3: Reflect on these problems
Reflect on the problems in task 1 and task 2.
- Which problem is more efficient to solve, the 0/1 knapsack or fractional
knapsack? Use big-OH complexity to justify your answer.
- How would the greedy item selection strategy work for the 0/1 knapsack
problem? Reflect on 1) whether this approach would yield the optimal answer
and 2) How would its the approaches efficiency/speed be impacted (when
compared to the backtracking 0/1 approach)?
- For the two questions above, give an example input to support your
description? Your input should be specified in the same format as the input to
Task 2.
Attach the answers to these question at the end of `cs412_knapsack_01pseudo.txt`
file.
## Rubric:
- 3 points -- Pseudo code for 0-1 knapsack problem
- 5 points -- code runs and passes Gradescope tests
- 3 points -- answers to reflection questions in Task 3
- 1 point -- instructor review
## Submit
Submit both files to Gradescope.
- `cs412_knapsack_01pseudo.txt` (submit this to Gradescope too and it should
contain both Task 1 and Task 3 work)
- `cs412_knapsack_fractional.py`

View File

@@ -0,0 +1,46 @@
# Task 1:
```
def knapsack(capacity, weights, values, index):
if index == 0 or capacity == 0:
return 0
if weights[index - 1] > capacity:
return knapsack(capacity, weights, values, n - 1)
skip_item = knapsack(capacity, weights, values, n - 1)
take_item = values[n - 1] + knapsack(capacity, weights, values, n - 1)
return max(skip_item, take_item)
```
# Task 3:
- Fractional which has a worst case runtime of O(nlogn) compared to the
backtracking 0/1 knapsack solution which is exponential or O(2^n)
- The greedy strategy for the 0/1 knapsack problems would still select the items
in order of best value-to-weight ratio, but instead of "cutting" once the
availabe capacity was less than the weight of the next item, it would just
skip that item.
1. This approach would not always reach the optimal result.
2. The speed of the algorithm would be greatly improved from and exponential
runtime to a linear-logarithmic runtime.
- The following example input would produce a suboptimal solution with the
greedy approach to the 0/1 knapsack problem. This is because It would take the
first item which has the greates value-to-weight ratio (3) leaving it with a
remaining capacity of 4 and unable to choose any of the remaining, leaving us
with a final value of 18. The optimal solution in this case is to take the
latter two for a total value of 20 and 0 remaining capacity.
```
10
3
A 18 6
B 14 5
C 6 5
```

View File

@@ -0,0 +1,78 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
from dataclasses import dataclass
from typing import Self, override
# I don't like tuples, dataclasses ftw
@dataclass(frozen=True)
class KnapsackItem:
name: str
value: float
weight: float
@property
def value_ratio(self) -> float:
return self.value / self.weight
@override
def __str__(self) -> str:
return f"{self.name}({self.value:.2f}, {self.weight:.2f})"
@classmethod
def parse(cls, string: str) -> Self:
values: list[str] = string.strip().split()
name, value, weight = values[0], float(values[1]), float(values[2])
return cls(name, value, weight)
def knapsack(
capacity: int, items: list[KnapsackItem]
) -> tuple[list[KnapsackItem], float]:
current_capacity: float = float(capacity)
sorted_items: list[KnapsackItem] = sorted(items, key=lambda i: -i.value_ratio)
total_value: float = 0.0
fractional_items: list[KnapsackItem] = []
for item in sorted_items:
if abs(current_capacity) < 1e-8:
break
name, value, weight = item.name, item.value, item.weight
if current_capacity < weight:
value = item.value_ratio * current_capacity
weight = current_capacity
fractional_items.append(KnapsackItem(name, value, weight))
total_value += value
current_capacity -= weight
return fractional_items, total_value
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
capacity: int = int(input())
n: int = int(input())
items: list[KnapsackItem] = [KnapsackItem.parse(input()) for _ in range(0, n)]
fractional_items, total_value = knapsack(capacity, items)
print(" ".join(map(str, fractional_items)))
print(total_value)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,5 @@
11
3
ring 100 5
gold 50 10
silver 50 5

65
NP-Complete/README.md Normal file
View File

@@ -0,0 +1,65 @@
# Lab 17: NP-Completeness
## NP-Complete Problems
### Part A: Unsatisfiable 3-SAT (3 points)
Answer the following questions:
- Given a 3SAT problem with 3 binary variables (x1, x2, and x3), what is the min number of clauses that you would need to create an unsatisfiable sentence?
- Create a 3SAT sentence with 3 binary variable that is not satisfiable using the minimum number of clauses identified in part 1.
Turn these in directly here in Canvas.
### Part B -- Python's Itertools (3 points)
Python provides a powerful package called itertools (see this webpage for details: [https://docs.python.org/3/library/itertools.html](https://docs.python.org/3/library/itertools.html)). This package provides an easy and memory efficient method of producing iterables that represent combination or permutations of items.
- Construct a python program that uses the itertools function to print out all possible assignments of a 3 boolean variables. You will need to use the product function within itertools. If you need more help with the product function, look up itertools' documentation on the web.
- Augment your python program so that it contains a function that accepts 3 variables (x1, x2, and x3). This function must return the truth value of the 3-SAT sentence you created in Part A:2 (should just be a return statement evaluating the expression). Call this function with all the possible settings and show that your sentence is indeed not satisfiable. In other words, if your function returns true, print the settings that produced a true answer, otherwise print a message verifying that it always returned false.
- Create another function that is a copy of the one created in #2 but is missing one of the 3-SAT clauses. Using a similar for loop to #2, call this function with all possible settings of the boolean variables and print out the case where it finds a true assignment (a satisfying assignment).
Attach this program to this assignment, call it: `cs412_np_3satcheck.py`
### Part C -- Practice Reduction (3 points)
Given the following 3-SAT sentence, construct the graph that would serve as input to the independent set problem.
$(a\lor{b}\lor{c})\land(\lnot{a}\lor{b}\lor{c})\land(a\lor\lnot{b}\lor{c})\land(\lnot{a}\lor\lnot{b}\lor\lnot{c})$
- Draw this graph on a piece of paper and take a picture of it (and attach it to this assignment).
- Write a brief (one sentence) description of the decision problem that needs to be solved by the independent set problem to show that this 3-SAT sentence has a valid assignment.
- Identify the largest independent set in the graph created in step #1. If you select the variable "a" that is in the first clause, write that down as A1 (where the one identifies the clause that the variable came from). You can write this on your piece of paper with the graph (just make sure it is included in the picture) OR you can write it in the response section in Canvas.
### Part D -- Prove that Independent Set is in NP (3 points)
Write a program that accepts a description of an undirected graph G and a list of vertices and verifies (in polynomial time) that the list of vertices is indeed an independent set).
Here is some example input:
```
4
0 1
1 0 3
2
3 1
0 3 2
```
The first line tells you how many vertices are in the graph G. The lines that follow contain the edge list for edge vertex. Finally, a proposed independent set is listed. Your program should output TRUE if the set listed in an independent set or FALSE if it is not an independent set (for example, 0 1 should not be an independent set).
Attached this program to this assignment and call it `cs412_np_independent_set.py`
### Part E -- Show that Hamiltonian Paths are NP-Complete (3 points)
In this section, you need to construct the required components to show that finding a Hamiltonian path is NP-Complete (there are 3 components). Illustrate the reduction sequence as we did on the slides (as a picture) and using the <=p syntax and the boxes. **Note: You do NOT need to show the steps required to change the input (the reduction), just the sketch of the order and what would need to be accomplished.** For the other steps, write a sentence or two and if necessary, argue that these steps can be accomplished in polynomial time.
Submit these items in a section labels Part E in your writeup.
### What and Where to Submit:
Submit the following 3 files here in Canvas:
- `cs412_np_3satcheck.py`
- `cs412_np_independent_set.py`
- `cs412_nplab.pdf`

View File

@@ -0,0 +1,51 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
from itertools import product
from typing import Callable
type Three_SAT = Callable[[bool, bool, bool], bool]
def three_sat_missing(x1: bool, x2: bool, x3: bool) -> bool:
combos = product([x1, not x1], [x2, not x2], [x3, not x3])
# Remove the last clause
missing_combo = list(combos)[:-1]
return all(a or b or c for a, b, c in missing_combo)
def three_sat(x1: bool, x2: bool, x3: bool) -> bool:
return all(
a or b or c for a, b, c in product([x1, not x1], [x2, not x2], [x3, not x3])
)
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
settings = product([False, True], repeat=3)
sentences_satisfied: dict[Three_SAT, bool] = {}
sentences_satisfied[three_sat] = False
sentences_satisfied[three_sat_missing] = False
for setting in settings:
for sentence in sentences_satisfied:
if sentence(*setting):
print(f"{setting} satisfied {sentence.__name__}")
sentences_satisfied[sentence] = True
for sentence, satisfied in sentences_satisfied.items():
if not satisfied:
print(f"{sentence.__name__} is not satisfiable")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,38 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
def independent_set(proposed_set: set[int], graph: dict[int, set[int]]) -> bool:
for vertex in proposed_set:
for adjecent in graph[vertex]:
if adjecent in proposed_set:
return False
return True
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
n = int(input())
graph: dict[int, set[int]] = {}
for _ in range(n):
vertecies = list(map(int, input().split()))
graph[vertecies[0]] = set(vertecies[1:])
proposed_set = set(map(int, input().split()))
print("TRUE" if independent_set(proposed_set, graph) else "FALSE")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,6 @@
4
0 1
1 0 3
2
3 1
0 3 2

View File

@@ -9,9 +9,9 @@ Honor Code and Acknowledgments:
"""
# Analysis:
# The loop on line 23 iterates n times where n is the length of the input string.
# The loop on line 28 iterates n - i times, which despite being practically fewer
# steps on average than the line 23 loop, still scales linearly with the size of
# The loop on line 28 iterates n times where n is the length of the input string.
# The loop on line 32 iterates n - i times, which despite being practically fewer
# steps on average than the line 28 loop, still scales linearly with the size of
# the input string. Because both loops are O(n) and the second is nested in the
# first, the overall runtime complexity is O(n^2).

View File

@@ -0,0 +1,90 @@
# Lab 12: Railroad Construction
## Specification
There are _n_ cities that want to create a shared train network so that each
pair of cities is connected by rail to each of the other cities. The cost of
laying track is directly proportional to the distance between two cities. The
cost to lay one mile of railroad track is $1M. Your task is to determine the
lowest cost possible for laying the track so that all the cities are connected.
## Input
A single line containing the number n of cities followed by n city coordinate
lines. Each of the coordinate lines is a pair of floating point numbers x y
giving the coordinates of the cities on a square grid (for this problem you may
assume the earth is flat and that the units of the grid structure are given in
miles).
## Output
The minimum amount of money (in millions) that must be spent to create the
railroad network. You can round this to a single decimal place.
<table>
<tr>
<td>Sample Input</td>
<td>Sample Output</td>
</tr>
<tr>
<td><pre>
3
0 1.5
1 0
0 0</pre></td>
<td><pre>$2.5M</pre></td>
</tr>
<tr>
<td><pre>
8
0 0
1 1
0 1
3 3
4 5
2 2
1 0
3 2</pre></td>
<td><pre>$8.7M</pre></td>
</tr>
</table>
## Hints
1. You may use our [connected_components.py](./connected_components.py) Download
connected_components.py in your code if you want., which will produce labels
for each of the vertices that correspond to their component. You will need to
use the adjacency list structure as suggested below if you wish to use this
code in its unmodified state.
2. You may have already envisioned a way to represent this problem as a graph.
Draw the graph in the small example above on paper. How many edges does it
have? If you are unsure of this, check with me before you start coding the
solution.
## Adjacency List
Augment your dictionary/set based adjacency structure to use a dictionary for
the edges (instead of a set as before). This will allow you to store the edge
weights as the values of this second dictionary.
## Method/Programming requirement
Your program must implement either Borůvka's algorithm (I suggest you use
Borůvka's) or Prims (not Kruskal). You must specify which algorithm you are
implementing in the comments. Any other method to compute the answer will result
in zero credit.
## Submission
Submit the code in a file named [cs412_railroad_a.py](./cs412_railroad_a.py) to
Gradescope. If you use the supplied copy of
[connected_components.py](./connected_components.py), you only need to import it
into your code (it will be available on Gradescope, so, no need to submit). If
you change connected_components.py, then please rename it and submit it as part
of your code (or you can copy/paste the code into your lab and just submit a
single file, it is up to you). You can import the code using the following
python:
```
from connected_components import count_and_label
```

View File

@@ -0,0 +1,71 @@
# Implementation of the count-and-label
# connected component counting algorithm.
#
# Author: John Bowers
# Version: Mar 17, 2021
# March 2022 -- molloykp
# Changed code to label components starting a 0 (instead of 1)
# October 2023 -- molloykp
# Change code to accept new adj list/hash map structure
# Version of the iterative dfs that takes as input
# a list of labels, one per vertex, and a starting
# vertex v and uses iterative depth-first-search
# to label each vertex with a given currentLabel
#
# Parameters:
# * graph is an adjaceny list where vertices name/keys are stored
# in a dictionary. Each vertex has its own dictionary
# where the key is the edge endpoint and the value is the
# weight of the edge.
# * v is the vertex to DFS from
# * labels is an array of length V which is -1 if the vertex is unvisited
# * currentLabel is the label to set on every visited vertex
#
# labels is an out-parameter and will be modified destructively during the
# run of this operation.
#
def dfs_label(graph, v, labels, currentLabel):
bag = [v]
while bag: # while bag is not empty
u = bag.pop()
if labels[u] == -1:
labels[u] = currentLabel
for w in graph[u]:
bag.append(w)
# Counts the number of connected components in the graph
# and labels each vertex with its connected component index
#
# Parameters:
# * graph given as an adjaceny list/set structure with vertices 0..(n-1)
#
# Returns:
# count, labels
# where count is the number of connected components in the graph
# and labels is the labeling of each vertex's connected component
# starting from index 0
def count_and_label(graph):
labels = [-1 for _ in range(len(graph))] # Initially all labels are -1
count = -1
for v in range(len(graph)): # for each vertex
if labels[v] == -1: # if v is not visited
count += 1
dfs_label(graph, v, labels, count)
return count+1, labels
if __name__ == "__main__":
graph = {
0: {1:2}, # 0's neighbors
1: {0:2, 2:4, 3:1}, # 1's neighbors
2: {1:4, 3:5}, # 2's neighbors
3: {1:1, 2:5}, # 3's neighbors
4: {5:8}, # 4's neighbors
5: {4:8} # 5's neighbors
}
count, labels = count_and_label(graph)
print(f"Number of connected components: {count}")
print(f"Vertex labels: {labels}")

View File

@@ -0,0 +1,113 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
from collections import defaultdict
from math import sqrt
type Coordinate = tuple[float, float]
type CoordMap = dict[int, Coordinate]
type WeightedGraph = dict[int, dict[int, float]]
type Edge = tuple[int, int, float]
def distance(a: Coordinate, b: Coordinate) -> float:
x1, y1 = a
x2, y2 = b
return sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
def create_graph(coords: CoordMap) -> WeightedGraph:
graph: WeightedGraph = defaultdict(dict)
coord_items = coords.items()
for label1, coord1 in coord_items:
for label2, coord2 in coord_items:
if label1 == label2:
continue
weight = distance(coord1, coord2)
graph[label1][label2] = weight
graph[label2][label1] = weight
return graph
def calculate_cost(graph: WeightedGraph) -> float:
parent: dict[int, int] = {v: v for v in graph}
rank: dict[int, int] = {v: 0 for v in graph}
def find(v: int) -> int:
if parent[v] != v:
parent[v] = find(parent[v])
return parent[v]
def union(u: int, v: int) -> None:
root_u, root_v = find(u), find(v)
if root_u == root_v:
return
if rank[root_u] < rank[root_v]:
parent[root_u] = root_v
elif rank[root_u] > rank[root_v]:
parent[root_v] = root_u
else:
parent[root_v] = root_u
rank[root_u] += 1
num_components = len(graph)
mst_edges: list[Edge] = []
while num_components > 1:
cheapest: dict[int, Edge] = {}
for u in graph:
for v, w in graph[u].items():
set_u = find(u)
set_v = find(v)
if set_u == set_v:
continue
if set_u not in cheapest or cheapest[set_u][2] > w:
cheapest[set_u] = (u, v, w)
if set_v not in cheapest or cheapest[set_v][2] > w:
cheapest[set_v] = (v, u, w)
for edge in cheapest.values():
u, v, w = edge
set_u = find(u)
set_v = find(v)
if set_u == set_v:
continue
mst_edges.append(edge)
union(set_u, set_v)
num_components -= 1
return sum(map(lambda x: x[2], mst_edges))
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
n: int = int(input())
coords: CoordMap = {}
for i in range(0, n):
tokens = input().split()
coords[i] = (float(tokens[0]), float(tokens[1]))
weighted_graph = create_graph(coords)
cost = calculate_cost(weighted_graph)
print(f"${cost:.1f}M")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,4 @@
3
0 1.5
1 0
0 0

View File

@@ -0,0 +1,9 @@
8
0 0
1 1
0 1
3 3
4 5
2 2
1 0
3 2

View File

@@ -0,0 +1,3 @@
ACF
BCF
ABCCFF

View File

@@ -0,0 +1,3 @@
ACF
BCF
ABFCCF

62
Shuffle/shuffle.py Normal file
View File

@@ -0,0 +1,62 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
import pprint
def valid_shuffle(a: str, b: str, c: str) -> bool:
memo: list[list[bool | None]] = [
([None] * (len(a) + 1)) for _ in range(0, len(b) + 1)
]
memo[len(a)][len(b)] = True
def recurse(i: int, j: int) -> bool:
if i >= len(a) or j >= len(b):
return False
k: int = i + j
if k == (len(c) - 2):
return True
a_val: bool | None = memo[i + 1][j]
if a_val is None:
a_val = recurse(i + 1, j) if a[i] == c[k] else False
memo[i + 1][j] = a_val
b_val: bool | None = memo[i][j + 1]
if b_val is None:
b_val = recurse(i, j + 1) if b[j] == c[k] else False
memo[i][j + 1] = b_val
memo[i][j] = a_val or b_val
return a_val or b_val
val = recurse(0, 0)
pprint.pprint(memo)
return val
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
a: str = input()
b: str = input()
c: str = input()
print(valid_shuffle(a, b, c))
if __name__ == "__main__":
main()

View File

@@ -3,10 +3,10 @@
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1757570236,
"lastModified": 1762789285,
"owner": "cachix",
"repo": "devenv",
"rev": "c57bded76fa6a885ab1dee2c75216cc23d58b311",
"rev": "379dd49a2ae3470e216cf07bfee4326e3c5a5baf",
"type": "github"
},
"original": {
@@ -19,10 +19,10 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"lastModified": 1761588595,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
@@ -40,10 +40,10 @@
]
},
"locked": {
"lastModified": 1757588530,
"lastModified": 1762441963,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "b084b2c2b6bc23e83bbfe583b03664eb0b18c411",
"rev": "8e7576e79b88c16d7ee3bbd112c8d90070832885",
"type": "github"
},
"original": {
@@ -74,10 +74,10 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1755783167,
"lastModified": 1761313199,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "4a880fb247d24fbca57269af672e8f78935b0328",
"rev": "d1c30452ebecfc55185ae6d1c983c09da0c274ff",
"type": "github"
},
"original": {