ads1_2021/code/algorithms/search/poison.py

139 lines
5.6 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from local.maths import *;
from local.typing import *;
from code.core.log import *;
from code.algorithms.methods import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# GLOBAL VARIABLES/CONSTANTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# CHECKS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def preChecks(L: List[int], **_):
# TODO
return;
def postChecks(L: List[int], indexes: List[int], **_):
if sum(L) > 0:
assert [ k for k in range(len(L)) if L[k] == True ] == indexes, 'Der Algorithmus hat die vergifteten Getränke nicht erfolgreich bestimmt.';
else:
assert len(indexes) == 0, 'Der Algorithmus sollte erkennen, dass Gift nicht vorhanden ist.';
return;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ALGORITHM find poison
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@algorithmInfos(name='Giftsuche (O(n) Vorkoster)', outputnames='indexes', checks=True, metrics=True, preChecks=preChecks, postChecks=postChecks)
def FindPoison(L: List[bool]) -> List[int]:
'''
Inputs: L = Liste von Getränken: durch boolesche Werte wird dargestellt, ob ein Getränk vergiftet ist.
Outputs: Die Liste aller Indexes i mit L[i] == true, was den vergifteten Getränken entspricht.
NOTE: Zeitkosten hier messen nur die Anzahl der Vorkoster.
'''
logDebug('Bereite Vorkoster vor');
n = len(L);
testers = [];
for i in range(n):
AddToCounter();
logDebug('Füge Vorkoster hinzu, der nur Getränk {i} testet.'.format(i=i))
testers.append([i]);
logDebug('Warte auf Effekte');
2021-10-26 14:14:35 +02:00
effects = waitForEffects(L, testers);
logDebug('Effekte auswerten, um vergiftete Getränke zu lokalisieren.');
poisened = evaluateEffects(testers, effects);
return poisened;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ALGORITHM find poison fast
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@algorithmInfos(name='Giftsuche (O(log(n)) Vorkoster)', outputnames='indexes', checks=True, metrics=True, preChecks=preChecks, postChecks=postChecks)
def FindPoisonFast(L: List[bool]) -> List[int]:
'''
Inputs: L = Liste von Getränken: durch boolesche Werte wird dargestellt, ob ein Getränk vergiftet ist.
Outputs: Die Liste aller Indexes i mit L[i] == true, was den vergifteten Getränken entspricht.
NOTE: Zeitkosten hier messen nur die Anzahl der Vorkoster.
'''
logDebug('Bereite Vorkoster vor');
n = len(L);
p = math.floor(math.log2(n));
testers = [];
## Für jedes Bit i=0 bis p ...
for i in range(p+1):
AddToCounter(2);
logDebug('Füge Vorkoster hinzu, der alle Getränke k testet mit {i}. Bit = 1.'.format(i=i))
testers.append([ k for k in range(n) if nthBit(number=k, digit=i) == 1 ]);
logDebug('Füge Vorkoster hinzu, der alle Getränke k testet mit {i}. Bit = 0.'.format(i=i))
testers.append([ k for k in range(n) if nthBit(number=k, digit=i) == 0 ]);
logDebug('Warte auf Effekte');
2021-10-26 14:14:35 +02:00
effects = waitForEffects(L, testers);
logDebug('Effekte auswerten, um vergiftete Getränke zu lokalisieren.');
poisened = evaluateEffects(testers, effects);
return poisened;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUXILIARY METHOD wait for effects, evaluate effects
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2021-10-26 14:14:35 +02:00
def waitForEffects(L: List[bool], testers: List[List[int]]) -> List[int]:
'''
Inputs:
- L = Liste von Getränken: durch boolesche Werte wird dargestellt, ob ein Getränk vergiftet ist.
- testers = Liste von Vorkostern. Jeder Vorkoster kostet eine 'Teilliste' der Getränke.
Outputs: effects = eine Liste, die jedem Vorkoster zuordnet, wie viele vergiftete Getränke er konsumiert hat.
'''
m = len(testers);
effects = [];
for i in range(m):
effect = sum([L[k] for k in testers[i]]);
effects.append(effect);
return effects;
def evaluateEffects(testers: List[List[int]], effects: List[int]) -> List[int]:
'''
Inputs:
- testers = Liste von Vorkostern. Jeder Vorkoster kostet eine 'Teilliste' der Getränke.
- effects = eine Liste, die jedem Vorkoster zuordnet, wie viele vergiftete Getränke er konsumiert hat.
Annahmen: Vorkoster wurden so angewiesen, dass es garantiert ist, vergiftete Getränke zu finden, wenn es die gibt.
Outputs: Liste der Indexes aller vergifteten Getränke.
'''
## Werte Effekte aus, um Gift zu lokalisieren:
search = set([]);
## Zuerst die Indexes der Getränke bei allen vergifteten Tester zusammenführen:
for i in range(len(testers)):
if effects[i] > 0:
search = search.union(testers[i]);
## jetzt eliminieren wir alle Getränke, die von nicht vergifteten Testern konsumiert wurden:
for i in range(len(testers)):
if effects[i] == 0:
search = search.difference(testers[i]);
return list(search);
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUXILIARY METHOD n. Bit der binären Darstellung einer Zahl
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def nthBit(number: int, digit: int) -> int:
number_binary = bin(number)[2:][::-1];
if digit < len(number_binary):
return int(number_binary[digit]);
return 0;