File size: 7,481 Bytes
e232e39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# Hexagonal Adjacency Cache
# Pre-computed adjacency matrix for O(1) neighbor lookups
# Eliminates O(ring^2) recursive lookups in get_hex_neighbor()

# Module-level cache environment
.hex_adjacency_cache <- new.env(hash = TRUE, parent = emptyenv())

#' Build or retrieve cached hexagonal adjacency matrix
#'
#' Creates a pre-computed matrix of all neighbor relationships for a hexagonal
#' puzzle. Uses axial coordinate math for O(n) construction instead of O(n*ring^2)
#' recursive lookups.
#'
#' @param rings Number of rings in puzzle
#' @return Matrix where adj_matrix[piece_id, side+1] = neighbor_id (or NA for boundary)
get_hex_adjacency_matrix <- function(rings) {
  cache_key <- as.character(rings)


  # Check cache first
  if (exists(cache_key, envir = .hex_adjacency_cache, inherits = FALSE)) {
    return(get(cache_key, envir = .hex_adjacency_cache, inherits = FALSE))
  }

  # Build adjacency matrix using axial coordinate math
  num_pieces <- 3 * rings * (rings - 1) + 1

  # Matrix: rows = piece_id (1 to num_pieces), cols = side (1-6 for sides 0-5)
  # Value = neighbor piece_id or NA for boundary
  adj_matrix <- matrix(NA_integer_, nrow = num_pieces, ncol = 6)

  # Build coordinate-to-piece lookup table (hash map for O(1) lookup)
  coord_to_piece <- new.env(hash = TRUE, parent = emptyenv())
  piece_coords <- vector("list", num_pieces)

  # First pass: map all pieces to coordinates
  for (piece_id in 1:num_pieces) {
    axial <- map_piece_id_to_axial(piece_id, rings)
    coord_key <- paste(axial$q, axial$r, sep = ",")
    coord_to_piece[[coord_key]] <- piece_id
    piece_coords[[piece_id]] <- c(axial$q, axial$r)
  }

  # Axial direction vectors for flat-top hexagons
  # Side 0 = E, 1 = NE, 2 = NW, 3 = W, 4 = SW, 5 = SE
  directions <- list(
    c(1, 0),    # Side 0: E
    c(1, -1),   # Side 1: NE
    c(0, -1),   # Side 2: NW
    c(-1, 0),   # Side 3: W
    c(-1, 1),   # Side 4: SW
    c(0, 1)     # Side 5: SE
  )

  # Second pass: for each piece, compute neighbors using coordinate math
  for (piece_id in 1:num_pieces) {
    coords <- piece_coords[[piece_id]]
    q <- coords[1]
    r <- coords[2]

    for (side in 0:5) {
      dir <- directions[[side + 1]]
      neighbor_q <- q + dir[1]
      neighbor_r <- r + dir[2]
      neighbor_key <- paste(neighbor_q, neighbor_r, sep = ",")

      # Look up neighbor in coordinate table
      if (exists(neighbor_key, envir = coord_to_piece, inherits = FALSE)) {
        adj_matrix[piece_id, side + 1] <- coord_to_piece[[neighbor_key]]
      }
      # If not found, it's a boundary edge (NA already set)
    }
  }

  # Cache the result
  assign(cache_key, adj_matrix, envir = .hex_adjacency_cache)

  return(adj_matrix)
}

#' Fast hexagonal neighbor lookup using cached adjacency matrix
#'
#' O(1) lookup after initial O(n) matrix construction.
#'
#' @param piece_id Piece ID (1 to num_pieces)
#' @param side Side number (0-5)
#' @param rings Number of rings in puzzle
#' @return Neighbor piece ID or NA if boundary
get_hex_neighbor_fast <- function(piece_id, side, rings) {
  adj_matrix <- get_hex_adjacency_matrix(rings)
  return(adj_matrix[piece_id, side + 1])
}

#' Get all neighbors for a piece using cached matrix
#'
#' Returns pre-allocated data frame with all 6 neighbors.
#' Much faster than iterative rbind approach.
#'
#' @param piece_id Piece ID (1 to num_pieces)
#' @param rings Number of rings in puzzle
#' @return Data frame with direction, neighbor_id, is_boundary
get_hex_neighbors_fast <- function(piece_id, rings) {
  adj_matrix <- get_hex_adjacency_matrix(rings)

  # Pre-allocate data frame (no rbind needed)
  neighbors <- data.frame(
    direction = as.character(0:5),
    neighbor_id = adj_matrix[piece_id, ],
    is_boundary = is.na(adj_matrix[piece_id, ]),
    stringsAsFactors = FALSE
  )

  return(neighbors)
}

#' Clear hexagonal adjacency cache
#'
#' Useful for testing or when memory is constrained.
#'
clear_hex_adjacency_cache <- function() {
  rm(list = ls(envir = .hex_adjacency_cache), envir = .hex_adjacency_cache)
}

#' Optimized fused edge computation using cached adjacency
#'
#' Uses hash sets for O(1) group membership and edge existence checks.
#' Uses the original get_hex_neighbors_for_fusion for topology-correct sides.
#' Uses list accumulation instead of vector c() for O(n) instead of O(n^2).
#'
#' IMPORTANT: Uses original get_hex_neighbor() for side mapping to maintain
#' compatibility with is_edge_fused() and the existing edge key format.
#' The main speedup comes from hash sets for membership/existence checks
#' and list accumulation instead of vector concatenation.
#'
#' @param fusion_groups List of piece ID vectors
#' @param rings Number of rings in puzzle
#' @return List with fused_edges, edge_to_group, piece_to_group
compute_hex_fused_edges_fast <- function(fusion_groups, rings) {
  if (is.null(fusion_groups) || length(fusion_groups) == 0) {
    return(NULL)
  }

  # Build piece-to-group hash map (O(1) lookup)
  piece_to_group <- new.env(hash = TRUE, parent = emptyenv())
  for (group_idx in seq_along(fusion_groups)) {
    group <- fusion_groups[[group_idx]]
    for (piece_id in group) {
      piece_to_group[[as.character(piece_id)]] <- group_idx
    }
  }

  # Build group membership hash sets (O(1) membership test)
  group_sets <- lapply(fusion_groups, function(group) {
    group_set <- new.env(hash = TRUE, parent = emptyenv())
    for (piece_id in group) {
      group_set[[as.character(piece_id)]] <- TRUE
    }
    group_set
  })

  # Hash set for edge deduplication (O(1) existence check)
  edge_set <- new.env(hash = TRUE, parent = emptyenv())

  # List accumulation for edges (O(1) append, O(n) final unlist)
  fused_edges_list <- list()
  edge_to_group <- list()

  # Process all pieces in all groups
  for (group_idx in seq_along(fusion_groups)) {
    group <- fusion_groups[[group_idx]]
    group_set <- group_sets[[group_idx]]

    for (piece_id in group) {
      # Use original function for topology-correct side mapping
      neighbors <- get_hex_neighbors_for_fusion(piece_id, rings)

      for (i in seq_len(nrow(neighbors))) {
        direction <- neighbors$direction[i]
        neighbor_id <- neighbors$neighbor_id[i]
        is_boundary <- neighbors$is_boundary[i]

        # Skip boundary edges
        if (is_boundary || is.na(neighbor_id)) next

        # Check if neighbor is in same group (O(1) hash lookup instead of O(n) %in%)
        if (exists(as.character(neighbor_id), envir = group_set, inherits = FALSE)) {
          # This edge is fused - create edge key using standard format
          edge_key <- make_edge_key(piece_id, direction)

          # Check if already added (O(1) hash lookup instead of O(n) %in%)
          if (!exists(edge_key, envir = edge_set, inherits = FALSE)) {
            edge_set[[edge_key]] <- TRUE
            fused_edges_list[[length(fused_edges_list) + 1]] <- edge_key
            edge_to_group[[edge_key]] <- group_idx
          }
        }
      }
    }
  }

  # Convert list to named list for piece_to_group return
  piece_to_group_list <- as.list(piece_to_group)
  names(piece_to_group_list) <- ls(piece_to_group)

  return(list(
    fused_edges = unlist(fused_edges_list),
    edge_to_group = edge_to_group,
    piece_to_group = piece_to_group_list
  ))
}