#algorithm #sorting #search #data-structures
#алгоритм #сортировка #Поиск #структуры данных
Вопрос:
Реальная проблема: у меня есть API, который возвращает не более 5000 элементов. В любой момент времени я могу видеть, сколько элементов существует, путем фильтрации по ценовому диапазону. Я также могу найти самый дорогой элемент.Мне нужно разбить эти элементы на ценовые диапазоны размером 5000 (размер узла), чтобы я мог собрать их все.
Практический пример ниже.
Заполнить список.
price_list = []
for i in range(0, 100000):
price_list.append(random.uniform(0, 100000))
Реализовать функцию поиска.
def get_prange_count(startp, endp):
return len([r for r in price_list if r <= endp and r >= startp])
Используя функцию get_prange_count, разбейте все элементы в price_list на узлы не более 5000.
например
[{"startp": 0, "endp": 3.5, "item_count": 5000},
{"startp": 3.51, "endp": 4.03, "item_count": 5000}]
Я понимаю, что это проблема алгоритма, однако я не смог найти идеальный вариант. Я заглянул в двоичный поиск, но я не ищу позиции, я изучил венгерский алгоритм, но, похоже, это не проблема с назначением.
Заранее спасибо за любые рекомендации,
Редактировать:
5000 -> 2 for demonstration purposes.
price_list = [0.1, 0.2, 0.4, 1.3, 1.6, 5.0]
node1 = {"startp": 0, "endp": 0.2, "item_count": 2}
node2 = {"startp": 0.4, "endp": 1.3, "item_count": 2}
node3 = {"startp": 1.6, "endp": 5.0, "item_count": 2}
Комментарии:
1. Что, если существует более 5000 элементов с одинаковой ценой?
2. Есть ли у вас функция API для получения всех товаров в определенном ценовом диапазоне или вы можете только их посчитать?
3. У меня есть функция для получения всех товаров в определенном ценовом диапазоне. Маловероятно, что вы найдете более 5000 товаров с одинаковой ценой, потому что я уже разбиваю товары по категориям и, если нужно, по цвету.
4. Можем ли мы предположить, что минимальная цена равна 0?
5. Да, мы можем предположить, что минимальная цена равна 0.
Ответ №1:
Это решение на Python использует только функции get_prange_count()
и get_max_price()
, как вы заявили, для поиска диапазонов цен по 5 тыс. элементов каждый. Ну, мне также нужна минимальная дельта-цена между двумя ценами, которые не совпадают — я предполагаю, что это единица, но 0,001 можно использовать для центов.
Количество элементов отклоняется от 5000 из-за нескольких элементов с одинаковой ценой вокруг границ ячеек и последней ячейки, которая остается, поскольку я не обязательно генерирую общие цены, кратные 5 тыс.
import random
price_list_size = random.choice(range(90_000, 100_000))
price_list = random.choices(range(100_000), k=price_list_size)
delta_price = 1 # Minimum difference between any two different prices.
def get_prange_count(startp, endp):
return len([r for r in price_list if startp <= r <= endp])
def get_max_price():
return max(price_list)
def get_5k(mn=0, mx=get_max_price(), num=5_000):
count = get_prange_count(mn, mx)
delta_mx = (mx - mn) / 2
while count != num and delta_mx >= delta_price / 2:
mx = -delta_mx if count > num else delta_mx
count, delta_mx = get_prange_count(mn, mx), delta_mx / 2
return mx, count
def get_all_5k(mn=0, mx=get_max_price(), num=5_000):
partmax, partcount = get_5k(mn, mx, num)
result = [(mn, partmax, partcount)]
while partmax < mx:
partmin = partmax delta_price
partmax, partcount = get_5k(partmin, mx, num)
result.append((partmin, partmax, partcount))
return result
if __name__ == '__main__':
print(f"Using {price_list_size} random prices from 0 to {get_max_price()}")
result = get_all_5k()
print(f"Splits into {len(result)} bins of approx 5000 elements")
for mn, mx, count in result:
print(f" From {mn:8.1f} ... {mx:8.1f} with {count} items.")
Пример вывода:
Using 96625 random prices from 0 to 99997
Splits into 20 bins of approx 5000 elements
From 0.0 ... 5144.3 with 4998 items.
From 5145.3 ... 10266.7 with 5001 items.
From 10267.7 ... 15504.7 with 5000 items.
From 15505.7 ... 20584.7 with 4999 items.
From 20585.7 ... 25701.0 with 4997 items.
From 25702.0 ... 30907.7 with 5000 items.
From 30908.7 ... 36111.7 with 4999 items.
From 36112.7 ... 41304.5 with 5000 items.
From 41305.5 ... 46588.4 with 5000 items.
From 46589.4 ... 51771.6 with 4999 items.
From 51772.6 ... 56861.7 with 5000 items.
From 56862.7 ... 62156.4 with 5001 items.
From 62157.4 ... 67289.8 with 4999 items.
From 67290.8 ... 72393.2 with 5000 items.
From 72394.2 ... 77545.3 with 5000 items.
From 77546.3 ... 82747.2 with 5003 items.
From 82748.2 ... 87995.3 with 5000 items.
From 87996.3 ... 93270.8 with 4999 items.
From 93271.8 ... 98331.3 with 5001 items.
From 98332.3 ... 101660.9 with 1599 items.
Если ваша истинная функция get_prange_count() вызывает медленную базу данных, то это делает много вызовов двоичного поиска для каждого значения максимальной цены ячейки.
ОСТАНОВИТЬ НАЖАТИЕ!
Я перечитал ваш вопрос, и в нем написано не более 5 тыс. Если вы внесете в него следующее изменение get_5k
, двоичный поиск будет таким же, как и раньше, но если он превышает 5 кб и delta_mx
будет достаточно мал для выхода из внешнего цикла while, новый внутренний цикл while уменьшается mx
до count
<= 5 кб.
def get_5k(mn=0, mx=get_max_price(), num=5_000):
count = get_prange_count(mn, mx)
delta_mx = (mx - mn) / 2
while count != num and delta_mx >= delta_price / 2:
mx = -delta_mx if count > num else delta_mx
count, delta_mx = get_prange_count(mn, mx), delta_mx / 2
# Assure count < num
while count > num and delta_mx < delta_price / 2 and mx > mn:
mx -= delta_price
count = get_prange_count(mn, mx)
return mx, count
Пример вывода:
Using 95007 random prices from 0 to 99999
Splits into 19 bins of approx 5000 elements
From 0.0 ... 5236.8 with 5000 items.
From 5237.8 ... 10564.6 with 5000 items.
From 10565.6 ... 15872.4 with 4998 items.
From 15873.4 ... 21146.7 with 5000 items.
From 21147.7 ... 26417.0 with 4999 items.
From 26418.0 ... 31730.9 with 5000 items.
From 31731.9 ... 36962.1 with 5000 items.
From 36963.1 ... 42165.8 with 4998 items.
From 42166.8 ... 47382.0 with 5000 items.
From 47383.0 ... 52662.6 with 5000 items.
From 52663.6 ... 57884.3 with 5000 items.
From 57885.3 ... 63183.5 with 5000 items.
From 63184.5 ... 68374.5 with 4999 items.
From 68375.5 ... 73688.2 with 5000 items.
From 73689.2 ... 78915.4 with 4998 items.
From 78916.4 ... 84297.7 with 5000 items.
From 84298.7 ... 89515.5 with 4999 items.
From 89516.5 ... 94709.1 with 5000 items.
From 94710.1 ... 105287.2 with 4991 items.
ОСТАНОВИТЬ НАЖАТИЕ # 2!!
Дальнейшее тестирование показывает, что предыдущий код удаляет элементы. Следующее не удаляет элементы и остается ниже 5k
def get_5k(mn=0, mx=get_max_price(), num=5_000):
"Mainly binary search for num items between mn and mx, adjusting mx"
count = get_prange_count(mn, mx)
delta_mx = (mx - mn) / 2
while count != num and delta_mx >= delta_price / 2:
mx = -delta_mx if count > num else delta_mx
mx = mx // 1 # Floor
count, delta_mx = get_prange_count(mn, mx), delta_mx / 2
# Assure count < num
# while count > num and delta_mx < delta_price / 2 and mx > mn:
# mx -= delta_price
# count = get_prange_count(mn, mx)
return mx, count
Добавьте следующую проверку в конец:
if __name__ == '__main__':
if len(price_list) != sum(count for mn, mx, count in result):
print("nWhoops! Some items missing:")
p = price_list.copy()
for mn, mx, count in result:
items = _get_prange(mn, mx)
assert len(items) == get_prange_count(mn, mx)
for i in items:
p.remove(i)
p.sort()
print(p)
Вывод теперь выглядит так:
Using 100941 random prices from 0 to 99999
Splits into 21 bins of approx 5000 elements
From 0.0 ... 4918.0 with 4998 items.
From 4919.0 ... 9863.0 with 5000 items.
From 9864.0 ... 14901.0 with 4998 items.
From 14902.0 ... 19708.0 with 4999 items.
From 19709.0 ... 24605.0 with 5000 items.
From 24606.0 ... 29555.0 with 5000 items.
From 29556.0 ... 34539.0 with 5000 items.
From 34540.0 ... 39507.0 with 5000 items.
From 39508.0 ... 44602.0 with 4997 items.
From 44603.0 ... 49462.0 with 5000 items.
From 49463.0 ... 54458.0 with 4999 items.
From 54459.0 ... 59432.0 with 5000 items.
From 59433.0 ... 64315.0 with 5000 items.
From 64316.0 ... 69416.0 with 4998 items.
From 69417.0 ... 74389.0 with 4998 items.
From 74390.0 ... 79253.0 with 4999 items.
From 79254.0 ... 84149.0 with 5000 items.
From 84150.0 ... 89134.0 with 5000 items.
From 89135.0 ... 94059.0 with 5000 items.
From 94060.0 ... 99055.0 with 5000 items.
From 99056.0 ... 100934.0 with 955 items.
Комментарии:
1. Спасибо, это очень сложно.
2. Привет, продолжение. Изучая функцию и экспериментируя некоторое время, можете ли вы объяснить мне, почему в вашей функции get_5k вы предпочитаете count != num вместо count> num? Спасибо в любом случае
3. Привет, Бенони, считай!= num, так как к 5k можно подойти с обеих сторон. Например, количество может быть: 15003, 7456 6143 4867 5167 4987 5001, отскакивая примерно на 5 тыс. по мере приближения. (Хотя я этого не проверял — я думал, что это будет из прошлого опыта и закодировано соответствующим образом. (Я оставляю тщательное тестирование авторам вопросов о переполнении стека и просто проверяю, чтобы получить, как я надеюсь, правильный результат).
4. Спасибо за ваше объяснение, теперь я понял.
Ответ №2:
Поскольку вы можете найти самый дорогой товар, выполните двоичный поиск по ценам от 0 до максимальной цены, чтобы найти самую высокую цену p
, чтобы было меньше 5000 товаров с ценой от 0 до p
, используя get_prange_count
в качестве функции оценки в двоичном поиске. Поиск завершается, поскольку для поиска требуется только конечное число цен (с точностью до 2 цифр).
Например, если get_prange_count(0, max) = 6000
, вычислить c = get_prange_count(0, max/2)
. Если c = 4000
, скажем, продолжить, get_prange_count(max/2, max)
чтобы найти самую высокую цену p
, такую, что get_prange_count(max/2, p) < 6000 - c = 2000
. Таким образом, размер блока меняется и является параметром рекурсивного двоичного поиска.
Затем вы можете продолжить бинарный поиск между текущей ценой p
и максимальной ценой, чтобы получить следующие фрагменты.
Вот некоторый (грубый) псевдокод:
search(startp, endp, threshold) {
if (currency_round(startp) == currency_round(endp)) return startp;
mid = startp (endp - startp) / 2;
count = get_prange_count(startp, mid);
if (count == threshold) return mid;
if (count > threshold)
return search(startp, mid, threshold);
// else count < threshold
return search(mid, endp, threshold - count);
}
Временная сложность — это O(log max)
где max
максимальная цена.
Комментарии:
1. Привет, большое спасибо. Я думаю, что понимаю это, но, возможно, вы можете продемонстрировать это с помощью отредактированного price_list? Спасибо в любом случае.
2. Извините, слишком утомительно выполнять все шаги вручную. Я добавил некоторый псевдокод, чтобы сделать его более понятным.