#!/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'); 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'); 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 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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;