Каков наиболее эффективный способ увеличения большого количества значений в Python?

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

10 детей.
5 ящиков.
Каждый ребенок выбирает три коробки.
Каждый ящик открыт:
- Если он содержит что-то, все дети, выбранные этим полем, получают 1 балл
- В противном случае никто не получает точку.

Мой вопрос в том, что я делаю жирным. Потому что в моем коде есть много детей и много ящиков.

В настоящее время я делаю следующее:

children = {"child_1" : 0, ... , "child_10": 0}

gp1 = ["child_3", "child_7", "child_10"] #children who selected the box 1
...
gp5 = ["child_2", "child_5", "child_8", "child_10"]

boxes = [(0,gp1), (0,gp2), (1,gp3), (1,gp4), (0,gp5)]

for box in boxes:
    if box[0] == 1: #something inside
        for child in box[1]:
            children[child] += 1

В основном я беспокоюсь о цикле for, который назначает каждому ребенку дополнительную точку. Потому что в моем последнем коде у меня много детей, я боюсь, что это тоже замедлит программу.

Есть ли более эффективный способ для всех детей той же группы быстрее иметь свою точку?

Ответ 1

  • Представляйте дети как индексы в массивы, а не как строки:

    childrenScores = [0] * 10
    gp1 = [2,6,9] # children who selected box 1
    ...
    gp5 = [1,4,7,9]
    
    boxes = [(0,gp1), (0,gp2), (1,gp3), (1,gp4), (0,gp5)]
    
  • Затем вы можете сохранить childrenScores в качестве массива NumPy и использовать расширенную индексацию:

    childrenScores = np.zeros(10, dtype=int)
    ...
    for box in boxes:
        if box[0]:
            childrenScores[box[1]] += 1 # NumPy advanced indexing
    

    Это все еще связано с циклом где-то, но цикл находится внутри внутри NumPy, что должно обеспечить значимое ускорение.

Ответ 2

Единственная скорость, о которой я могу думать, - использовать массивы numpy и передать операцию суммирования.

children[child] += np.ones(len(children[child]))

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

Ответ 3

Что я буду делать

В списках gpX не сохраняются "имя дочернего элемента" (например, "child_10"), но сохраняются ссылки на дочерние номера точек.

Как это сделать

Используя тот факт, что списки являются объектами в python, вы можете:

  • Измените детей dict следующим образом: children = {"child_0": [0], "child_1": [0], ...} и т.д.
  • При назначении группе не назначайте ключ, а присваивайте значение (например, gp1.append(children["child_0"])).
  • Затем цикл должен выглядеть так: for child in box[1]: child[0]+=1. Этот WILL обновит children dict.

EDIT:

Почему это быстрее: Потому что вы не учитываете ту часть, где вы ищете children[child], что может быть дорогостоящим.

Этот метод работает, потому что, сохраняя итоговые значения в изменяемом типе и добавляя эти значения в списки групп, как значение dict, так и каждое значение списка ящиков указывают на те же записи в списке, а изменение одного изменит другое.

Ответ 4

Две общие точки:

(1) Основываясь на том, что вы нам сказали, нет причин фокусировать свою энергию на незначительной оптимизации производительности. Ваше время будет лучше потрачено на размышления о способах сделать ваши структуры данных менее неудобными и более коммуникативными. Сложно поддерживать связку взаимосвязанных диктов, списков и кортежей. Для альтернативы см. Пример ниже.

(2) Как разработчик игр, вы понимаете, что события следуют определенной последовательности: сначала дети выбирают свои боксы, а позже выясняют, получают ли они очки за них. Но вам этого не нужно. Ребенок может выбрать коробку и немедленно получить очки (или нет). Если есть необходимость сохранить детское невежество в отношении таких результатов, части вашего алгоритма, зависящие от такого невежества, могут при необходимости использовать эту завесу секретности. Результат: нет необходимости, чтобы ящик проходил через своих детей, назначая очки каждому из них; вместо этого сразу же присваивайте очки детям, когда будут выбраны ящики.

import random

class Box(object):
    def __init__(self, name):
        self.name = name
        self.prize = random.randint(0,1)

class Child(object):
    def __init__(self, name):
        self.name = name
        self.boxes = []
        self.score = 0
        self._score = 0

    def choose(self, n, boxes):
        bs = random.sample(boxes, n)
        for b in bs:
            self.boxes.append(b)
            self._score += b.prize

    def reveal_score(self):
        self.score = self._score

boxes = [Box(i) for i in range(5)]
kids = [Child(i) for i in range(10)]

for k in kids:
    k.choose(3, boxes)

# Later in the game ...
for k in kids:
    k.reveal_score()
    print (k.name, k.score), '=>', [(b.name, b.prize) for b in k.boxes]

Ответ 5

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

Возможно, будет немного быстрее использовать фильтр или itertools.ifilter, чтобы выбрать поля, которые имеют что-то в них:

import itertools

...

for box in itertools.ifilter(lambda x: x[0], boxes):
    for child in box[1]
        children[child] += 1

Ответ 6

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

Во-первых, вам нужно знать, к каким группам принадлежит ребенок. Мы будем хранить эту информацию в качестве карты, которую мы будем называть childToGroupsMap, которая будет отображать каждый дочерний элемент в массив, содержащий принадлежащие ему поля, например:

childToGroupsMap = {}
for child in children:
    childToGroupsMap[child[0]] = []
for box in boxes:
    for child in box[1]:
        if (box[1] not in childToGroupsMap[child]):
            childToGroupsMap[child].append(box[1])

Это создает обратную карту от детей к блокам.

Это также поможет отобразить карту из каждого окна в логическое значение, представляющее, было ли оно открыто:

boxToOpenedMap = {}
for box in boxes:
    boxToOpenedMap[box[1]] = box[0]

Теперь, когда кто-то задает количество точек, которые имеет дочерний элемент, вы можете пройти через каждый из его полей (используя childToGroupsMap, конечно) и просто подсчитать, сколько из этих ящиков было сопоставлено с 1 на карте boxes:

def countBoxesForChild(child):
    points = 0
    for box in childToGroupsMap[child]
        if boxToOpenedMap[box] == 1:
            points += 1
    return points

Чтобы сделать это лучше, вы можете кэшировать полученное количество очков. Сделайте такую ​​карту:

childToPointsCalculated = {}
for child in children:
    childToPointsCalculated[child[0]] = -1

Где -1 означает, что мы еще не знаем, сколько баллов у этого ребенка.

Наконец, вы можете изменить свою функцию countBoxesForChild для использования кеша:

def countBoxesForChild(child):
    if childToPointsCalculated[child] != -1
        return childToPointsCalculated[child]

    points = 0
    for box in childToGroupsMap[child]
        if boxToOpenedMap[box] == 1:
            points += 1
    childToPointsCalculated[child] = points
    return points