1

Suppose I am given a matrix that resembles a phone keypad like-

1 2 3
4 5 6
7 8 9
  0

How do I generate the following dictionary without actually typing it myself (the tuples should not necessarily be sorted)-

my_dict = {1: (1, 2, 4, 5), 2: (1, 2, 3, 4, 5, 6), 3: (2, 3, 5, 6),
           4: (1, 2, 4, 5, 7, 8), 5: (1, 2, 3, 4, 5, 6, 7, 8, 9),
           6: (2, 3, 5, 6, 8, 9), 7: (0, 4, 5, 7, 8),
           8: (0, 4, 5, 6, 7, 8, 9), 9: (0, 5, 6, 8, 9),
           0: (0, 7, 8, 9)}

This dictionary basically tells me all the adjacent digits of a given number. For example, the adjacent digits of 1 are 1, 2, 4, 5.

Edit: The matrix would ideally be stored as a list of lists:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [None, 0, None]]

I know the brute force approach, but wanted to know a way to do this efficiently.

3
  • 4
    How is that matrix stored? Commented Dec 11, 2018 at 1:14
  • And the logic is unclear to me, can you please explain? Commented Dec 11, 2018 at 1:18
  • Matrix would be a list of lists, I edited the question. Thank you for pointing that out. Commented Dec 11, 2018 at 3:19

4 Answers 4

2

Assuming the keypad is store as a dictionary of positions (tuple x, y) and the corresponding number as value, you could do something like this:

import itertools


def distance(p1, p2):
    return sum((x1 - x2) ** 2 for x1, x2 in zip(p1, p2))


def neighbors(positions, target):
    return [position for position in positions if distance(target, position) < 4]


def numbers(kpad, keys):
    return tuple(sorted(map(kpad.get, keys)))


values = list(range(1, 10)) + [0]
positions = list(itertools.product([0, 1, 2], repeat=2)) + [(3, 1)]

keypad = dict(zip(positions, values))

result = {value: numbers(keypad, neighbors(keypad, key)) for key, value in keypad.items()}
print(result)

Output

{0: (0, 7, 8, 9), 1: (1, 2, 4, 5), 2: (1, 2, 3, 4, 5, 6), 3: (2, 3, 5, 6), 4: (1, 2, 4, 5, 7, 8), 5: (1, 2, 3, 4, 5, 6, 7, 8, 9), 6: (2, 3, 5, 6, 8, 9), 7: (0, 4, 5, 7, 8), 8: (0, 4, 5, 6, 7, 8, 9), 9: (0, 5, 6, 8, 9)}

The idea is for each position get the values of the neighboring points.

UPDATE

To transform a list a of lists into the keypad dictionary you can do something like this:

data = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [None, 0, None]]
keypad = {(i, j): value for i, sub in enumerate(data) for j, value in enumerate(sub) if value is not None}

The rest of the approach remains the same.

Sign up to request clarification or add additional context in comments.

Comments

1

Assuming your matrix looks something like this:

m = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
    [None, 0, None]
]

You could brute force your solution and just iterate over the matrix and collect adjacent cells using a defaultdict:

from collections import defaultdict
from pprint import pprint

m = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
    [None, 0, None]
]

rows = len(m)
cols = len(m[0])

# adjacent cells
adjacency = [(i, j) for i in (-1, 0, 1) for j in (-1, 0, 1) if not i == j == 0]

d = defaultdict(list)
for r in range(rows):
    for c in range(cols):
        cell = m[r][c]
        if cell is not None:
            d[cell].append(cell)
            for x, y in adjacency:
                if 0 <= r + x < rows and 0 <= c + y < cols:
                    adjacent = m[r + x][c + y]
                    if adjacent is not None:
                        d[cell].append(adjacent)

# print sorted adjacent cells
pprint({k: tuple(sorted(v)) for k, v in d.items()})

Which gives a dictionary of sorted adjacent cells:

{0: (0, 7, 8, 9),
 1: (1, 2, 4, 5),
 2: (1, 2, 3, 4, 5, 6),
 3: (2, 3, 5, 6),
 4: (1, 2, 4, 5, 7, 8),
 5: (1, 2, 3, 4, 5, 6, 7, 8, 9),
 6: (2, 3, 5, 6, 8, 9),
 7: (0, 4, 5, 7, 8),
 8: (0, 4, 5, 6, 7, 8, 9),
 9: (0, 5, 6, 8, 9)}

Comments

1

You can use a generator function:

import re
def all_adjacent(_c, _graph):
   _funcs = [lambda x,y:(x+1, y), lambda x,y:(x+1, y+1), lambda x,y:(x+1, y-1), lambda x,y:(x, y+1), lambda x,y:(x-1, y+1), lambda x,y:(x-1, y-1), lambda x,y:(x, y-1), lambda x,y:(x-1, y)]
   yield _graph[_c[0]][_c[1]]
   for func in _funcs:
     a, b = func(*_c)
     try:
       if a >= 0 and b >= 0:
         _val = _graph[a][b]
         if _val != '  ':
           yield _val
     except:
       pass


s = """
1 2 3
4 5 6
7 8 9
  0  
"""
new_data = [re.findall('\d+|\s{2,}', i) for i in filter(None, s.split('\n'))]
final_results = {c:list(all_adjacent((i, d), new_data)) for i, a in enumerate(new_data) for d, c in enumerate(a) if c != '  '}
_result = {int(a):tuple(sorted(map(int, b))) for a, b in final_results.items()}

Output:

{1: (1, 2, 4, 5), 2: (1, 2, 3, 4, 5, 6), 3: (2, 3, 5, 6), 4: (1, 2, 4, 5, 7, 8), 5: (1, 2, 3, 4, 5, 6, 7, 8, 9), 6: (2, 3, 5, 6, 8, 9), 7: (0, 4, 5, 7, 8), 8: (0, 4, 5, 6, 7, 8, 9), 9: (0, 5, 6, 8, 9), 0: (0, 7, 8, 9)}

Edit: storing the matrix as a list of lists:

import re
def all_adjacent(_c, _graph):
  _funcs = [lambda x,y:(x+1, y), lambda x,y:(x+1, y+1), lambda x,y:(x+1, y-1), lambda x,y:(x, y+1), lambda x,y:(x-1, y+1), lambda x,y:(x-1, y-1), lambda x,y:(x, y-1), lambda x,y:(x-1, y)]
  yield _graph[_c[0]][_c[1]]
  for func in _funcs:
    a, b = func(*_c)
    try:
      if a >= 0 and b >= 0:
        _val = _graph[a][b]
        if _val is not None:
          yield _val
    except:
      pass

new_data = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [None, 0, None]]
final_results = {c:(i, d) for i, a in enumerate(new_data) for d, c in enumerate(a) if c is not None}
_result = {int(a):tuple(map(int, all_adjacent(b, new_data))) for a, b in final_results.items()}

Output:

{1: (1, 4, 5, 2), 2: (2, 5, 6, 4, 3, 1), 3: (3, 6, 5, 2), 4: (4, 7, 8, 5, 2, 1), 5: (5, 8, 9, 7, 6, 3, 1, 4, 2), 6: (6, 9, 8, 2, 5, 3), 7: (7, 0, 8, 5, 4), 8: (8, 0, 9, 6, 4, 7, 5), 9: (9, 0, 5, 8, 6), 0: (0, 9, 7, 8)}

3 Comments

That's an impressive answer, I just updated the question and mentioned that the matrix would ideally be a list of lists. What do you think would be the most efficient way?
Can you please add comments to explain what you've done with your func(*_c) and yield statements? Also, what is the time complexity of your function? Also, the tuples need not be sorted, so you could remove that.
@kev * is special syntax for unpacking iterables in either a function call or in another container of the same type. yield is utilized as part of a generator expression which is utilized for "lazy" generation of values for lower memory usage and thus improved performance. For the edited solution, the worst case time complexity is O(n^2).
0
T9 = [
    [1,2,3],
    [4,5,6],
    [7,8,9],
    [None,0,None]
]

result = {T9[i][j]:[T9[i+di][j+dj] for dj in range(-1,2) for di in range(-1,2) if 0<= i+di <=3 and 0 <= j+dj <=2 and T9[i+di][j+dj] is not None]  for j in range(3) for i in range(4) if T9[i][j] is not None}

ugly but works

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.