Эффективное переупорядочение координатных пар (2 кортежа) в списке пар в Python - PullRequest
2 голосов
/ 28 января 2010

Я хочу сжать список объектов с новым объектом, чтобы сгенерировать список координат (2 кортежа), но я хочу заверить, что для (i, j) i

Однако я не очень доволен моими текущими решениями:

from itertools import repeat

mems = range(1, 10, 2) 
mem = 8

def ij(i, j):
  if i < j:
    return (i, j)
  else:
    return (j, i)

def zipij(m=mem, ms=mems, f=ij):
  return map(lambda i: f(i, m), ms)

def zipij2(m=mem, ms=mems):
  return map(lambda i: tuple(sorted([i, m])), ms)

def zipij3(m=mem, ms=mems):
  return [tuple(sorted([i, m])) for i in ms]

def zipij4(m=mem, ms=mems):
  mems = zip(ms, repeat(m))
  half1 = [(i, j) for i, j in mems if i < j]
  half2 = [(j, i) for i, j in mems[len(half1):]]

  return half1 + half2

def zipij5(m=mem, ms=mems):
  mems = zip(ms, repeat(m))
  return [(i, j) for i, j in mems if i < j] + [(j, i) for i, j in mems if i > j]

Выход для выше:

>>> print zipij() # or zipij{2-5}  
[(1, 8), (3, 8), (5, 8), (7, 8), (8, 9)]

Вместо обычно:

>>> print zip(mems, repeat(mem))
[(1, 8), (3, 8), (5, 8), (7, 8), (9, 8)]

Время: отрублено (больше не актуально, смотрите ответы на гораздо более быстрые результаты ниже)

Для len(mems) == 5 нет реальной проблемы с каким-либо решением, но для zipij5 (), например, понимание второго списка без необходимости возвращается к первым четырем значениям, когда i > j уже было оценено как True для тех, кто в первом понимании.

Для моих целей я уверен, что len(mems) никогда не превысит ~ 10000, если это поможет сформировать ответы на вопрос, какое решение лучше. Чтобы немного объяснить мой вариант использования (я нахожу это интересным), я буду хранить разреженную, верхнетреугольную матрицу подобия, и поэтому мне нужна координата (i, j), чтобы не дублироваться в (j, i). Я говорю своего рода , потому что я буду использовать новый объект Counter() в 2.7 для выполнения квазиматричной матрицы и матричного вектора-сложения. Затем я просто передаю counter_obj.update() список из двух кортежей, и он увеличивает эти координаты, сколько раз они встречаются. Редкие матрицы SciPy работали примерно в 50 раз медленнее, к моему ужасу, для моих случаев использования ... поэтому я быстро отказался от них.

Так или иначе, я был удивлен моими результатами ... Первые методы, которые я придумал, были zipij4 и zipij5, и все же они все еще самые быстрые, несмотря на создание нормального zip() и затем генерацию новый почтовый индекс после изменения значений. Я все еще довольно новичок в Python, условно говоря (Алекс Мартелли, вы меня слышите?), Поэтому вот мои наивные выводы:

  • tuple(sorted([i, j])) очень дорого (почему?)
  • map(lambda ...), кажется, всегда делает хуже, чем список компа (я думаю, что я прочитал это, и это имеет смысл)
  • Почему-то zipij5() не намного медленнее, несмотря на то, что дважды просматривал список, чтобы проверить неравенство i-j. (Почему это так?)

И, наконец, я хотел бы знать, какой из них считается наиболее эффективным ... или есть ли другие быстрые и недорогие способы, о которых я еще не думал. Спасибо.


Текущие лучшие решения

## Most BRIEF, Quickest with UNSORTED input list:
## truppo's
def zipij9(m=mem, ms=mems):
  return [(i, m) if i < m else (m, i) for i in ms]

## Quickest with pre-SORTED input list:
## Michal's
def zipij10(m=mem, ms=mems):
  i = binsearch(m, ms)  ## See Michal's answer for binsearch()
  return zip(ms[:i], repeat(m)) + zip(repeat(m), ms[i:])

Задержка

# Michal's  
Presorted - 410µs per loop
Unsorted - 2.09ms per loop  ## Due solely to the expensive sorted()

# truppo's
Presorted - 880µs per loop
Unsorted - 896µs per loop  ## No sorted() needed

Время использовалось mems = range(1, 10000, 2), длина которого всего ~ 5000. sorted() вероятно станет хуже при более высоких значениях и списках, которые более перемешаны. random.shuffle() был использован для "несортированных" таймингов.

Ответы [ 3 ]

2 голосов
/ 28 января 2010

Текущая версия:

(Самый быстрый на момент публикации с Python 2.6.4 на моей машине.)

Обновление 3: так как мы делаем все возможное, давайте выполним бинарный поиск - способом, который не требует введения m в mems:

def binsearch(x, lst):
    low, high = -1, len(lst)
    while low < high:                                                           
        i = (high - low) // 2
        if i > 0:
            i += low
            if lst[i] < x:
                low = i
            else:
                high = i
        else:
            i = high
            high = low
    return i

def zipij(m=mem, ms=mems):
    i = binsearch(m, ms)
    return zip(ms[:i], repeat(m)) + zip(repeat(m), ms[i:])

Это работает в 828 мкс = 0,828 мс на моей машине против текущего решения ОП 1,14 мс. Предполагается, что входной список отсортирован (и, конечно, обычный тест).

Эта реализация двоичного поиска возвращает индекс первого элемента в данном списке, который не меньше, чем искомый объект. Таким образом, нет необходимости вводить m в mems и сортировать все целиком (как в текущем решении ОП с .index(m)) или шаг за шагом проходить по началу списка (как я делал ранее), чтобы найти смещение, на которое оно должно быть разделено.


Предыдущие попытки:

Как насчет этого? (Предлагаемое решение рядом с In [25] ниже, 2,42 мс до 3,13 мс zipij5.)

In [24]: timeit zipij5(m = mem, ms = mems)
100 loops, best of 3: 3.13 ms per loop

In [25]: timeit [(i, j) if i < j else (j, i) for (i, j) in zip(mems, repeat(mem))]
100 loops, best of 3: 2.42 ms per loop

In [27]: [(i, j) if i < j else (j, i) for (i, j) in zip(mems, repeat(mem))] == zipij5(m=mem, ms=mems)
Out[27]: True

Обновление: похоже, что оно примерно так же быстро, как и самоотчет ОП. Тем не менее, кажется более простым.

Обновление 2: реализация предложенного OP упрощенного решения:

def zipij(m=mem, ms=mems):
    split_at = 0
    for item in ms:
        if item < m:
            split_at += 1
        else:
            break
    return [(item, m) for item in mems[:split_at]] + [(m, item) for item in mems[split_at:]]

In [54]: timeit zipij()
1000 loops, best of 3: 1.15 ms per loop

Кроме того, решение Truppo запускается на моей машине за 1,36 мс. Я предполагаю, что вышеупомянутое самое быстрое до сих пор Примечание вам нужно отсортировать mems перед передачей их в эту функцию ! Если вы генерируете его с range, он, конечно, уже отсортирован.

1 голос
/ 28 января 2010

Почему бы просто не встроить вашу функцию ij ()?

def zipij(m=mem, ms=mems):
  return [(i, m) if i < m else (m, i) for i in ms]

(на моем компьютере это выполняется за 0,64 мс вместо 2,12 мс)

Некоторые тесты:

zipit.py:

from itertools import repeat

mems = range(1, 50000, 2)
mem = 8

def zipij7(m=mem, ms=mems):
  cpy = sorted(ms + [m])
  loc = cpy.index(m)

  return zip(ms[:(loc)], repeat(m)) + zip(repeat(m), ms[(loc):])

def zipinline(m=mem, ms=mems):
  return [(i, m) if i < m else (m, i) for i in ms]

Сортировка:

>python -m timeit -s "import zipit" "zipit.zipinline()"
100 loops, best of 3: 4.44 msec per loop

>python -m timeit -s "import zipit" "zipit.zipij7()"
100 loops, best of 3: 4.8 msec per loop

Unsorted:

>python -m timeit -s "import zipit, random; random.shuffle(zipit.mems)" "zipit.zipinline()"
100 loops, best of 3: 4.65 msec per loop

p>python -m timeit -s "import zipit, random; random.shuffle(zipit.mems)" "zipit.zipij7()"
100 loops, best of 3: 17.1 msec per loop
0 голосов
/ 28 января 2010

Самая последняя версия:

def zipij7(m=mem, ms=mems):
  cpy = sorted(ms + [m])
  loc = cpy.index(m)

  return zip(ms[:(loc)], repeat(m)) + zip(repeat(m), ms[(loc):])

Скамьи для меня немного быстрее, чем у трупо, медленнее на 30%, чем у Михала.(Изучая это сейчас)


Возможно, я нашел свой ответ (пока).Кажется, я забыл сделать компиляторную версию списка для `zipij ()` `:

def zipij1(m=mem, ms=mems, f=ij):
  return [f(i, m) for i in ms]

Она все еще опирается на мою глупую вспомогательную функцию ij(), поэтому она не выигрывает награду за краткостьконечно, но время улучшилось:

# 10000
1.27s
# 50000
6.74s

Так что теперь он мой «победитель», и ему также не нужно генерировать более одного списка или использовать много вызовов функций, кроме ij() помощник, поэтому я считаю, что это также будет наиболее эффективным.

Тем не менее, я думаю, что это все еще можно улучшить ... Я думаю, что выполнение N ij() вызовов функций (где N - длинасписок результатов) не требуется:

  • Найти, по какому индексу mem будет соответствовать mems при заказе
  • Разделить мемы по этому индексу на две части
  • Do zip(part1, repeat(mem))
  • Добавить zip(repeat(mem), part2) к этому

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...