Consider a list containing a number of items, each containing information about the same N properties. Each of these properties can take on a certain limited (but perhaps unknown) number of discrete values. The list is not necessarily ordered.
I would like to sort these items into an N-dimensional array such that each property varies only along its own axis, i.e. is constant within any slice along that axis.
Simplified Example:
# Three properties
prop_1 = ['01','02','03','07']
prop_2 = ['foo','bar','baz']
prop_3 = ['yellow','red']
from itertools import product
# Consider this as the input
string_list = ['_'.join(s) for s in product(prop_1, prop_2, prop_3)]
# HOWEVER...
from random import shuffle
# Inputs may be unsorted
shuffle(string_list)
I now want to organize string_list into an array of shape (4,3,2) such that the first property varies along the first axis, & so on. That is, the expected output would be:
array([[['01_foo_yellow','01_foo_red'],
['01_bar_yellow','01_bar_red'],
['01_baz_yellow','01_baz_red']],
[['02_foo_yellow','02_foo_red'],
['02_bar_yellow','02_bar_red'],
['02_baz_yellow','02_baz_red']],
[['03_foo_yellow','03_foo_red'],
['03_bar_yellow','03_bar_red'],
['03_baz_yellow','03_baz_red']],
[['07_foo_yellow','07_foo_red'],
['07_bar_yellow','07_bar_red'],
['07_baz_yellow','07_baz_red']]])
Each property is then constant within any slice along its own axis, i.e.:
A[3,...] # All strings containing '07' as property 1
A[:,1,:] # All strings containing 'bar' as property 2
A[...,0] # All strings containing 'yellow' as property 3
The method should be robust even when there are missing items. For example, if we remove '02_bar_yellow' and '03_baz_red' from the inputs, the shape of the output array should remain unchanged, with None where these entries would otherwise have been sorted:
array([[['01_foo_yellow','01_foo_red'],
['01_bar_yellow','01_bar_red'],
['01_baz_yellow','01_baz_red']],
[['02_foo_yellow','02_foo_red'],
[ None, '02_bar_red'],
['02_baz_yellow','02_baz_red']],
[['03_foo_yellow','03_foo_red'],
['03_bar_yellow','03_bar_red'],
['03_baz_yellow', None. ]],
[['07_foo_yellow','07_foo_red'],
['07_bar_yellow','07_bar_red'],
['07_baz_yellow','07_baz_red']]])
The Problem:
While the above illustrates the general idea, I am actually trying to get this to work for a set of regex match objects whose 'properties' are its capturing groups.
import re
pattern = '(\d+)_(\w+)_(\w+)'
regex = re.compile(pattern)
# Consider this as the input
matches = [re.match(s) for s in string_list]
I then want to sort according to the values given by the group() method of each match object.
Although it does not quite approach a solution, I can get the items sorted by slice along an axis using itertools.groupby():
# Sort by the first capturing group
groupings = itertools.groupby(matches, key=lambda m: m.groups()[0])
grouped_strings = [[m.string for m in g] for n,g in groupings]
So the contents of grouped_strings[3] are the same as the slice A[3,...] given in the first example. However, these entries are given as a flattened array.
It occurs to me that I should be able to use itertools.groupby iteratively to achieve the proper sorting, but I can't quite figure it out. At the same time, I wonder if there is a simpler or more 'pythonic' way to achieve this.