""" Compute minimum hops from each node to the nearest letter node (a-z) in the slipnet. Like Erdos number, but starting from letter nodes. Adds 'minPathToLetter' field to each node in the JSON. """ import json import networkx as nx def load_slipnet(filepath): with open(filepath, 'r') as f: return json.load(f) def save_slipnet(data, filepath): with open(filepath, 'w') as f: json.dump(data, f, indent=2) def build_graph(data): """Build an undirected unweighted NetworkX graph from slipnet JSON.""" G = nx.Graph() # Undirected graph - edges work both ways # Add all nodes for node in data['nodes']: G.add_node(node['name']) # Add edges (unweighted - each edge counts as 1 hop) for link in data['links']: G.add_edge(link['source'], link['destination']) return G def get_letter_nodes(): """Return set of letter nodes (a-z).""" return set(chr(i) for i in range(ord('a'), ord('z') + 1)) def compute_min_hops_to_letters(G, letter_nodes): """ Compute minimum hops from each node to the nearest letter node. Like Erdos number but for letters. Returns dict: node_name -> {hops, path, nearest_letter} """ results = {} # For each node, find shortest path (by hop count) to any letter for node in G.nodes(): if node in letter_nodes: # Letters have 0 hops to themselves results[node] = { 'hops': 0, 'path': [node], 'nearestLetter': node } else: min_hops = float('inf') min_path = None nearest_letter = None for letter in letter_nodes: try: # Find shortest path by hop count (no weight parameter) path = nx.shortest_path(G, source=node, target=letter) hops = len(path) - 1 # Number of edges = nodes - 1 if hops < min_hops: min_hops = hops min_path = path nearest_letter = letter except nx.NetworkXNoPath: continue if min_path is not None: results[node] = { 'hops': min_hops, 'path': min_path, 'nearestLetter': nearest_letter } else: results[node] = { 'hops': None, 'path': None, 'nearestLetter': None } return results def main(): filepath = r'C:\Users\alexa\copycat\slipnet_analysis\slipnet.json' # Load the slipnet data = load_slipnet(filepath) print(f"Loaded slipnet with {data['nodeCount']} nodes and {data['linkCount']} links") # Build the graph G = build_graph(data) print(f"Built graph with {G.number_of_nodes()} nodes and {G.number_of_edges()} edges") # Get letter nodes letter_nodes = get_letter_nodes() print(f"Letter nodes: {sorted(letter_nodes)}") # Compute minimum hops to letters path_results = compute_min_hops_to_letters(G, letter_nodes) # Find max hops among reachable nodes max_hops = max(r['hops'] for r in path_results.values() if r['hops'] is not None) unreachable_hops = 2 * max_hops print(f"Max hops among reachable nodes: {max_hops}") print(f"Assigning unreachable nodes hops = 2 * {max_hops} = {unreachable_hops}") # Assign unreachable nodes hops = 2 * max_hops for node_name, result in path_results.items(): if result['hops'] is None: result['hops'] = unreachable_hops result['path'] = None # No path exists result['nearestLetter'] = None # Add results to each node in the JSON for node in data['nodes']: node_name = node['name'] if node_name in path_results: result = path_results[node_name] node['minPathToLetter'] = { 'hops': result['hops'], 'path': result['path'], 'nearestLetter': result['nearestLetter'] } # Save the updated JSON save_slipnet(data, filepath) print(f"\nUpdated slipnet saved to {filepath}") # Print summary sorted by hops print("\n=== Summary of minimum hops to letter nodes (Erdos-style) ===") reachable_nodes = [(n['name'], n['minPathToLetter']) for n in data['nodes'] if 'minPathToLetter' in n and n['minPathToLetter']['path'] is not None and n['minPathToLetter']['hops'] > 0] unreachable_nodes = [(n['name'], n['minPathToLetter']) for n in data['nodes'] if 'minPathToLetter' in n and n['minPathToLetter']['path'] is None and n['minPathToLetter']['hops'] > 0] # Sort by hops, then by name reachable_nodes.sort(key=lambda x: (x[1]['hops'], x[0])) unreachable_nodes.sort(key=lambda x: x[0]) print(f"\n{'Node':<30} {'Hops':<6} {'Nearest':<8} {'Path'}") print("-" * 80) for name, info in reachable_nodes: path_str = ' -> '.join(info['path']) print(f"{name:<30} {info['hops']:<6} {info['nearestLetter']:<8} {path_str}") if unreachable_nodes: print(f"\nUnreachable nodes (assigned hops = {unreachable_hops}):") for name, info in unreachable_nodes: print(f" {name:<30} (depth: {[n['conceptualDepth'] for n in data['nodes'] if n['name'] == name][0]})") if __name__ == '__main__': main()