0

I want to let the user make a table of any size necessary. Firstly he would input the number of rows and columns, then he would input a pair of numbers for each column and each row, and then data in the remaining cells. This is an example:

row\column|  23  |  54  |  34  |  75
-------------------------------------
   65     |  AM  |   h  |   9  |  C
-------------------------------------
   78     |  56  |   in |   13 |  ok

So basically after inputing all the information he should be able to say 7834 and get 13 and vice versa (input 9C and get 65346575, for example).

I tried this and I understand why it doesn't work, but it's the only idea I had.

nc = int(raw_input('Input number of columns: '))
nr = int(raw_input('Input number of rows: '))

table = [[raw_input('Input two digits for each column: ') for i in range(2, nc)] for i in range(1)]
table = [[raw_input('Input two digits for each row: ') for i in range(1)] for i in range(2, nr)]
table = [[raw_input('Input data: ') for i in range(2, nc)] for i in range(2, nr)]

Any help is appreciated.

  • You're setting the value of `table` three times, to three different values. Is that what you want? – Sam McCreery Nov 26 '15 at 00:17
  • No, I want it to be one table. I just did not know how to separate the first row and column from the rest of the table, because the data is different. –  Nov 26 '15 at 11:06

2 Answers2

0

Won't work that way.

Actually, I sort of lie. It kind of will work that way (I had trouble with the generators you wrote):

>>> table = [[raw_input('Input data: ') for i in range(1, nc+1)] for i in         range(1, nr+1)]
Input data: 1
Input data: 2
Input data: 3
Input data: 4
Input data: 5
Input data: 6 
Input data: 7 
Input data: 8
Input data: 9
>>> table
[['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]

But that's what you asked for in the beginning--you seem to want the end user to be able to enter the column and row headers, and then fill the data.

If we:

table = [[raw_input('Input data: {},{} '.format(a,b)) for a in range(0, nc)] for b in range(0, nr)]

then we can get:

Input data: 0,0 None
Input data: 1,0 1
Input data: 2,0 2
Input data: 0,1 a
Input data: 1,1 Ted
Input data: 2,1 Fred
Input data: 0,2 b
Input data: 1,2 3.214
Input data: 2,2 Copy
>>> table
[['None', '1', '2'], ['a', 'Ted', 'Fred'], ['b', '3.214', 'Copy']]

Now, the messed up part of your scheme is that to find a coordinate in your "grid" you have to read every column heading and get back it's position, then read every row heading to get back it's position. then you can :

value = grid[r][c]

Now, to do your reverse is even harder--you have to read EVERY CELL to get back your row and column headings.

Oh, and we've done no error checking to make sure you don't enter 2 rows or headings the same, which would completely blow your scheme. You'll need that.

Also it's a lot of work filling out that grid.

The rest of this is me noodling through it w/out generators, and making some educational mistakes.

If you want to use lists of lists:

row = [None for i in range(0,nc+1)]
grid = [row for i in range(0,nr+1)]

This then gives you a list of lists that are filled with None.

>>> grid
[[None, None, None, None], [None, None, None, None], [None, None, None, None], [None, None, None, None]]

Ok, so put in the column headers:

>>> for ch in range (1,nc+1):
...     grid[0][ch] = raw_input("Header, Column {}".format(ch))
...
Header, Column 111
Header, Column 222
Header, Column 333
>>> grid
[[None, '11', '22', '33'], [None, '11', '22', '33'], [None, '11', '22', '33'], [None, '11', '22', '33']]

Huh, why didn't that work?

>>> grid[0][3]="steve"
>>> grid
[[None, '11', '22', 'steve'], [None, '11', '22', 'steve'], [None, '11', '22', 'steve'], [None, '11', '22', 'steve']]

Oh, yeah.

Bugger.

import copy
row = [None for i in range(0,nc+1)]
grid = [copy.deepcopy(row) for i in range(0,nr+1)]
>>> for ch in range (1,nc+1):
...     grid[0][ch] = raw_input("Header, Column {}: ".format(ch))
...
Header, Column 1: 11
Header, Column 2: 22
Header, Column 3: 33
>>> grid
[[None, '11', '22', '33'], [None, 5, None, None], [None, None, None, None], [None, None, None, None]]

(ignore the 5, that was me testing)

And then the rows:

>>> for rh in range(1,nr+1):
...     grid[rh][0] = raw_input("Row Header: {} ".format(rh))
...
Row Header: 1 11
Row Header: 2 22
Row Header: 3 33
>>> grid
[[None, '11', '22', '33'], ['11', 5, None, None], ['22', None, None, None], ['33', None, None, None]]

So now you fill it with data (left as an exercise because it's blood obvious).

Petro
  • 776
  • 6
  • 13
  • can you please elaborate on how I can get the row and column header based on the data in the cell and the other way around? –  Nov 26 '15 at 22:31
  • 1
    Yeah, column headers are always the contents of row 0 for that column, while rows are always column 0 for that row. So when you find a cell [x,y] that matches what you are looking for [0,y] is the header for one while [x,0] is the header for the other. At least I think. – Petro Nov 28 '15 at 02:07
0

I took the liberty of leaving all of your integers as strings because you showed the tendency of concatenating them together.

If you really want a 2D array, which is transposable, the least painful (and quite powerful) solution would be to use a numpy array. You will have to install numpy if you haven't done so already, but since you didn't specify any restriction for extension libraries, I assumed this solution to be acceptable.

Array indexing starts with 0 and only accepts integer numbers. There are ways to have an array with custom indices, such as 23, 54, 34, 75 instead of 0, 1, 2, 3. If you decide to subclass numpy.array and override a lot of its methods, it may get more convoluted that expected.

Instead, I offer the solution of creating a wrapper class which can handle your custom indexing, but not slicing (which might be a nonsense thing in your case anyway). Under the hood, when you request for '6523', it will split it up into 2-digit strings, '65' and '23' and then look for their position in the row/column lists. In this case, it would be (0, 0). Now you can use this index for the array to fetch the desired element. Finding the indices for an element works in the reverse way. At no point do we directly interact with the array structure, hence no need to override any of its methods.

Code

import numpy as np

class CustomIndexTable:
    def __init__(self, rows, columns, elements):
        self.rows = rows
        self.columns = columns
        self.data = np.array(elements, dtype=object)
        self.data = self.data.reshape((len(rows), len(columns)))

    def __getitem__(self, index):
        x, y = index[:2], index[2:]
        return self.data[self.rows.index(x),self.columns.index(y)]

    def __setitem__(self, index, element):
        x, y = index[:2], index[2:]
        self.data[self.rows.index(x),self.columns.index(y)] = element

    def _where(self, element):
        x, y = np.where(self.data == element)
        return self.rows[x] + self.columns[y]

    def transpose(self):
        self.rows, self.columns = self.columns, self.rows
        self.data = self.data.T

    def where(self, sequence):
        elements = []
        start = 0
        for end in xrange(1, len(sequence)+1):
            if sequence[start:end] in self.data:
                elements.append(sequence[start:end])
                start = end
        return ''.join(self._where(e) for e in elements)

def input_matrix_data(text):
    return raw_input(text).split()

col_indices = input_matrix_data("Column indices: ")
row_indices = input_matrix_data("Row indices: ")
data = input_matrix_data("All data, sorted by row: ")

table = CustomIndexTable(row_indices, col_indices, data)

Comments

You don't want the user to repeat the table indices when you input a new element, such as repeating 65 for both (65, 23) and (65, 54). You can simply ask the user to input the column and row indices once and we'll construct the individual table coordinates later. For the data, have the user input it all at once like reading lines in a book, i.e., line by line from left to right. For all inputs, the user should separate individual members with a space. For example, when inputting the column indices, he should write

23 54 34 75

and for the data

AM h 9 C 56 in 13 ok

Once we have the data in a 1D list, we can put them in an array and reshape it to 2D with the specified number of columns per row.

The structure of the class makes a few assumptions required for functionality, which are implicit from your question.

  • All row/column labels are 2-digit integers (in string format for convenience).
  • There are no two or more table/row/column elements with the same name, since list.index() or numpy.where() might not behave as you expect them to in that case. This assumption makes sense since the use of your table seems to be for encryption/decryption and as such, each element should uniquely map to another.
  • When searching for the indices of a sequence of elements, it assumes no element in the table is a prefix of another one, i.e., '9' and '97'.

Usage

Once you have constructed your table, you can view the data (don't edit the array directly!),

>>> table.data
array([['AM', 'h', '9', 'C'],
       ['56', 'in', '13', 'ok']], dtype=object)

access a specific element,

>>> table['7834']
'13'

set a new value for an element,

>>> table['7834']  = 'B'
>>> table['7834']
'B'

find where an element resides,

>>> table.where('9')   # this should work equally well for '9C'
'6534'

or permanently transpose the array.

>>> table.transpose()
>>> table.where('9')
'3465'

Finally, you can add more methods to this class as they serve your needs, e.g. adding/deleting a whole row of elements after the table has been created.

Community
  • 1
  • 1
Reti43
  • 9,656
  • 3
  • 28
  • 44
  • This would work perfectly for me if I didn't need it to be a real table which could be transposed later. Could that be done easily? –  Nov 26 '15 at 11:05
  • @shomz I have completely changed my answer to suit your needs better. – Reti43 Nov 26 '15 at 14:45
  • There are simple ways of making `self.data`, `self.rows` and `self.columns` read-only for an outsider while writeable internally within the class. However, that would expand the code and would be unnecessary unless you expect your users to be nosy and possibly corrupt their own data. – Reti43 Nov 26 '15 at 17:13
  • Thank you for such an elaborate answer –  Nov 26 '15 at 18:05
  • I tried to make this work with a menu in which the user can choose to either form the table, encrypt, decrypt, change the value of the heading of a row or column, or transpose, but I just can't get it to work.. Any chance you could help me out? It may be worth mentioning that the user can do more than one thing (for example, he could form the table, transpose, and then decrypt something) –  Nov 26 '15 at 22:46
  • @shomz You should create a new question about that since the menu code and the functionality the user has sound like significant additions to my provided suggestion and it wouldn't make sense to address them here. – Reti43 Nov 26 '15 at 22:51
  • I'm afraid it would be too large.. If you could answer me here I would really appreciate it. –  Nov 26 '15 at 22:56
  • @shomz Comments should be reserved for further clarifications, e.g. "why does your solution work for integers but not for floats?". For substantial code modifications/additions, you get a new beast, which deserves its own question. If you think your code is too large, consider testing it so you can localise where the error might originate and only post [the necessary components required to replicate it](http://stackoverflow.com/help/mcve). If it were still too large for a question, it would also be unfit for the comments section, which is more limited. – Reti43 Nov 26 '15 at 23:08