Что тут можно в коде улучшить или оптимизировать? чтобы он не был таким длинным?

Игра в русскую рулетку в пистолет вставлено 5 патронов 4 холостых и 1 боевой

Если вы будете стрелять в себя то на следующем ходе можете снова сделать выбор в кого стрелять. Но если стреляете в соперника и не убиваете его то ход переходит ему. ДЛя него действуют такие же правила.

Так вот какая веротяность выиграть в кого стрелять?

резульат

В себя - 47% выиграть

В соперника - 52% выиграть

import itertools

x = list(map(list, set(itertools.permutations([0, 1, 0, 1, 1]))))

for gg in range(2):
    победа=0
    поражение=0
    z=set()
    for _ in x:
        for num, i in enumerate(_):
            if num==0:          
                if i==1:
                    if gg==0:
                        поражение+=1
                    elif gg==1:
                        победа+=1
                else:
                    if gg==0: z.add((x.index(_),"всебя"))
                    elif gg==1: z.add((x.index(_),"внего"))
            else:
                for k in z.copy():
                    z.remove(k)
                    if k[-1]=='всебя':
                        if i==1:
                            if num==_.count(0):
                                победа+=1
                            else:       
                                победа+=1
                                поражение+=1
                        else:
                            xc=list(k)
                            z.add(tuple(xc+["внего"]))
                            z.add(tuple(xc+["всебя"]))
                    elif k[-1]=='внего':
                        if i==1:
                            if num==_.count(0):
                                поражение+=1
                            else: 
                                победа+=1
                                поражение+=1
                        else:
                            xc=list(k)
                            z.add(tuple(xc+["внего"]))
                            z.add(tuple(xc+["всебя"]))
    if gg==0:
        print("В себя -", str(int(победа/(победа+поражение)*100))+"% выиграть")
    else:
        print("В соперника -", str(int(победа/(победа+поражение)*100))+"% выиграть")

Ответы (3 шт):

Автор решения: CrazyElf

Учитывая, что задача не поставлена чётко, я просто покажу, как можно писать гораздо более понятный код. Можно было обойтись и без Enum, но на мой взгляд такой год гораздо проще читать, понимать и дорабатывать. Хотя размер кода и не изменился, но сам код поменялся кардинально:

from enum import Enum
from random import choice, shuffle

class Player(Enum):
    One = 1
    Two = 2
    
class Shoot(Enum):
    Self = 1
    Other = 2
    Random = -1
    
class Cartridge(Enum):
    Live = 1
    Blank = 0

def game(clip, player: Player, shoot=Shoot.Random) -> Player:
    if shoot == Shoot.Random:
        shoot = choice([Shoot.Self, Shoot.Other])
    else:
        shuffle(clip)
    bullet, *clip = clip
    if bullet == Cartridge.Live:
        if shoot == Shoot.Other:
            return player
        else:
            return next_player(player)
    return game(clip, next_player(player))

def next_player(player: Player):
    match player:
        case Player.One:
            return Player.Two
        case Player.Two:
            return Player.One
        
def get_stats(clip, trials = 100_000):
    stats = {}
    for shoot in (Shoot.Self, Shoot.Other):
        stats[shoot] = sum(game(clip, Player.One, shoot) == Player.One for _ in range(trials)) / trials
    return stats
        
clip = [Cartridge.Live] + [Cartridge.Blank] * 4
for s, r in get_stats(clip).items():
    print(f'{s}: {r:.2%}')

Вывод:

Shoot.Self: 40.01%
Shoot.Other: 60.11%

Я понял задачу так, что мы первый ход фиксируем - либо игрок стреляет в себя, либо в другого игрока. Остальные ходы - случайные. Барабан крутится один раз - в начале игры (и это завязано в коде на то, что первый ход игрока жёстко задан, это не очень хорошо, лучше потом переделать это место). Ну и собираем статистику для первого варианта и для второго, рекурсивно играя в эту игру, пока кто-нибудь не выиграет. Возможно тут что-то другое хотели, но с данным кодом гораздо проще будет переделать схему игры, чем с вашим, где вообще непонятно что происходит.

P.S. Варианты:

  • Второй игрок всегда стреляет в первого, а первый стреляет случайно (кроме фиксированного первого выстрела). Вероятность выиграть у 1-го игрока резко падает:
    • В себя: 20%
    • В другого: 40%
  • Оба игрока всегда стреляют в другого (за исключением 1-го выстрела 1-го игрока). Нет разницы со случайным выбором в кого стрелять:
    • В себя: 40%
    • В другого: 60%
→ Ссылка
Автор решения: Pavel Mayorov

И так, правильный ответ. Разумеется, с учётом что оба игрока играют оптимально.

Как часто бывает, для получения ответа на задачу её надо обобщить. Обозначим через P(N) вероятность победы при оптимальной игре в ситуации, когда в обойме осталось N случайных патронов. Заметим, что следующий патрон будет боевым с вероятностью 1/N.

В таком случае, получаем следующие исходы:

  • при выстреле в себя игрок погибает с вероятностью 1/N, иначе же выигрывает с вероятностью (1 - 1/N) P(N-1) = (N-1)/N P(N-1)

  • при выстреле в противника игрок мгновенно выигрывает с вероятностью 1/N, иначе же выигрывает с вероятностью (1 - 1/N) (1 - P(N-1)) = (N-1)/N (1 - P(N-1))

Поскольку каждый игрок играет оптимально, получаем формулу:

P(N) = max( (N-1)/N P(N-1), 1/N + (N-1)/N (1 - P(N-1)) )

Эту формулу можно перенести в программу напрямую:

def prob():
  p = 0
  n = 0
  while True:
    n = n+1
    self = (1 - 1/n) * p
    other = 1/n + (1 - 1/n) * (1 - p)
    if self > other:
        yield n, "self", self
        p = self
    else:
        yield n, "other", other
        p = other
       
import itertools
for x in itertools.islice(prob(), 10):
    print(x)

Однако, запустив эту программу, можно увидеть странность: каждый чётный элемент последовательности равен 0.5. Это не случайность, и это можно доказать по индукции.

И так, теорема

P(N) = 1/2 для любого чётного положительного N, причём направление выстрела не важно

Доказательство по индукции. База:

Очевидно, что P(1) = 1 Это значит, что в ситуации с двумя патронами тот в кого стреляли, либо умирает - либо получает ход и гарантировано побеждает. Чистая ситуация 50/50. Ч.т.д.

Теперь переход.

И так, мы знаем что P(N-2) = 1/2, посчитаем P(N-1):

При выстреле в себя вероятность победы равна (N-2)/(N-1) P(N-2) = (N-2)/2(N-1)

При выстреле в противника вероятность победы равна 1/(N-1) + (N-2)/(N-1) (1 - P(N-2)) = 1/(N-1) + (N-2)/2(N-1) = N/2(N-1)

Вторая вероятность очевидно больше, и P(N-1) = N/2(N-1)

Теперь посчитаем P(N)

При выстреле в себя вероятность победы равна (N-1)/N P(N-1) = 1/2

При выстреле в противника вероятность победы равна 1/N + (N-1)/N (1 - P(N-1)) = 1/N + (N-1)/N (1 - N/2(N-1)) = 1/N + (N-1 - N/2)/N = 1/2

Ч.т.д

Окончательное решение

И так, для чётных N P(N) = 1/2 независимо от того куда стрелять

Осталось найти решение для нечётных. Но вообще-то его уже нашли раньше в процессе доказательства перехода индукции.

При выстреле в себя вероятность победы равна (N-1)/2N

При выстреле в противника вероятность победы равна (N+1)/2N

Итоговая программа:

n = 5

if n % 2 == 0:
  print(f"в себя - 0.5")
  print(f"в противника - 0.5")
else:
  print(f"в себя - {(n-1)/2/n}")
  print(f"в противника - {(n+1)/2/n}")
→ Ссылка
Автор решения: Stanislav Volodarskiy

Буду решать задачу для n мест в барабане и m патронов. Патроны помещаются на случайные позиции. После чего нулевой игрок стреляет в себя или стреляет в противника и передаёт пистолет ему. Противник повторяет действие. Игра продолжается до первого выстрела. Противники играют наилучшим для себя образом. Требуется найти вероятность победы первого игрока при условии что он делает первый выстрел в себя и при условии что он делает первый выстрел в противника.

i – сколько было сделано ходов до сих пор.

Пусть qi – вероятность что на i-том месте патрон при условии что на предыдущих местах патронов нет. qi = m/n-i.
В частности qn-m = 1.

Обозначим:

p0(i, 0) – вероятность победы нулевого игрока, при условии что пистолет у него в руках.
p0(i, 1) – вероятность победы нулевого игрока, при условии что пистолет в руках противника.

Если нулевой игрок стреляет в себя на шаге i, то он убивает себя с вероятностью qi или продолжает игру с вероятностью 1 - qi. Во втором случае он побеждает с вероятностью p0(i + 1, 0). Собираем всё вместе:

(1 - qi) p0(i + 1, 0)

Если нулевой игрок стреляет в противника на шаге i, то он убивает его с вероятностью qi или игра продолжается вероятностью 1 - qi. Во втором случае пистолет переходит противнику и мы можем победить с вероятностью p0(i + 1, 1). Собираем всё вместе:

qi + (1 - qi) p0(i + 1, 1)

Нулевой игрок старается увеличить свои шансы на победу и выбирает максимум из доступных вариантов:

p0(i, 0) = max((1 - qi) p0(i + 1, 0), qi + (1 - qi) p0(i + 1, 1))

Если пистолет в руках противника, то он играет так, чтобы наши шансы на победу были минимальны:

p0(i, 1) = min((1 - qi) p0(i + 1, 0), qi + (1 - qi) p0(i + 1, 1))

p0 вычисляется реккурентно. База i = n - m. На этом шагу можно быть уверенным что выстрел произойдёт, все пустые позиции уже пройдены:

p0(n - m, 0) = 1 – нулевой стреляет в первого.
p0(n - m, 1) = 0 – первый стреляет в нулевого.

n, m = map(int, input().split())

q = [m / (n - i) for i in range(n - m + 1)]
p0 = [[1, 0] for _ in range(n - m + 2)]


def p0_self(i):   # вероятность победы после выстрела в себя
    return (1 - q[i]) * p0[i + 1][0]


def p0_other(i):  # вероятность победы после выстрела в противника 
    return q[i] + (1 - q[i]) * p0[i + 1][1]


for i in range(n - m + 1)[::-1]:
    p0[i][0] = max(p0_self(i), p0_other(i))
    p0[i][1] = min(p0_self(i), p0_other(i))

print(p0_self (0))  
print(p0_other(0))

Если убрать все ненужные таблицы и заменить переменные, получится такой вариант:

n, m = map(int, input().split())
a, b = 0, 1
for i in range(n - m + 1)[::-1]:
    q = m / (n - i)
    a, b = (1 - q) * max(a, b), q + (1 - q) * min(a, b)
print(a)
print(b)  

По индукции докажем что a + b = 1 и a ≤ b. Что после замены индексов даст:

n, m = map(int, input().split())
a = 0
for j in range(m, n + 1):
    a = (j - m) / j * (1 - a)
print(a)
print(1 - a)  
→ Ссылка