Сортировка элементов в массив по свойству - PullRequest
2 голосов
/ 12 июля 2020

Рассмотрим список, содержащий несколько элементов, каждый из которых содержит информацию об одних и тех же N свойствах. Каждое из этих свойств может принимать определенное ограниченное (но, возможно, неизвестное) количество дискретных значений. Список не обязательно упорядочен.

Я хотел бы отсортировать эти элементы в N -мерный массив, чтобы каждое свойство изменялось только вдоль своей оси, то есть было постоянным в пределах любого фрагмента вдоль этой оси.

Упрощенный пример:

# 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)

Теперь я хочу организовать string_list в массив формы (4,3,2) так, чтобы первое свойство изменялось вдоль первой оси, & скоро. Таким образом, ожидаемый результат будет следующим:

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']]])

Каждое свойство будет постоянным в любом срезе вдоль своей оси, то есть:

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

Метод должен быть устойчивым, даже если есть отсутствуют предметы. Например, если мы удалим '02_bar_yellow' и '03_baz_red' из входов, форма выходного массива должна остаться неизменной, с None, где эти записи в противном случае были бы отсортированы:

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']]])

Проблема:

Хотя приведенное выше иллюстрирует общую идею, я на самом деле пытаюсь заставить это работать для набора match объектов регулярных выражений, «свойствами» которых являются его группы захвата.

import re

pattern = '(\d+)_(\w+)_(\w+)'
regex = re.compile(pattern)

# Consider this as the input
matches = [re.match(s) for s in string_list]

Затем я хочу отсортировать в соответствии со значениями, заданными методом group() каждого объекта match.

Хотя это не совсем подходит к решению, я могу получить элементы, отсортированные по фрагментам вдоль оси с использованием 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]

Таким образом, содержимое grouped_strings[3] совпадает с содержимым фрагмента A[3,...], приведенным в первом примере. Однако эти записи представлены в виде уплощенного массива.

Мне приходит в голову, что я должен иметь возможность использовать itertools.groupby итеративно для достижения правильной сортировки, но я не могу понять это. В то же время мне интересно, есть ли более простой или более «pythoni c» способ добиться этого.

Ответы [ 2 ]

2 голосов
/ 12 июля 2020

Если вы изначально не знаете длину осей, вам необходимо получить их, например, используя set или np.unique, которые также отсортируют результат и вычислит для вас обратный индекс.

Затем вы можете создать логическую маску, указывающую, какие элементы были найдены, а какие нет. Сопоставить индексы масок с отсортированными массивами меток тривиально.

Допустим, у вас есть три интересных совпадения, как в примере:

# Findall is the lazy shortcut that assumes only one match
matches = np.array([regex.findall(s) for s in string_list])
p1, i1 = np.unique(matches[:, 0], return_inverse=True)
p2, i2 = np.unique(matches[:, 1], return_inverse=True)
p3, i3 = np.unique(matches[:, 2], return_inverse=True)
mask = np.zeros((p1.size, p2.size, p3.size), dtype=bool)
mask[i1, i2, i3] = True

p* - метки оси вашего массива. Поскольку np.unique основан на сортировке, они будут уникальными и отсортированными.

i* - это индексы, отображающие элементы string_list на каждую ось. Это означает, что причудливый индекс i1, i2, i3 указывает места в логической матрице, соответствующие найденному совпадению.

Это можно легко обобщить с помощью al oop на произвольное количество групп захвата.

# note the transpose
matches = np.array([regex.findall(s) for s in string_list]).T
# must be list because ragged
axes = [np.unique(x, return_inverse=True) for x in matches]
mask = np.zeros(tuple(x[0].size for x in axes), dtype=bool)
mask[tuple(x[1] for x in axes)] = True
1 голос
/ 12 июля 2020

Вы можете попробовать с np.reshape с начальной строкой:

import numpy as np
output=np.array(string_list).reshape(4,3,2) 

print(output)

Вывод:

[[['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']]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...