Python Поиск и удаление пар значений из списка
Есть список чисел в датафрейме. Из этого списка нужно найти пары чисел a и b, такие чтобы a == -b. Если такие пары есть их нужно дропнуть.
Вопрос собственно в том, как это сделать максимально эффективно?
Условия
a == -b- Необходимо найти и удалить все пары.
- Числа удаляются только папарно.
- Числа корорые уже входят в пару не могут быть частью другой пары. Т.е. для числа
снужно искать другоеdдаже еслис == -bилис == -a - Все равно что происходит с нулями
0 == -0. Можно удалить или оставить, главное чтобы работало быстрее. - Не обязательно, но было бы лучше, если бы растояние по индексу, между парой, было минимальным.
- Порядок чисел не должен поменятся после удаления. Нужно оставить исходные индексы датафрейма.
Текущая реализация
def drop_paired(data:pd.DataFrame, target_column:str):
result = data.copy()
result['index'] = result.index
# Сортируем значения без учета знака
result = result.sort_values([target_column, 'index'], key=abs)
result['drop'] = False
value_a = index_a = np.nan
i = start_i = 0
while i < len(result):
row = result.iloc[i]
# Проверяем не использовано это число в качестве какой либо пары
if(~row['drop']):
# Если значение и индекс первого числа в паре (a) не определены, задаем новые
if(index_a is np.nan):
value_a = row[target_column]
index_a = row['index']
start_i = i
# Если нашли b == -a, ставим пометку на удаление и опустошаем значение и индекс первого числа в паре
elif(row[target_column] == -value_a):
result.loc[row['index'], 'drop'] = True
result.loc[index_a, 'drop'] = True
value_a = index_a = np.nan
# Возвращаемся назад для поисков другой пары среди этих же значений
i = start_i
continue
# Если есть а с более "старшим" индексом, возьмём его, чтобы расстояние между a и b было минимальным
elif(row[target_column] == value_a):
index_a = row['index']
# Если числа не равны по модулю, задаем новые
else:
value_a = row[target_column]
index_a = row['index']
start_i = i
i += 1
result = result[~result['drop']].sort_index()
return result[list(data)]
Результат работы
np.random.seed(0)
df = pd.DataFrame(np.random.randint(-6, 11, 20))
print("До дропа")
display(df.value_counts())
display(df)
print("\n--------------\nПосле дропа")
display(drop_paired(df, 0).value_counts())
drop_paired(df, 0)
Итого
Алгоритм работает, но меня смущает гавнокодовость данного решения. Как сделать лучше?
Ответы (3 шт):
Автор решения: GrAnd
→ Ссылка
Ну, для обычного списка я не вижу тут чего-то сложного.
data = [6,9,-6,-3,-3,1,3,-2,0,6,-5,0,1,8,-1,7,2,3,10,-1]
i = 0
while i < len(data):
try:
i_pair = data.index(-data[i], i+1) # ищем антипода для текущего элемента (начиная со следующего)
del data[i_pair] # удаляем антипода
del data[i] # удаляем сам элемент
except ValueError : # если антипода не нашли
i += 1 # переходим к следующему элементу
print(data)
>>> [9, 6, -5, 8, 7, 10]
Автор решения: Limbend
→ Ссылка
Посоветовали в другом месте следующее
def drop_paired2(data:pd.DataFrame, by:str):
sort_values = data[by].sort_values()
abs_values = sort_values.abs()
c1 = sort_values.groupby(abs_values).transform(pd.Series.cumsum) > 0
c2 = sort_values[::-1].groupby(abs_values).transform(pd.Series.cumsum) < 0
return data[c1 | c2]
Автор решения: SergFSM
→ Ссылка
решение через рекурсивную функцию:
data = [6,9,-6,-3,-3,1,3,-2,0,6,-5,0,1,8,-1,7,2,3,10,-1]
def f(x):
if not x: return []
if -x[0] not in x[1:]: return [x.pop(0)] + f(x)
del x[x.index(-x.pop(0))]
return f(x)
f(data) # [9, 6, -5, 8, 7, 10]
