Округлите каждое число списка до ближайшего числа в другом списке

Предположим, у меня есть некий список x с номерами и еще один список y с другими номерами. Элементы y должны быть элементами x, но из-за шума в измерениях они несколько отличаются. Я хочу найти для каждого значения y значение x, ближайшее к нему.

Я могу сделать это с помощью нескольких циклов и проверить для каждого элемента y[i], какой элемент x[j] минимизирует abs(x[j]-y[i]), но я почти уверен, что есть гораздо более простой и чистый способ сделать это. Списки могут быть огромными, поэтому я ищу здесь эффективный код.

Код, который я написал до сих пор:

x_in = [1.1, 2.2, 3, 4, 6.2]
y_in = [0.9, 2, 1.9, 6, 5, 6, 6.2, 0.5, 0, 3.1]
desired_output = [1.1, 2.2, 2.2, 6.2, 4, 6.2, 6.2, 1.1, 1.1, 3]

y_out = []

for y in y_in:
    aux = [abs(l - y) for l in x_in]
    mn,idx = min( (aux[i],i) for i in range(len(aux)) )
    y_out.append(x_in[idx])

>>> y_out == desired_output
True

Но я не знаю, есть ли более эффективный способ сделать это...

РЕДАКТИРОВАТЬ:

Из-за моего невежества я забыл прояснить кое-что, что может иметь отношение к комментариям, которые я получил.

  • Список x отсортирован.
  • x — единственный список, который может иметь довольно большой размер: обычно от 500 000 до 1 000 000 элементов. y в целом будет очень маленьким, менее 10 элементов.

person Tendero    schedule 18.07.2018    source источник
comment
Как долго x и y? Циклы и проверка будут полиномиальной сложности, что не очень хорошо. Если производительность важна, вы, вероятно, могли бы улучшить ее с помощью дерева интервалов.   -  person wim    schedule 18.07.2018
comment
Прямой подход состоял бы в том, чтобы отсортировать оба массива, затем пройтись по x, пока вы не найдете элемент e больше, чем текущий элемент в y, а затем выбрать более близкий из двух (e или элемент, который следует за ним). Продолжайте с этой позиции в x, пока не будут обработаны все y, что-то вроде сортировки слиянием.   -  person Dillon Davis    schedule 19.07.2018
comment
@user3483203 user3483203 Я добавил свою попытку к вопросу.   -  person Tendero    schedule 19.07.2018
comment
Насколько огромен огромный? Я ожидаю, что интервальное дерево wim будет масштабироваться лучше всего, но это требует много настроек.   -  person Useless    schedule 19.07.2018
comment
Когда вы говорите списки могут быть огромными, вы имеете в виду длину X, Y или обе? В любом случае, два списка — это неправильная структура данных для вставки. Вместо этого используйте два дерева (или кучу). Тогда обе структуры будут отсортированы по умолчанию и смогут тривиально легко найти своих (предшественника и преемника) соседей. Остальное тривиально.   -  person smci    schedule 19.07.2018
comment
Если вам действительно нужно масштабируемое решение с большой оценкой, укажите это в вопросе. Я пометил это big-o. Назовем X, Y длинами (/размерами) структур. Что в целом вы ожидаете доминировать: X или Y? (Предположительно Y, так как есть шум измерения)   -  person smci    schedule 19.07.2018
comment
имеют ли значение заказы x, y?   -  person Azat Ibrakov    schedule 19.07.2018
comment
@Useless Я добавил в вопрос нужную информацию. Извините, что не сказал об этом раньше, я не знал, что это будет актуально (мой плохой!).   -  person Tendero    schedule 19.07.2018
comment
@smci Только X. Я отредактировал вопрос!   -  person Tendero    schedule 19.07.2018
comment
Знаете ли вы значение x до того, как начнете измерять y?   -  person smci    schedule 19.07.2018
comment
@smci Да, когда приходит одно значение y, весь список x определяется заранее.   -  person Tendero    schedule 19.07.2018


Ответы (6)


Учитывая, что x отсортировано, наиболее эффективный способ сделать это — использовать bisect для поиска ближайшего значения. Просто создайте список средних точек между значениями x и запустите их пополам:

In [69]: mid_points = [(x1+x2)/2 for x1, x2 in zip(x[1:], x[:-1])]

In [70]: mid_points
Out[70]: [1.5, 2.5, 3.5, 4.5]

In [72]: [x[bisect.bisect(mid_points, v)] for v in y]
Out[72]: [1, 1, 4, 5, 2]

Это будет работать за O(Mlog(N)+N) времени, где `M=len(y), N=len(x)

(Для python2 выполните from __future__ import division или используйте float(x1+x2)/2 в расчете mid_points)

person kuppern87    schedule 18.07.2018
comment
Это действительно остроумно, но я только что попробовал и не получил желаемого результата для примера в вопросе (второй). Последний элемент должен быть 3, а ваш скрипт возвращает 4. - person Tendero; 19.07.2018
comment
Я нашел ошибку. Из-за того, что x имеет два целых числа, когда вы выполняете (3+4)/2, вы получаете 3, а не 3.5. Если вы выполните преобразование, вы получите желаемый результат, и ваш код явно превосходит мой и остальные в других ответах. Спасибо. - person Tendero; 19.07.2018
comment
@Tendero Думаю, вы используете Python 2. Вы можете использовать from __future__ import division, чтобы избежать этого. - person wim; 19.07.2018
comment
Это будет медленнее, чем 1x по списку, что возможно - person dawg; 19.07.2018

Вы можете сделать это быстро с помощью лямбда-функции и понимания списка:

[min(x, key=lambda x:abs(x-a)) for a in y]

Это будет работать с числами с плавающей запятой, целыми числами и т. д.

person dpwilson    schedule 18.07.2018
comment
Я понятия не имею. Любая конструктивная критика, пожалуйста? - person dpwilson; 19.07.2018
comment
Это то же самое, что уже было у ОП, поэтому бесполезно. - person wim; 19.07.2018
comment
Чистый, читаемый ответ с реальным кодом — это не то же самое, что его описание некоторых циклов. - person dpwilson; 19.07.2018
comment
Вопрос явно требует большей эффективности, и этот ответ обеспечивает ту же сложность. Более короткий код хорош, но несколько упускает из виду IMO. - person wim; 19.07.2018
comment
Здесь две вещи: 1) Эффективность никогда не упоминалась в исходном тексте OP. Проверьте историю редактирования. 2) Проще и чище были два явных запроса. - person dpwilson; 19.07.2018
comment
Хорошо, это справедливо. Но теперь вопрос был отредактирован для уточнения, возможно, вы могли бы отредактировать (или удалить) ответ. - person wim; 19.07.2018

Так что это то, что я быстро придумал, что просто получает все различия и сортирует их от меньшего к большему. Берет наименьшую разницу и идет оттуда.

x = [1, 2, 3, 4, 5]
y = [1.1, 1.2, 3.6, 6.2, 2.1]

for y_index in range(len(y)):
    value_and_index= {}
    for x_index in range(len(x)):
        difference= y[y_index]-x[x_index]
        difference= difference*-1 if difference<0 else difference
        value_and_index[difference]= x_index
    y[y_index]= x[value_and_index[sorted(value_and_index.keys())[0]]]

print y # [1, 1, 4, 5, 2]

Надеюсь, это поможет, удачного кодирования!

person wowwee    schedule 18.07.2018

Моя попытка:

Сначала я сортирую массив X (если он еще не отсортирован). Цикл проходит через каждый y и вычисляет абсолютное значение для каждого x, пока это абсолютное значение не станет выше предыдущего, а затем останавливает цикл for (поскольку массив X отсортирован):

x = sorted([1, 2, 3, 4, 5])
y = [1.1, 1.2, 3.6, 6.2, 2.1]

out = []
while y:
    current_value = y.pop()
    current_min = float('inf')
    current_x_value = None
    for v in x:
        temp_min = abs(current_value - v)
        if temp_min < current_min:
            current_min = temp_min
            current_x_value = v
        if temp_min > current_min:  # no need to iterate further, X is sorted
            break
    out.insert(0, current_x_value)
print(out)

Выходы:

[1, 1, 4, 5, 2]
person Andrej Kesely    schedule 18.07.2018
comment
Лучше отсортировать оба массива и пройтись по ним с помощью двух движущихся итераторов. - person wim; 19.07.2018

Со следующими предположениями:

  • порядок результатов не имеет значения,

  • мы используем Python 3.3+.

довольно простое решение может выглядеть

from itertools import repeat


def evaluate(expected_values, measurements):
    if not expected_values:
        raise ValueError('Expected values should be a non-empty sequence.')
    expected_values = sorted(expected_values)
    measurements = sorted(measurements)
    expected_iter = iter(expected_values)
    left_value = next(expected_iter)
    try:
        right_value = next(expected_iter)
    except StopIteration:
        # there is only one expected value
        yield from repeat(left_value,
                          len(measurements))
        return
    for evaluated_count, measurement in enumerate(measurements):
        while measurement > right_value:
            try:
                left_value, right_value = right_value, next(expected_iter)
            except StopIteration:
                # rest of the measurements are closer to max expected value
                yield from repeat(right_value,
                                  len(measurements) - evaluated_count)
                return

        def key(expected_value):
            return abs(expected_value - measurement)

        yield min([left_value, right_value],
                  key=key)

Для Python3.3- мы можем заменить

yield from repeat(object_, times)

с for-петлей вроде

for _ in range(times):
    yield object_

Тест

>>> x_in = [1.1, 2.2, 3, 4, 6.2]
>>> y_in = [0.9, 2, 1.9, 6, 5, 6, 6.2, 0.5, 0, 3.1, 7.6, 10.4]
>>> y_out = list(evaluate(x_in, y_in))
>>> y_out
[1.1, 1.1, 1.1, 2.2, 2.2, 3, 4, 6.2, 6.2, 6.2, 6.2, 6.2]
person Azat Ibrakov    schedule 18.07.2018

Если x отсортировано, используйте bisect:

import bisect 
test_out=[]
max_x=max(x)
min_x=min(x)
for f in y:
    if f>=max_x:
        idx=-1
    elif f<=min_x:
        idx=0
    else:
        idx=bisect.bisect_left(x,f)
        if abs(x[idx-1]-f)<abs(x[idx]-f):
            idx-=1
    test_out.append(x[idx])

>>> test_out==desired_output
True
person dawg    schedule 18.07.2018
comment
idx=bisect.bisect_left(x,f) может возвращать 0, а затем индексация следующей строки непреднамеренно зацикливается. - person wim; 19.07.2018
comment
Этот случай обрабатывается f<min_x выше, а дубликаты слева, я думаю, нет? - person dawg; 19.07.2018
comment
Неа. Должно быть f<=min_x. - person wim; 19.07.2018
comment
Хм. Когда я прочитал bisect.left, он указывает all(val < x for val in a[lo:i]) вместо bisect.left и all(val <= x for val in a[lo:i]) для bisect.right (или bisect.bisect - то же самое). Я полагаю, что elif f<=min_x: исправляет, нет? Спасибо за внимание! - person dawg; 19.07.2018
comment
Проще говоря, bisection возвращает индекс места для вставки нового элемента. В случае ничьей bisect_left вставит его слева от равного элемента, а bisect_right справа. f<=min_x предотвращает возможность возникновения ничьей по индексу 0, так что да, это исправляет. - person wim; 19.07.2018