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