# Path: scratch/linear_algebra.py
# def add(v: Vector, w: Vector) -> Vector:
# def subtract(v: Vector, w: Vector) -> Vector:
# def vector_sum(vectors: List[Vector]) -> Vector:
# def scalar_multiply(c: float, v: Vector) -> Vector:
# def vector_mean(vectors: List[Vector]) -> Vector:
# def dot(v: Vector, w: Vector) -> float:
# def sum_of_squares(v: Vector) -> float:
# def magnitude(v: Vector) -> float:
# def squared_distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:  # type: ignore
# def shape(A: Matrix) -> Tuple[int, int]:
# def get_row(A: Matrix, i: int) -> Vector:
# def get_column(A: Matrix, j: int) -> Vector:
# def make_matrix(num_rows: int,
#                 num_cols: int,
#                 entry_fn: Callable[[int, int], float]) -> Matrix:
# def identity_matrix(n: int) -> Matrix:
# A = [[1, 2, 3],  # A has 2 rows and 3 columns
#      [4, 5, 6]]
# B = [[1, 2],     # B has 3 rows and 2 columns
#      [3, 4],
#      [5, 6]]
# 
# Path: scratch/linear_algebra.py
# def add(v: Vector, w: Vector) -> Vector:
# def subtract(v: Vector, w: Vector) -> Vector:
# def vector_sum(vectors: List[Vector]) -> Vector:
# def scalar_multiply(c: float, v: Vector) -> Vector:
# def vector_mean(vectors: List[Vector]) -> Vector:
# def dot(v: Vector, w: Vector) -> float:
# def sum_of_squares(v: Vector) -> float:
# def magnitude(v: Vector) -> float:
# def squared_distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:  # type: ignore
# def shape(A: Matrix) -> Tuple[int, int]:
# def get_row(A: Matrix, i: int) -> Vector:
# def get_column(A: Matrix, j: int) -> Vector:
# def make_matrix(num_rows: int,
#                 num_cols: int,
#                 entry_fn: Callable[[int, int], float]) -> Matrix:
# def identity_matrix(n: int) -> Matrix:
# A = [[1, 2, 3],  # A has 2 rows and 3 columns
#      [4, 5, 6]]
# B = [[1, 2],     # B has 3 rows and 2 columns
#      [3, 4],
#      [5, 6]]
# 
# Path: scratch/linear_algebra.py
# def magnitude(v: Vector) -> float:
#     """Returns the magnitude (or length) of v"""
#     return math.sqrt(sum_of_squares(v))   # math.sqrt is square root function
# 
# def distance(v: Vector, w: Vector) -> float:
#     """Computes the distance between v and w"""
#     return math.sqrt(squared_distance(v, w))


from typing import NamedTuple
from typing import Dict, List
from collections import deque
from scratch.linear_algebra import Matrix, make_matrix, shape
from scratch.linear_algebra import Vector, dot
from typing import Tuple
from scratch.linear_algebra import magnitude, distance
from collections import Counter
import random
import tqdm
for source in users:
    for target_id, paths in shortest_paths[source.id].items():
        if source.id < target_id:      # don't double count
            num_paths = len(paths)     # how many shortest paths?
            contrib = 1 / num_paths    # contribution to centrality
            for path in paths:
                for between_id in path:
                    if between_id not in [source.id, target_id]:
                        betweenness_centrality[between_id] += contrib

def farness(user_id: int) -> float:
    """the sum of the lengths of the shortest paths to each other user"""
    return sum(len(paths[0])
               for paths in shortest_paths[user_id].values())

closeness_centrality = {user.id: 1 / farness(user.id) for user in users}


def matrix_times_matrix(m1: Matrix, m2: Matrix) -> Matrix:
    nr1, nc1 = shape(m1)
    nr2, nc2 = shape(m2)
    assert nc1 == nr2, "must have (# of columns in m1) == (# of rows in m2)"

    def entry_fn(i: int, j: int) -> float:
        """dot product of i-th row of m1 with j-th column of m2"""
        return sum(m1[i][k] * m2[k][j] for k in range(nc1))

    return make_matrix(nr1, nc2, entry_fn)



# Path: scratch/linear_algebra.py
# def add(v: Vector, w: Vector) -> Vector:
# def subtract(v: Vector, w: Vector) -> Vector:
# def vector_sum(vectors: List[Vector]) -> Vector:
# def scalar_multiply(c: float, v: Vector) -> Vector:
# def vector_mean(vectors: List[Vector]) -> Vector:
# def dot(v: Vector, w: Vector) -> float:
# def sum_of_squares(v: Vector) -> float:
# def magnitude(v: Vector) -> float:
# def squared_distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:  # type: ignore
# def shape(A: Matrix) -> Tuple[int, int]:
# def get_row(A: Matrix, i: int) -> Vector:
# def get_column(A: Matrix, j: int) -> Vector:
# def make_matrix(num_rows: int,
#                 num_cols: int,
#                 entry_fn: Callable[[int, int], float]) -> Matrix:
# def identity_matrix(n: int) -> Matrix:
# A = [[1, 2, 3],  # A has 2 rows and 3 columns
#      [4, 5, 6]]
# B = [[1, 2],     # B has 3 rows and 2 columns
#      [3, 4],
#      [5, 6]]
# 
# Path: scratch/linear_algebra.py
# def add(v: Vector, w: Vector) -> Vector:
# def subtract(v: Vector, w: Vector) -> Vector:
# def vector_sum(vectors: List[Vector]) -> Vector:
# def scalar_multiply(c: float, v: Vector) -> Vector:
# def vector_mean(vectors: List[Vector]) -> Vector:
# def dot(v: Vector, w: Vector) -> float:
# def sum_of_squares(v: Vector) -> float:
# def magnitude(v: Vector) -> float:
# def squared_distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:  # type: ignore
# def shape(A: Matrix) -> Tuple[int, int]:
# def get_row(A: Matrix, i: int) -> Vector:
# def get_column(A: Matrix, j: int) -> Vector:
# def make_matrix(num_rows: int,
#                 num_cols: int,
#                 entry_fn: Callable[[int, int], float]) -> Matrix:
# def identity_matrix(n: int) -> Matrix:
# A = [[1, 2, 3],  # A has 2 rows and 3 columns
#      [4, 5, 6]]
# B = [[1, 2],     # B has 3 rows and 2 columns
#      [3, 4],
#      [5, 6]]
# 
# Path: scratch/linear_algebra.py
# def magnitude(v: Vector) -> float:
#     """Returns the magnitude (or length) of v"""
#     return math.sqrt(sum_of_squares(v))   # math.sqrt is square root function
# 
# def distance(v: Vector, w: Vector) -> float:
#     """Computes the distance between v and w"""
#     return math.sqrt(squared_distance(v, w))


# Path: scratch/network_analysis.py
from typing import NamedTuple
from typing import Dict, List
from collections import deque
from scratch.linear_algebra import Matrix, make_matrix, shape
from scratch.linear_algebra import Vector, dot
from typing import Tuple
from scratch.linear_algebra import magnitude, distance
from collections import Counter
import random
import tqdm

for source in users:
    for target_id, paths in shortest_paths[source.id].items():
        if source.id < target_id:      # don't double count
            num_paths = len(paths)     # how many shortest paths?
            contrib = 1 / num_paths    # contribution to centrality
            for path in paths:
                for between_id in path:
                    if between_id not in [source.id, target_id]:
                        betweenness_centrality[between_id] += contrib

def farness(user_id: int) -> float:
    """the sum of the lengths of the shortest paths to each other user"""
    return sum(len(paths[0])
               for paths in shortest_paths[user_id].values())

closeness_centrality = {user.id: 1 / farness(user.id) for user in users}


def matrix_times_matrix(m1: Matrix, m2: Matrix) -> Matrix:
    nr1, nc1 = shape(m1)
    nr2, nc2 = shape(m2)
    assert nc1 == nr2, "must have (# of columns in m1) == (# of rows in m2)"

    def entry_fn(i: int, j: int) -> float:
        """dot product of i-th row of m1 with j-th column of m2"""
        return sum(m1[i][k] * m2[k][j] for k in range(nc1))

    return make_matrix(nr1, nc2, entry_fn)



def matrix_times_vector(m: Matrix, v: Vector) -> Vector: