Skip to content

Instantly share code, notes, and snippets.

@kenorb
Last active February 15, 2025 00:25
Show Gist options
  • Save kenorb/2b03fd495a9f43a0dc7bac9dd1542d87 to your computer and use it in GitHub Desktop.
Save kenorb/2b03fd495a9f43a0dc7bac9dd1542d87 to your computer and use it in GitHub Desktop.
Find surrounding keys on keyboard layouts
#!/usr/bin/env bash
keyboard_layouts = {
# US QWERTY Layout (Lowercase)
"us_lowercase": [
"`1234567890-=",
" qwertyuiop[]\\",
" asdfghjkl;'",
" zxcvbnm,./"
],
# US QWERTY Layout (Uppercase)
"usuppercase": [
"~!@#$%^&*()+",
" QWERTYUIOP{}|",
" ASDFGHJKL:\"",
" ZXCVBNM<>?"
],
# UK QWERTY Layout (Lowercase)
"uk_lowercase": [
"§±1234567890-=",
" qwertyuiop[]#",
" asdfghjkl;'",
" zxcvbnm,./"
],
# UK QWERTY Layout (Uppercase)
"ukuppercase": [
"±§!@#$%^&*()+",
" QWERTYUIOP{}~",
" ASDFGHJKL:\"",
" ZXCVBNM<>?"
]
}
def find_surroundings(keyboard_layout, layout_name):
# Convert layout into a 2D matrix for easier processing
matrix = keyboard_layout[layout_name]
# Find max row length to pad shorter rows
max_length = max(len(row) for row in matrix)
padded_matrix = [row.ljust(max_length) for row in matrix]
results = []
# Iterate through each row and character
for row_idx, row in enumerate(padded_matrix):
for col_idx, char in enumerate(row):
if char == ' ': # Skip space characters
continue
surroundings = []
# Check all 8 directions around the character
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0: # Skip the character itself
continue
new_row = row_idx + dr
new_col = col_idx + dc
# Check if the position is valid
if (0 <= new_row < len(padded_matrix) and
0 <= new_col < len(padded_matrix[new_row])):
neighbor = padded_matrix[new_row][new_col]
if neighbor != ' ': # Only add non-space characters
surroundings.append(neighbor)
results.append(f"{char}[{''.join(surroundings)}]")
return results
# Process each layout
for layout_name in keyboard_layouts:
print(f"\n=== {layout_name} ===")
surroundings = find_surroundings(keyboard_layouts, layout_name)
for result in surroundings:
print(result)
import re
def generate_adjacent_sequences_from_grid(grid, min_len=2, max_len=4):
"""
Walk the grid in four directions (horizontal right, vertical down,
diagonal down-right, and diagonal down-left) and collect all contiguous
strings of length between min_len and max_len. Also add each sequence's reverse.
"""
sequences = set()
rows = len(grid)
cols = max(len(row) for row in grid)
# For each cell in the grid:
for r in range(rows):
for c in range(cols):
# If this row doesn't have a cell at index c or it's None, skip.
if c >= len(grid[r]) or grid[r][c] is None:
continue
start_char = grid[r][c]
# For each of the four directions:
for dr, dc in [(0, 1), (1, 0), (1, 1), (1, -1)]:
seq = start_char
nr, nc = r, c
# Build sequences by stepping up to max_len-1 additional keys.
for _ in range(1, max_len):
nr += dr
nc += dc
# Check bounds:
if nr < 0 or nr >= rows or nc < 0 or nc >= cols:
break
# If this row does not have a cell at index nc or it is None, stop.
if nc >= len(grid[nr]) or grid[nr][nc] is None:
break
seq += grid[nr][nc]
if len(seq) >= min_len:
sequences.add(seq)
sequences.add(seq[::-1])
return sequences
# --- Define grids for several layouts ---
# The grids below are approximate and assume that missing keys are represented by None.
# They use fixed-width rows so that physical offsets are approximated by None entries.
# QWERTY (letters only)
# Row0: 10 keys; Row1: offset by one (None in col0); Row2: offset by two (None in col0-1)
grid_qwerty = [
list("qwertyuiop"),
[None] + list("asdfghjkl"),
[None, None] + list("zxcvbnm") + [None] # padded to 10 columns
]
# QWERTZ – similar to QWERTY but with "qwertzuiop" in row0 and a slight difference on row2.
grid_qwertz = [
list("qwertzuiop"),
[None] + list("asdfghjkl"),
[None, None] + list("yxcvbnm") + [None]
]
# AZERTY – note that the home and bottom rows differ in letters.
grid_azerty = [
list("azertyuiop"),
[None] + list("qsdfghjklm"), # assume home row is offset by one
[None, None] + list("wxcvbn") + [None, None] # pad to 10 columns (2+6+2)
]
# Dvorak – using a common letter layout (all rows assumed aligned for simplicity)
grid_dvorak = [
list("',.pyfgcrl"),
list("aoeuidhtns"),
list(";qjkxbmwvz")
]
# Colemak – a common Colemak layout (again, assuming aligned rows)
grid_colemak = [
list("qwfpgjlyu;"),
list("arstdhneio"),
list("zxcvbkm,./")
]
# (If you also want to include number/punctuation rows, you can define additional grids or expand these ones.)
layouts_grids = {
"qwerty": grid_qwerty,
"qwertz": grid_qwertz,
"azerty": grid_azerty,
"dvorak": grid_dvorak,
"colemak": grid_colemak
}
all_sequences = set()
for layout_name, grid in layouts_grids.items():
seqs = generate_adjacent_sequences_from_grid(grid)
all_sequences.update(seqs)
# Escape any regex metacharacters in the sequences:
escaped_substrings = [re.escape(s) for s in all_sequences]
# Sort descending by length so longer alternatives are attempted first.
escaped_substrings.sort(key=len, reverse=True)
# Combine into one big alternation group.
combined_regex = "(?:" + "|".join(escaped_substrings) + ")"
# Print the final regex (it may be very long):
print("Combined regex for horizontal, vertical, and diagonal adjacencies:")
print(combined_regex)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment