146 lines
5.8 KiB
Python
146 lines
5.8 KiB
Python
#!/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[bool], **_):
|
|
assert sum(L) > 0, 'Mindestens ein Getränk muss vergiftet sein!';
|
|
assert sum(L) == 1, 'Höchstens ein Getränk darf vergiftet sein!';
|
|
return;
|
|
|
|
def postChecks(L: List[bool], index: int, **_):
|
|
assert L[index] == True, 'Der Algorithmus hat das vergiftete Getränk nicht erfolgreich bestimmt.';
|
|
return;
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
# ALGORITHM find poison
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
@algorithmInfos(name='Giftsuche (O(n) Vorkoster)', outputnames='index', checks=True, metrics=True, preChecks=preChecks, postChecks=postChecks)
|
|
def FindPoison(L: List[bool]) -> int:
|
|
'''
|
|
Inputs: L = Liste von Getränken: durch boolesche Werte wird dargestellt, ob ein Getränk vergiftet ist.
|
|
Annahme: Genau ein Getränk sei vergiftet.
|
|
Outputs: Der Index des vergifteten Getränks, falls es eines gibt, ansonsten -1.
|
|
|
|
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');
|
|
effects = waitForEffects(L, testers);
|
|
logDebug('Effekte auswerten, um vergiftete Getränke zu lokalisieren.');
|
|
poisened = evaluateEffects(testers, effects);
|
|
if len(poisened) > 0:
|
|
return poisened[0];
|
|
return -1;
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
# 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.
|
|
Annahme: Genau ein Getränk sei vergiftet.
|
|
Outputs: Der Index des vergifteten Getränks, falls es eines gibt, ansonsten -1.
|
|
|
|
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):
|
|
tester0 = [ k for k in range(n) if nthBit(number=k, digit=i) == 0 ];
|
|
tester1 = [ k for k in range(n) if not (k in tester0) ];
|
|
# NOTE: tester1 ist virtuell: aus den Effekten auf tester0 und den Annahmen lassen sich die Effekte auf tester0 erschließen.
|
|
# Darum zählen wir nicht 2 sondern 1 Vorkoster.
|
|
AddToCounter(1);
|
|
logDebug('Füge Vorkoster hinzu, der alle Getränke k testet mit {i}. Bit = 0.'.format(i=i))
|
|
testers.append(tester0);
|
|
testers.append(tester1);
|
|
logDebug('Warte auf Effekte');
|
|
effects = waitForEffects(L, testers);
|
|
logDebug('Effekte auswerten, um vergiftete Getränke zu lokalisieren.');
|
|
poisened = evaluateEffects(testers, effects);
|
|
if len(poisened) > 0:
|
|
return poisened[0];
|
|
return -1;
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
# AUXILIARY METHOD wait for effects, evaluate effects
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
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;
|