Compare commits

...

9 Commits

18 changed files with 735 additions and 10 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

@@ -9,14 +9,58 @@ Honor Code and Acknowledgments:
"""
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())
matrix: list[list[int]] = [list(map(int, input().split())) for _ in range(0, n)]
land_grid: list[list[int]] = [list(map(int, input().split())) for _ in range(0, n)]
pass
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__":

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

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.

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

@@ -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

@@ -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": {