<commit_before>import itertools
import copy
class kakuroSolver(object):
  """this class generates the solution"""
  def __init__(self, rows, columns, horizontalSequencesDict, verticalSequencesDict):
    self.rows = rows
    self.columns = columns
    self.kakuroBoard = []
    self.horizontalSequences = []
    self.verticalSequences = []
    self.horizontalSequencesDict = horizontalSequencesDict
    self.verticalSequencesDict = verticalSequencesDict

  def intitializeKakuroBoard(self):
    """initialize the kakuro board"""
    self.kakuroBoard = [[-1 for x in range(self.columns)] for y in range(self.rows)]
    for key, sequenceObject in self.horizontalSequencesDict.items():
      for index in sequenceObject.vertices:
        a, b = index
        self.kakuroBoard[a][b] = 0

  def initializeHorizontalDictionary(self):
    """convert dictionary to list for easier access of horizontal sequences"""
    for key, sequenceObject in self.horizontalSequencesDict.items():
      self.horizontalSequences.append(sequenceObject)
    self.horizontalSequences = sorted(list(set(self.horizontalSequences)))
    for sequenceObject in self.horizontalSequences:
      sequenceObject.index[1] -= sequenceObject.lengthOfSequence

  def initializeVerticalDictionary(self):
    """convert dictionary to list for easier access of vertical sequences"""
    for key, sequenceObject in self.verticalSequencesDict.items():
      self.verticalSequences.append(sequenceObject)
    self.verticalSequences = list(set(self.verticalSequences))
    for sequenceObject in self.verticalSequences:
      sequenceObject.index[0] -= sequenceObject.lengthOfSequence
    self.verticalSequences.sort(key=lambda x: x.sortBy, reverse=False)

  def initializeEliminateImprobableUniqueSequences(self):
    """this function removes unique sequences and their permutations that are improbable due to interections with other sequences"""
    for horiSequence in self.horizontalSequences:
      for num in (horiSequence.vertices):
        vertSequence = self.verticalSequencesDict[(num)]
        for h_item in horiSequence.uniqueSolutions:
          isValid = False
          for v_item in vertSequence.uniqueSolutions:
            if len(set(h_item) & set(v_item)) > 0:
              isValid = True
          if isValid is not True:
            horiSequence.uniqueSolutions.remove(h_item)
            tempList = list(itertools.permutations(h_item))
            for item in tempList:
              horiSequence.permutatedSolutions.remove(list(item))
            isValid = False

    for vertSequence in self.verticalSequences:
      for num in (vertSequence.vertices):
        horiSequence = self.horizontalSequencesDict[(num)]
        for v_item in vertSequence.uniqueSolutions:
          isValid = False
          for h_item in horiSequence.uniqueSolutions:
            if len(set(h_item) & set(v_item)) > 0:
              isValid = True
          if isValid is not True:
            vertSequence.uniqueSolutions.remove(v_item)
            tempList = list(itertools.permutations(v_item))
            for item in tempList:
              vertSequence.permutatedSolutions.remove(list(item))
            isValid = False

  def eliminateSequencePermutations(self):
    """eliminate improbable permuatations of sequences due to intersections"""
    status = False
    for horiSequence in self.horizontalSequences:
      if horiSequence.isFilled is True:
        continue
      toDelete = []
      for h_permSequence in horiSequence.permutatedSolutions:
        h_index = 0
        for h_coordinate in horiSequence.vertices:
          v_index = 0
          vertSequence = self.verticalSequencesDict[(h_coordinate)]
          while h_coordinate != vertSequence.vertices[v_index]:
            v_index += 1
          isMatch = False
          for v_permSequence in vertSequence.permutatedSolutions:
            if h_permSequence[h_index] == v_permSequence[v_index]:
              isMatch = True
              break
          if isMatch is not True:
            toDelete.append(h_permSequence)
            status = True
            break
          h_index += 1
      for item in toDelete:
        horiSequence.permutatedSolutions.remove(item)

    for vertSequence in self.verticalSequences:
      if vertSequence.isFilled is True:
        continue
      toDelete = []
      for v_permSequence in vertSequence.permutatedSolutions:
        v_index = 0
        for v_coordinate in vertSequence.vertices:
          h_index = 0
          horiSequence = self.horizontalSequencesDict[(v_coordinate)]
          while v_coordinate != horiSequence.vertices[h_index]:
            h_index += 1
          isMatch = False
          for h_permSequence in horiSequence.permutatedSolutions:
            if v_permSequence[v_index] == h_permSequence[h_index]:
              isMatch = True
              break
          if isMatch is not True:
            toDelete.append(v_permSequence)
            status = True
            break
          v_index += 1
      for item in toDelete:
        vertSequence.permutatedSolutions.remove(item)
    return status

  def fillSequences(self):
    """this function fills the board when there is only one sequence is possible"""
    for sequenceObject in (self.horizontalSequences + self.verticalSequences):
      if len(sequenceObject.permutatedSolutions) != 1 or sequenceObject.isFilled is True:
        continue
      numList = sequenceObject.permutatedSolutions
      numList = numList[0]
      i = 0
      for coordinate in sequenceObject.vertices:
        a, b = coordinate
        self.kakuroBoard[a][b] = numList[i]
        i += 1
      sequenceObject.isFilled = True

  def updateSequences(self):
    """this function eliminates sequence permutations based on exisiting board configuration"""
    count = 0
    for sequenceObject in (self.horizontalSequences + self.verticalSequences):
      if len(sequenceObject.permutatedSolutions) == 1:
        continue
      index = 0
      for coordinate in sequenceObject.vertices:
        a, b = coordinate
        if self.kakuroBoard[a][b] != 0:
          toDelete = []
          for solnList in sequenceObject.permutatedSolutions:
            if solnList[index] != self.kakuroBoard[a][b]:
              toDelete.append(solnList)
          for item in toDelete:
            sequenceObject.permutatedSolutions.remove(item)
            count += 1
        else:
          numberList = []
          toDelete = []
          self.checkNumbersInSameRowColumn(numberList, coordinate)
          for solnList in sequenceObject.permutatedSolutions:
            for number in numberList:
              if number == solnList[index]:
                toDelete.append(solnList)
                break
          for item in toDelete:
            sequenceObject.permutatedSolutions.remove(item)
            count += 1
        index += 1
    if count > 0:
      status = True
    else:
      status = False
    return status

  def checkNumbersInSameRowColumn(self, numberList, coordinate):
    """this function checks unfilled elements in the board and eliminates permutations based on filled elements in the same sequence"""
    a, b = coordinate

    row = a
    while row >= 0:
      if self.kakuroBoard[row][b] == -1:
        break
      elif self.kakuroBoard[row][b] == 0:
        row -= 1
        continue
      else:
        numberList.append(self.kakuroBoard[row][b])
        row -= 1

    row = a
    while row < self.rows:
      if self.kakuroBoard[row][b] == -1:
        break
      elif self.kakuroBoard[row][b] == 0:
        row += 1
        continue
      else:
        numberList.append(self.kakuroBoard[row][b])
        row += 1

    column = b
    while column >= 0:
      if self.kakuroBoard[a][column] == -1:
        break
      elif self.kakuroBoard[a][column] == 0:
        column -= 1
        continue
      else:
        numberList.append(self.kakuroBoard[a][column])
        column -= 1

    column = b
    while column < self.columns:
      if self.kakuroBoard[a][column] == -1:
        break
      elif self.kakuroBoard[a][column] == 0:
        column += 1
        continue
      else:
        numberList.append(self.kakuroBoard[a][column])
        column += 1

  def getSequenceInformationForMultipleSolution(self):
    """get the sequence that has maximum possible permutations and also returns a copy of sequences that are not filled yet"""
    backupHorizontalSequences = []
    backupVerticalSequences = []
    isHorizontal = False
    maxLengthSequence = None
    maxSize = 1
    for h_sequence in self.horizontalSequences:
      if len(h_sequence.permutatedSolutions) > 1:
        backupHorizontalSequences.append(copy.deepcopy(h_sequence))
        if len(h_sequence.permutatedSolutions) > maxSize:
          maxSize = len(h_sequence.permutatedSolutions)
          maxLengthSequence = copy.deepcopy(h_sequence)
          isHorizontal = True

    for v_sequence in self.verticalSequences:
      if len(v_sequence.permutatedSolutions) > 1:
        backupVerticalSequences.append(copy.deepcopy(v_sequence))
        if len(v_sequence.permutatedSolutions) > maxSize:
          maxSize = len(v_sequence.permutatedSolutions)
          maxLengthSequence = copy.deepcopy(v_sequence)

    backupKakuroBoard = copy.deepcopy(self.kakuroBoard)
    return isHorizontal, backupKakuroBoard, maxLengthSequence, backupHorizontalSequences, backupVerticalSequences

  def restoreSequences(self, backupKakuroBoard, backupHorizontalSequences, backupVerticalSequences):
    """this function restores sequences before we attempted a secific combination for the case where we have multiple solutions"""
    for h_sequence in backupHorizontalSequences:
      for h_coordinate in h_sequence.vertices:
        self.horizontalSequencesDict[h_coordinate] = copy.deepcopy(h_sequence)
        for sequences in self.horizontalSequences:
          toDelete = []
          if sequences.vertices == h_sequence.vertices and sequences.index == list(h_coordinate):
            toDelete.append(sequences)
            break
        for item in toDelete:
          self.horizontalSequences.remove(item)
        self.horizontalSequences.append(self.horizontalSequencesDict[h_coordinate])

    for v_sequence in backupVerticalSequences:
      for v_coordinate in v_sequence.vertices:
        self.verticalSequencesDict[v_coordinate] = copy.deepcopy(v_sequence)
        for sequences in self.verticalSequences:
          toDelete = []
          if sequences.vertices == v_sequence.vertices and sequences.index == list(v_coordinate):
            toDelete.append(sequences)
            break
        for item in toDelete:
          self.verticalSequences.remove(item)
        self.verticalSequences.append(self.verticalSequencesDict[v_coordinate])

    self.kakuroBoard = copy.deepcopy(backupKakuroBoard)

  def fillBoard(self):
    """fills the board with elements confirmed through elimination"""
    self.eliminateSequencePermutations()
    self.fillSequences()
    updateStatus = self.updateSequences()
    while updateStatus is True:
      self.fillSequences()
      updateStatus = self.updateSequences()
      if updateStatus is not True:
        eliminateStatus = self.eliminateSequencePermutations()
        if eliminateStatus is True:
          updateStatus = True

  def uniqueSolution(self):
    """solve for unique solution"""
    self.fillBoard()
    return self.testSolution()

  def multipleSolutions(self, solutionNumber = 0):
    """solve for multiple solutions"""
    isHorizontal, backupKakuroBoard, maxSequence, backupHorizontalSequences, backupVerticalSequences = self.getSequenceInformationForMultipleSolution()

    for permuatation in maxSequence.permutatedSolutions:
      newPermutatedSolution = []
      newPermutatedSolution.append(copy.deepcopy(permuatation))

      if isHorizontal is True:
        sequenceList = self.horizontalSequences
      else:
        sequenceList = self.verticalSequences

      for sequence in sequenceList:
        if sequence.vertices == maxSequence.vertices:
          sequence.permutatedSolutions = copy.deepcopy(newPermutatedSolution)

      self.fillBoard()
      isValidSolution = self.testSolution()
      if isValidSolution is True:
        solutionNumber += 1
        print
        print "solution number:", solutionNumber
        self.printSolution()
      else:
        self.multipleSolutions(solutionNumber)
      self.restoreSequences(backupKakuroBoard, backupHorizontalSequences, backupVerticalSequences)

    if solutionNumber == 0:
      print
      print "Sorry!! You did not get a valid solution"
      self.printDebugInformation()
      print
      print "Final Solution"
      self.printSolution()
    return

  def getSolution(self):
    """this function generates the solution"""
    #intitialize sequences and the kakuro board
    self.intitializeKakuroBoard()
    self.initializeHorizontalDictionary()
    self.initializeVerticalDictionary()
    self.initializeEliminateImprobableUniqueSequences()

    #solve
    if self.uniqueSolution() is True:
      self.printSuccess()
    else:
      self.multipleSolutions()

  def printSuccess(self):
    print
    print "Solution"
    self.printSolution()
    print
    return

  def printSolution(self):
    """this function prints the kakuro board"""
    for num in self.kakuroBoard:
      for item in num:
        if item == -1:
          print '#', ' ',
        else:
          print item, ' ',
      print

  def testSolution(self):
    """this function tests for validity of the solution"""
    #horizontal
    for key, sequenceObject in self.horizontalSequencesDict.items():
      sum = 0
      digits = set()
      for index in sequenceObject.vertices:
        a, b = index
        if self.kakuroBoard[a][b] < 1 or self.kakuroBoard[a][b] > 9:
          return False
        digits.add(self.kakuroBoard[a][b])
        sum += self.kakuroBoard[a][b]
      if sum != sequenceObject.sumOfInts or len(digits) != sequenceObject.lengthOfSequence:
        return False

    #vertical
    for key, sequenceObject in self.verticalSequencesDict.items():
      sum = 0
      digits = set()
      for index in sequenceObject.vertices:
        a, b = index
        if self.kakuroBoard[a][b] < 1 or self.kakuroBoard[a][b] > 9:
          return False
        digits.add(self.kakuroBoard[a][b])
        sum += self.kakuroBoard[a][b]
      if sum != sequenceObject.sumOfInts or len(digits) != sequenceObject.lengthOfSequence:
        return False
    return True

  def printDebugInformation(self):
    """function to debug that prints sequence values"""
    print "Horizontal"
    for h_sequence in self.horizontalSequences:
      if len(h_sequence.permutatedSolutions) > 1:
        print len(h_sequence.permutatedSolutions)
        print h_sequence.permutatedSolutions
    print "Vertical"
    for v_sequence in self.verticalSequences:
      if len(v_sequence.permutatedSolutions) > 1:
        print len(v_sequence.permutatedSolutions)
        print v_sequence.permutatedSolutions

<commit_msg>recursively solves when there are multiiple branches for non-unique solutions<commit_after>import itertools
import copy
class kakuroSolver(object):
  """this class generates the solution"""
  def __init__(self, rows, columns, horizontalSequencesDict, verticalSequencesDict):
    self.rows = rows
    self.columns = columns
    self.kakuroBoard = []
    self.horizontalSequences = []
    self.verticalSequences = []
    self.horizontalSequencesDict = horizontalSequencesDict
    self.verticalSequencesDict = verticalSequencesDict

  def intitializeKakuroBoard(self):
    """initialize the kakuro board"""
    self.kakuroBoard = [[-1 for x in range(self.columns)] for y in range(self.rows)]
    for key, sequenceObject in self.horizontalSequencesDict.items():
      for index in sequenceObject.vertices:
        a, b = index
        self.kakuroBoard[a][b] = 0

  def initializeHorizontalDictionary(self):
    """convert dictionary to list for easier access of horizontal sequences"""
    for key, sequenceObject in self.horizontalSequencesDict.items():
      self.horizontalSequences.append(sequenceObject)
    self.horizontalSequences = sorted(list(set(self.horizontalSequences)))
    for sequenceObject in self.horizontalSequences:
      sequenceObject.index[1] -= sequenceObject.lengthOfSequence

  def initializeVerticalDictionary(self):
    """convert dictionary to list for easier access of vertical sequences"""
    for key, sequenceObject in self.verticalSequencesDict.items():
      self.verticalSequences.append(sequenceObject)
    self.verticalSequences = list(set(self.verticalSequences))
    for sequenceObject in self.verticalSequences:
      sequenceObject.index[0] -= sequenceObject.lengthOfSequence
    self.verticalSequences.sort(key=lambda x: x.sortBy, reverse=False)

  def initializeEliminateImprobableUniqueSequences(self):
    """this function removes unique sequences and their permutations that are improbable due to interections with other sequences"""
    for horiSequence in self.horizontalSequences:
      for num in (horiSequence.vertices):
        vertSequence = self.verticalSequencesDict[(num)]
        for h_item in horiSequence.uniqueSolutions:
          isValid = False
          for v_item in vertSequence.uniqueSolutions:
            if len(set(h_item) & set(v_item)) > 0:
              isValid = True
          if isValid is not True:
            horiSequence.uniqueSolutions.remove(h_item)
            tempList = list(itertools.permutations(h_item))
            for item in tempList:
              horiSequence.permutatedSolutions.remove(list(item))
            isValid = False

    for vertSequence in self.verticalSequences:
      for num in (vertSequence.vertices):
        horiSequence = self.horizontalSequencesDict[(num)]
        for v_item in vertSequence.uniqueSolutions:
          isValid = False
          for h_item in horiSequence.uniqueSolutions:
            if len(set(h_item) & set(v_item)) > 0:
              isValid = True
          if isValid is not True:
            vertSequence.uniqueSolutions.remove(v_item)
            tempList = list(itertools.permutations(v_item))
            for item in tempList:
              vertSequence.permutatedSolutions.remove(list(item))
            isValid = False

  def eliminateSequencePermutations(self):
    """eliminate improbable permuatations of sequences due to intersections"""
    status = False
    for horiSequence in self.horizontalSequences:
      if horiSequence.isFilled is True:
        continue
      toDelete = []
      for h_permSequence in horiSequence.permutatedSolutions:
        h_index = 0
        for h_coordinate in horiSequence.vertices:
          v_index = 0
          vertSequence = self.verticalSequencesDict[(h_coordinate)]
          while h_coordinate != vertSequence.vertices[v_index]:
            v_index += 1
          isMatch = False
          for v_permSequence in vertSequence.permutatedSolutions:
            if h_permSequence[h_index] == v_permSequence[v_index]:
              isMatch = True
              break
          if isMatch is not True:
            toDelete.append(h_permSequence)
            status = True
            break
          h_index += 1
      for item in toDelete:
        horiSequence.permutatedSolutions.remove(item)

    for vertSequence in self.verticalSequences:
      if vertSequence.isFilled is True:
        continue
      toDelete = []
      for v_permSequence in vertSequence.permutatedSolutions:
        v_index = 0
        for v_coordinate in vertSequence.vertices:
          h_index = 0
          horiSequence = self.horizontalSequencesDict[(v_coordinate)]
          while v_coordinate != horiSequence.vertices[h_index]:
            h_index += 1
          isMatch = False
          for h_permSequence in horiSequence.permutatedSolutions:
            if v_permSequence[v_index] == h_permSequence[h_index]:
              isMatch = True
              break
          if isMatch is not True:
            toDelete.append(v_permSequence)
            status = True
            break
          v_index += 1
      for item in toDelete:
        vertSequence.permutatedSolutions.remove(item)
    return status

  def fillSequences(self):
    """this function fills the board when there is only one sequence is possible"""
    for sequenceObject in (self.horizontalSequences + self.verticalSequences):
      if len(sequenceObject.permutatedSolutions) != 1 or sequenceObject.isFilled is True:
        continue
      numList = sequenceObject.permutatedSolutions
      numList = numList[0]
      i = 0
      for coordinate in sequenceObject.vertices:
        a, b = coordinate
        self.kakuroBoard[a][b] = numList[i]
        i += 1
      sequenceObject.isFilled = True

  def updateSequences(self):
    """this function eliminates sequence permutations based on exisiting board configuration"""
    count = 0
    for sequenceObject in (self.horizontalSequences + self.verticalSequences):
      if len(sequenceObject.permutatedSolutions) == 1:
        continue
      index = 0
      for coordinate in sequenceObject.vertices:
        a, b = coordinate
        if self.kakuroBoard[a][b] != 0:
          toDelete = []
          for solnList in sequenceObject.permutatedSolutions:
            if solnList[index] != self.kakuroBoard[a][b]:
              toDelete.append(solnList)
          for item in toDelete:
            sequenceObject.permutatedSolutions.remove(item)
            count += 1
        else:
          numberList = []
          toDelete = []
          self.checkNumbersInSameRowColumn(numberList, coordinate)
          for solnList in sequenceObject.permutatedSolutions:
            for number in numberList:
              if number == solnList[index]:
                toDelete.append(solnList)
                break
          for item in toDelete:
            sequenceObject.permutatedSolutions.remove(item)
            count += 1
        index += 1
    if count > 0:
      status = True
    else:
      status = False
    return status

  def checkNumbersInSameRowColumn(self, numberList, coordinate):
    """this function checks unfilled elements in the board and eliminates permutations based on filled elements in the same sequence"""
    a, b = coordinate

    row = a
    while row >= 0:
      if self.kakuroBoard[row][b] == -1:
        break
      elif self.kakuroBoard[row][b] == 0:
        row -= 1
        continue
      else:
        numberList.append(self.kakuroBoard[row][b])
        row -= 1

    row = a
    while row < self.rows:
      if self.kakuroBoard[row][b] == -1:
        break
      elif self.kakuroBoard[row][b] == 0:
        row += 1
        continue
      else:
        numberList.append(self.kakuroBoard[row][b])
        row += 1

    column = b
    while column >= 0:
      if self.kakuroBoard[a][column] == -1:
        break
      elif self.kakuroBoard[a][column] == 0:
        column -= 1
        continue
      else:
        numberList.append(self.kakuroBoard[a][column])
        column -= 1

    column = b
    while column < self.columns:
      if self.kakuroBoard[a][column] == -1:
        break
      elif self.kakuroBoard[a][column] == 0:
        column += 1
        continue
      else:
        numberList.append(self.kakuroBoard[a][column])
        column += 1

  def getSequenceInformationForMultipleSolution(self):
    """get the sequence that has maximum possible permutations and also returns a copy of sequences that are not filled yet"""
    backupHorizontalSequences = []
    backupVerticalSequences = []
    isHorizontal = False
    maxLengthSequence = None
    maxSize = 1
    for h_sequence in self.horizontalSequences:
      if len(h_sequence.permutatedSolutions) > 1:
        backupHorizontalSequences.append(copy.deepcopy(h_sequence))
        if len(h_sequence.permutatedSolutions) > maxSize:
          maxSize = len(h_sequence.permutatedSolutions)
          maxLengthSequence = copy.deepcopy(h_sequence)
          isHorizontal = True

    for v_sequence in self.verticalSequences:
      if len(v_sequence.permutatedSolutions) > 1:
        backupVerticalSequences.append(copy.deepcopy(v_sequence))
        if len(v_sequence.permutatedSolutions) > maxSize:
          maxSize = len(v_sequence.permutatedSolutions)
          maxLengthSequence = copy.deepcopy(v_sequence)

    backupKakuroBoard = copy.deepcopy(self.kakuroBoard)
    return isHorizontal, backupKakuroBoard, maxLengthSequence, backupHorizontalSequences, backupVerticalSequences

  def restoreSequences(self, backupKakuroBoard, backupHorizontalSequences, backupVerticalSequences):
    """this function restores sequences before we attempted a secific combination for the case where we have multiple solutions"""
    for h_sequence in backupHorizontalSequences:
      for h_coordinate in h_sequence.vertices:
        self.horizontalSequencesDict[h_coordinate] = copy.deepcopy(h_sequence)
        for sequences in self.horizontalSequences:
          toDelete = []
          if sequences.vertices == h_sequence.vertices and sequences.index == list(h_coordinate):
            toDelete.append(sequences)
            break
        for item in toDelete:
          self.horizontalSequences.remove(item)
        self.horizontalSequences.append(self.horizontalSequencesDict[h_coordinate])

    for v_sequence in backupVerticalSequences:
      for v_coordinate in v_sequence.vertices:
        self.verticalSequencesDict[v_coordinate] = copy.deepcopy(v_sequence)
        for sequences in self.verticalSequences:
          toDelete = []
          if sequences.vertices == v_sequence.vertices and sequences.index == list(v_coordinate):
            toDelete.append(sequences)
            break
        for item in toDelete:
          self.verticalSequences.remove(item)
        self.verticalSequences.append(self.verticalSequencesDict[v_coordinate])

    self.kakuroBoard = copy.deepcopy(backupKakuroBoard)

  def fillBoard(self):
    """fills the board with elements confirmed through elimination"""
    self.eliminateSequencePermutations()
    self.fillSequences()
    updateStatus = self.updateSequences()
    while updateStatus is True:
      self.fillSequences()
      updateStatus = self.updateSequences()
      if updateStatus is not True:
        eliminateStatus = self.eliminateSequencePermutations()
        if eliminateStatus is True:
          updateStatus = True

  def uniqueSolution(self):
    """solve for unique solution"""
    self.fillBoard()
    return self.testSolution()

  def multipleSolutions(self, solutionNumber = 0):
    """solve for multiple solutions"""
    isHorizontal, backupKakuroBoard, maxSequence, backupHorizontalSequences, backupVerticalSequences = self.getSequenceInformationForMultipleSolution()

    for permuatation in maxSequence.permutatedSolutions:
      newPermutatedSolution = []
      newPermutatedSolution.append(copy.deepcopy(permuatation))

      if isHorizontal is True:
        sequenceList = self.horizontalSequences
      else:
        sequenceList = self.verticalSequences

      for sequence in sequenceList:
        if sequence.vertices == maxSequence.vertices:
          sequence.permutatedSolutions = copy.deepcopy(newPermutatedSolution)

      self.fillBoard()
      isValidSolution = self.testSolution()
      if isValidSolution is True:
        solutionNumber += 1
        print
        print "solution number:", solutionNumber
        self.printSolution()
      else:
        solutionNumber = self.multipleSolutions(solutionNumber)
      self.restoreSequences(backupKakuroBoard, backupHorizontalSequences, backupVerticalSequences)

    if solutionNumber == 0:
      print
      print "Sorry!! You did not get a valid solution"
      self.printDebugInformation()
      print
      print "Final Solution"
      self.printSolution()
    return solutionNumber

  def getSolution(self):
    """this function generates the solution"""
    #intitialize sequences and the kakuro board
    self.intitializeKakuroBoard()
    self.initializeHorizontalDictionary()
    self.initializeVerticalDictionary()
    self.initializeEliminateImprobableUniqueSequences()

    #solve
    if self.uniqueSolution() is True:
      self.printSuccess()
    else:
      self.multipleSolutions()

  def printSuccess(self):
    print
    print "Solution"
    self.printSolution()
    print
    return

  def printSolution(self):
    """this function prints the kakuro board"""
    for num in self.kakuroBoard:
      for item in num:
        if item == -1:
          print '#', ' ',
        else:
          print item, ' ',
      print

  def testSolution(self):
    """this function tests for validity of the solution"""
    #horizontal
    for key, sequenceObject in self.horizontalSequencesDict.items():
      sum = 0
      digits = set()
      for index in sequenceObject.vertices:
        a, b = index
        if self.kakuroBoard[a][b] < 1 or self.kakuroBoard[a][b] > 9:
          return False
        digits.add(self.kakuroBoard[a][b])
        sum += self.kakuroBoard[a][b]
      if sum != sequenceObject.sumOfInts or len(digits) != sequenceObject.lengthOfSequence:
        return False

    #vertical
    for key, sequenceObject in self.verticalSequencesDict.items():
      sum = 0
      digits = set()
      for index in sequenceObject.vertices:
        a, b = index
        if self.kakuroBoard[a][b] < 1 or self.kakuroBoard[a][b] > 9:
          return False
        digits.add(self.kakuroBoard[a][b])
        sum += self.kakuroBoard[a][b]
      if sum != sequenceObject.sumOfInts or len(digits) != sequenceObject.lengthOfSequence:
        return False
    return True

  def printDebugInformation(self):
    """function to debug that prints sequence values"""
    print "Horizontal"
    for h_sequence in self.horizontalSequences:
      if len(h_sequence.permutatedSolutions) > 1:
        print len(h_sequence.permutatedSolutions)
        print h_sequence.permutatedSolutions
    print "Vertical"
    for v_sequence in self.verticalSequences:
      if len(v_sequence.permutatedSolutions) > 1:
        print len(v_sequence.permutatedSolutions)
        print v_sequence.permutatedSolutions

<|endoftext|>


<commit_before>################################################################################
# Copyright (c) 2019, National Research Foundation (Square Kilometre Array)
#
# Licensed under the BSD 3-Clause License (the "License"); you may not use
# this file except in compliance with the License. You may obtain a copy
# of the License at
#
#   https://opensource.org/licenses/BSD-3-Clause
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

"""Utilities to validate and inspect bearer tokens of the S3 chunk store."""

from __future__ import print_function, division, absolute_import
from future import standard_library
standard_library.install_aliases()  # noqa: E402
from future.utils import raise_from

import base64
import json
import re
import time

BASE64URL_PADDING = {0: '', 2: '==', 3: '='}

class InvalidToken(ValueError):
    """Invalid JSON Web Token (JWT)."""

def encode_base64url_without_padding(b):
    """Encode bytes `b` to base64url string without padding (see RFC 7515)."""
    # Strip off padding as described in Appendix C of RFC 7515
    return base64.urlsafe_b64encode(b).rstrip(b'=').decode()

def decode_base64url_without_padding(s):
    """Decode base64url-encoded string `s` without padding (RFC 7515) to bytes."""
    # Restore padding as described in Appendix C of RFC 7515
    try:
        s += BASE64URL_PADDING[len(s) % 4]
    except KeyError:
        raise ValueError('Invalid base64url-encoded string {}: number of data '
                         'characters ({}) cannot be 1 more than a multiple of 4'
                         .format(s, len(s)))
    # Use the standard base64 decoder with padding (but with base64url alphabet)
    return base64.urlsafe_b64decode(s)

def encode_json_base64url(d):
    """Encode Python object `d` to JSON-serialised base64url-encoded string."""
    return encode_base64url_without_padding(json.dumps(d).encode())

def decode_json_base64url(s):
    """Decode JSON-serialised base64url-encoded string `s` (ie a JWS segment)."""
    try:
        b = decode_base64url_without_padding(s)
    except ValueError as err:
        raise_from(ValueError('Token segment {} has invalid base64url encoding'
                              .format(s)), err)
    try:
        return json.loads(b)
    except ValueError as err:
        raise_from(ValueError('Decoded token segment {} is not valid JSON'
                              .format(b)), err)

def encode_jwt(header, payload, signature):
    """Encode JSON Web Token (JWT) components to a base64url-encoded token string.

    Parameters
    ----------
    header : dict
        JSON Object Signing and Encryption (JOSE) header
    payload : dict
        JWT Claims Set
    signature : bytes
        Digital signature or MAC (calculated externally)

    Returns
    -------
    token : str
        JWT as a JWS Compact Serialization string
    """
    return '.'.join((encode_json_base64url(header),
                     encode_json_base64url(payload),
                     encode_base64url_without_padding(signature)))

def decode_jwt(token):
    """Decode JSON Web Token (JWT) string and extract claims.

    The MeerKAT archive uses JWT bearer tokens for authorisation. Each token is
    a JSON Web Signature (JWS) string with a payload of claims. This function
    extracts the claims as a dict, while also doing basic validation on the
    token. The signature is decoded but not validated, since that would require
    the server secrets.

    Parameters
    ----------
    token : str
        JWS Compact Serialization as an ASCII string

    Returns
    -------
    claims : dict
        The JWT Claims Set as a dict of key-value pairs

    Raises
    ------
    :exc:`InvalidToken`
        If token is malformed or the wrong type, or has expired
    """
    # A valid JWS Compact Serialization has multiple base64url-encoded segments
    # without padding separated by periods ('.') - see RFC 7515, RFC 4648
    if not re.match('^[A-Za-z0-9-_.]*$', token):
        raise InvalidToken("Token {} contains invalid characters not in base64url "
                           "set (or .) - are you sure it's a token?".format(token))
    # A valid JWS Compact Serialization has three segments
    try:
        encoded_header, encoded_payload, encoded_signature = token.split('.')
    except ValueError:
        raise InvalidToken("Token does not have JWS structure (maybe it's truncated?)")
    # Obtain JSON Object Signing and Encryption (JOSE) header
    try:
        header = decode_json_base64url(encoded_header)
    except ValueError as err:
        raise_from(InvalidToken('Could not decode token header {}'
                                .format(encoded_header)), err)
    # Obtain payload consisting of JWT claims
    try:
        claims = decode_json_base64url(encoded_payload)
    except ValueError as err:
        raise_from(InvalidToken('Could not decode token payload {}'
                                .format(encoded_payload)), err)
    # Check that signature is at least encoded properly (maybe truncated otherwise)
    try:
        decode_base64url_without_padding(encoded_signature)
    except ValueError as err:
        raise_from(InvalidToken('Could not decode token signature {} (maybe token '
                                'is truncated?)'.format(encoded_signature)), err)
    # A valid JWT must have 'alg' parameter, and be of type 'JWT' in our application
    if 'alg' not in header or header.get('typ') != 'JWT':
        raise InvalidToken('Token is not valid JWT')
    # Check whether the JWT has expired
    if 'exp' in claims:
        if claims['exp'] <= time.time():
            exp_time = time.strftime('%d-%b-%Y %H:%M:%S', time.gmtime(claims['exp']))
            raise InvalidToken('Token has expired on {} UTC'.format(exp_time))
    return claims
<commit_msg>Restore Python 2 support in token functions<commit_after>################################################################################
# Copyright (c) 2019, National Research Foundation (Square Kilometre Array)
#
# Licensed under the BSD 3-Clause License (the "License"); you may not use
# this file except in compliance with the License. You may obtain a copy
# of the License at
#
#   https://opensource.org/licenses/BSD-3-Clause
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

"""Utilities to validate and inspect bearer tokens of the S3 chunk store."""

from __future__ import print_function, division, absolute_import
from future import standard_library
standard_library.install_aliases()  # noqa: E402
from future.utils import bytes_to_native_str, raise_from

import base64
import json
import re
import time

BASE64URL_PADDING = {0: '', 2: '==', 3: '='}

class InvalidToken(ValueError):
    """Invalid JSON Web Token (JWT)."""

def encode_base64url_without_padding(b):
    """Encode bytes `b` to base64url string without padding (see RFC 7515)."""
    # Strip off padding as described in Appendix C of RFC 7515
    return bytes_to_native_str(base64.urlsafe_b64encode(b).rstrip(b'='))

def decode_base64url_without_padding(s):
    """Decode base64url-encoded string `s` without padding (RFC 7515) to bytes."""
    # Restore padding as described in Appendix C of RFC 7515
    try:
        s += BASE64URL_PADDING[len(s) % 4]
    except KeyError:
        raise ValueError('Invalid base64url-encoded string {}: number of data '
                         'characters ({}) cannot be 1 more than a multiple of 4'
                         .format(s, len(s)))
    # Use the standard base64 decoder with padding (but with base64url alphabet)
    try:
        return base64.urlsafe_b64decode(s)
    except TypeError as err:
        # In Python 2, base64 raises a TypeError if string is incorrectly padded
        raise_from(ValueError(err), err)

def encode_json_base64url(d):
    """Encode Python object `d` to JSON-serialised base64url-encoded string."""
    return encode_base64url_without_padding(json.dumps(d).encode())

def decode_json_base64url(s):
    """Decode JSON-serialised base64url-encoded string `s` (ie a JWS segment)."""
    try:
        b = decode_base64url_without_padding(s)
    except ValueError as err:
        raise_from(ValueError('Token segment {} has invalid base64url encoding'
                              .format(s)), err)
    try:
        return json.loads(b)
    except ValueError as err:
        raise_from(ValueError('Decoded token segment {} is not valid JSON'
                              .format(b)), err)

def encode_jwt(header, payload, signature):
    """Encode JSON Web Token (JWT) components to a base64url-encoded token string.

    Parameters
    ----------
    header : dict
        JSON Object Signing and Encryption (JOSE) header
    payload : dict
        JWT Claims Set
    signature : bytes
        Digital signature or MAC (calculated externally)

    Returns
    -------
    token : str
        JWT as a JWS Compact Serialization string
    """
    return '.'.join((encode_json_base64url(header),
                     encode_json_base64url(payload),
                     encode_base64url_without_padding(signature)))

def decode_jwt(token):
    """Decode JSON Web Token (JWT) string and extract claims.

    The MeerKAT archive uses JWT bearer tokens for authorisation. Each token is
    a JSON Web Signature (JWS) string with a payload of claims. This function
    extracts the claims as a dict, while also doing basic validation on the
    token. The signature is decoded but not validated, since that would require
    the server secrets.

    Parameters
    ----------
    token : str
        JWS Compact Serialization as an ASCII string

    Returns
    -------
    claims : dict
        The JWT Claims Set as a dict of key-value pairs

    Raises
    ------
    :exc:`InvalidToken`
        If token is malformed or the wrong type, or has expired
    """
    # A valid JWS Compact Serialization has multiple base64url-encoded segments
    # without padding separated by periods ('.') - see RFC 7515, RFC 4648
    if not re.match('^[A-Za-z0-9-_.]*$', token):
        raise InvalidToken("Token {} contains invalid characters not in base64url "
                           "set (or .) - are you sure it's a token?".format(token))
    # A valid JWS Compact Serialization has three segments
    try:
        encoded_header, encoded_payload, encoded_signature = token.split('.')
    except ValueError:
        raise InvalidToken("Token does not have JWS structure (maybe it's truncated?)")
    # Obtain JSON Object Signing and Encryption (JOSE) header
    try:
        header = decode_json_base64url(encoded_header)
    except ValueError as err:
        raise_from(InvalidToken('Could not decode token header {}'
                                .format(encoded_header)), err)
    # Obtain payload consisting of JWT claims
    try:
        claims = decode_json_base64url(encoded_payload)
    except ValueError as err:
        raise_from(InvalidToken('Could not decode token payload {}'
                                .format(encoded_payload)), err)
    # Check that signature is at least encoded properly (maybe truncated otherwise)
    try:
        decode_base64url_without_padding(encoded_signature)
    except ValueError as err:
        raise_from(InvalidToken('Could not decode token signature {} (maybe token '
                                'is truncated?)'.format(encoded_signature)), err)
    # A valid JWT must have 'alg' parameter, and be of type 'JWT' in our application
    if 'alg' not in header or header.get('typ') != 'JWT':
        raise InvalidToken('Token is not valid JWT')
    # Check whether the JWT has expired
    if 'exp' in claims:
        if claims['exp'] <= time.time():
            exp_time = time.strftime('%d-%b-%Y %H:%M:%S', time.gmtime(claims['exp']))
            raise InvalidToken('Token has expired on {} UTC'.format(exp_time))
    return claims
<|endoftext|>


<commit_before>import asyncio
from contextlib import redirect_stdout
import inspect
import io
import os
import sys
import textwrap
import traceback
import discord
from discord.ext import commands

# imports for REPL env
import math  # noqa: F401
import objgraph   # noqa: F401

class REPL:
    def __init__(self):
        self._last_result = None
        self.sessions = set()

    async def __local_check(self, ctx):
        return await ctx.bot.is_owner(ctx.author)

    def cleanup_code(self, content):
        """Automatically removes code blocks from the code."""
        # remove ```py\n```
        if content.startswith('```') and content.endswith('```'):
            return '\n'.join(content.split('\n')[1:-1])

        # remove `foo`
        return content.strip('` \n').lstrip('<')

    def get_syntax_error(self, e):
        return f'```py\n{e.text}{"^":>{e.offset}}\n{type(e).__name__}: {e}```'

    @commands.command(hidden=True, name='eval')
    async def eval_(self, ctx, *, body: str):
        env = {
            'author': ctx.author,
            'bot': ctx.bot,
            'ctx': ctx,
            'channel': ctx.channel,
            'guild': ctx.guild,
            'message': ctx.message,
            '_': self._last_result,
        }

        env.update(globals())

        body = self.cleanup_code(body)
        stdout = io.StringIO()

        to_compile = f'async def func():\n{textwrap.indent(body, "  ")}'

        try:
            exec(to_compile, env)
        except SyntaxError as e:
            return await ctx.send(self.get_syntax_error(e))

        func = env['func']
        try:
            with redirect_stdout(stdout):
                ret = await func()
        except Exception as e:
            value = stdout.getvalue()
            await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
        else:
            value = stdout.getvalue()

            if ret is None:
                if value:
                    await ctx.send(f'```py\n{value}\n```')
            else:
                self._last_result = ret
                await ctx.send(f'```py\n{value}{ret}\n```')

    @commands.command(hidden=True)
    async def peval(self, ctx, *, body: str):
        body = self.cleanup_code(body)
        await ctx.invoke(self.eval_, body=f'print({body})')

    @commands.command(hidden=True)
    async def repl(self, ctx):
        env = {
            'author': ctx.author,
            'bot': ctx.bot,
            'ctx': ctx,
            'channel': ctx.channel,
            'guild': ctx.guild,
            'message': ctx.message,
            '_': None,
        }

        env.update(globals())

        if ctx.channel.id in self.sessions:
            await ctx.send('Already running a REPL session in this channel. '
                           'Exit it with `quit`.')
            return

        self.sessions.add(ctx.channel.id)
        await ctx.send('Enter code to execute or evaluate. '
                       '`exit()` or `quit` to exit.')
        while True:
            response = await (
                 ctx.bot.wait_for('message',
                                  check=lambda m: m.content.startswith('<')
                                  and (m.author, m.channel) ==
                                  (ctx.author, ctx.channel)))

            cleaned = self.cleanup_code(response.content)

            if cleaned in ('quit', 'exit', 'exit()'):
                await ctx.send('Exiting.')
                self.sessions.remove(ctx.channel.id)
                return

            executor = exec
            if cleaned.count('\n') == 0:
                # single statement, potentially 'eval'
                try:
                    code = compile(cleaned, '<repl session>', 'eval')
                except SyntaxError:
                    pass
                else:
                    executor = eval

            if executor is exec:
                try:
                    code = compile(cleaned, '<repl session>', 'exec')
                except SyntaxError as e:
                    await ctx.send(self.get_syntax_error(e))
                    continue

            env['message'] = response

            fmt = None
            stdout = io.StringIO()

            try:
                with redirect_stdout(stdout):
                    result = executor(code, env)
                    if inspect.isawaitable(result):
                        result = await result
            except Exception as e:
                value = stdout.getvalue()
                fmt = f'```py\n{value}{traceback.format_exc()}\n```'
            else:
                value = stdout.getvalue()
                if result is not None:
                    fmt = f'```py\n{value}{result}\n```'
                    env['_'] = result
                elif value:
                    fmt = f'```py\n{value}\n```'

            try:
                if fmt is not None:
                    if len(fmt) > 2000:
                        await ctx.send('Content too big to be printed.')
                    else:
                        await ctx.send(fmt)
            except discord.Forbidden:
                pass
            except discord.HTTPException as e:
                await ctx.send(f'Unexpected error: `{e}`')

    @commands.command()
    async def run(self, ctx, *, command):
        proc = await asyncio.create_subprocess_shell(
            command,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE)
        await proc.wait()
        stdout = (await proc.stdout.read()).decode()
        stderr = (await proc.stderr.read()).decode()
        res = ''
        if stdout:
            res = f'Output:```\n{stdout}```'
        if stderr:
            res += f'Error:```\n{stderr}```'
        if not res:
            res = 'No result.'
        await ctx.send(res)

    @commands.command()
    async def restart(self, ctx):
        ctx.bot.logout()
        with open('main.py') as file:
            os.fsync(file)
        os.execl(sys.executable, os.path.abspath(__file__), *sys.argv)

def setup(bot):
    bot.add_cog(REPL())
<commit_msg>Use subprocess.communicate<commit_after>import asyncio
from contextlib import redirect_stdout
import inspect
import io
import os
import sys
import textwrap
import traceback
import discord
from discord.ext import commands

# imports for REPL env
import math  # noqa: F401
import objgraph   # noqa: F401

class REPL:
    def __init__(self):
        self._last_result = None
        self.sessions = set()

    async def __local_check(self, ctx):
        return await ctx.bot.is_owner(ctx.author)

    def cleanup_code(self, content):
        """Automatically removes code blocks from the code."""
        # remove ```py\n```
        if content.startswith('```') and content.endswith('```'):
            return '\n'.join(content.split('\n')[1:-1])

        # remove `foo`
        return content.strip('` \n').lstrip('<')

    def get_syntax_error(self, e):
        return f'```py\n{e.text}{"^":>{e.offset}}\n{type(e).__name__}: {e}```'

    @commands.command(hidden=True, name='eval')
    async def eval_(self, ctx, *, body: str):
        env = {
            'author': ctx.author,
            'bot': ctx.bot,
            'ctx': ctx,
            'channel': ctx.channel,
            'guild': ctx.guild,
            'message': ctx.message,
            '_': self._last_result,
        }

        env.update(globals())

        body = self.cleanup_code(body)
        stdout = io.StringIO()

        to_compile = f'async def func():\n{textwrap.indent(body, "  ")}'

        try:
            exec(to_compile, env)
        except SyntaxError as e:
            return await ctx.send(self.get_syntax_error(e))

        func = env['func']
        try:
            with redirect_stdout(stdout):
                ret = await func()
        except Exception as e:
            value = stdout.getvalue()
            await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
        else:
            value = stdout.getvalue()

            if ret is None:
                if value:
                    await ctx.send(f'```py\n{value}\n```')
            else:
                self._last_result = ret
                await ctx.send(f'```py\n{value}{ret}\n```')

    @commands.command(hidden=True)
    async def peval(self, ctx, *, body: str):
        body = self.cleanup_code(body)
        await ctx.invoke(self.eval_, body=f'print({body})')

    @commands.command(hidden=True)
    async def repl(self, ctx):
        env = {
            'author': ctx.author,
            'bot': ctx.bot,
            'ctx': ctx,
            'channel': ctx.channel,
            'guild': ctx.guild,
            'message': ctx.message,
            '_': None,
        }

        env.update(globals())

        if ctx.channel.id in self.sessions:
            await ctx.send('Already running a REPL session in this channel. '
                           'Exit it with `quit`.')
            return

        self.sessions.add(ctx.channel.id)
        await ctx.send('Enter code to execute or evaluate. '
                       '`exit()` or `quit` to exit.')
        while True:
            response = await (
                 ctx.bot.wait_for('message',
                                  check=lambda m: m.content.startswith('<')
                                  and (m.author, m.channel) ==
                                  (ctx.author, ctx.channel)))

            cleaned = self.cleanup_code(response.content)

            if cleaned in ('quit', 'exit', 'exit()'):
                await ctx.send('Exiting.')
                self.sessions.remove(ctx.channel.id)
                return

            executor = exec
            if cleaned.count('\n') == 0:
                # single statement, potentially 'eval'
                try:
                    code = compile(cleaned, '<repl session>', 'eval')
                except SyntaxError:
                    pass
                else:
                    executor = eval

            if executor is exec:
                try:
                    code = compile(cleaned, '<repl session>', 'exec')
                except SyntaxError as e:
                    await ctx.send(self.get_syntax_error(e))
                    continue

            env['message'] = response

            fmt = None
            stdout = io.StringIO()

            try:
                with redirect_stdout(stdout):
                    result = executor(code, env)
                    if inspect.isawaitable(result):
                        result = await result
            except Exception as e:
                value = stdout.getvalue()
                fmt = f'```py\n{value}{traceback.format_exc()}\n```'
            else:
                value = stdout.getvalue()
                if result is not None:
                    fmt = f'```py\n{value}{result}\n```'
                    env['_'] = result
                elif value:
                    fmt = f'```py\n{value}\n```'

            try:
                if fmt is not None:
                    if len(fmt) > 2000:
                        await ctx.send('Content too big to be printed.')
                    else:
                        await ctx.send(fmt)
            except discord.Forbidden:
                pass
            except discord.HTTPException as e:
                await ctx.send(f'Unexpected error: `{e}`')

    @commands.command()
    async def run(self, ctx, *, command):
        proc = await asyncio.create_subprocess_shell(
            command,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE)
        stdout, stderr = (text.decode() for text in await proc.communicate())
        res = ''
        if stdout:
            res = f'Output:```\n{stdout}```'
        if stderr:
            res += f'Error:```\n{stderr}```'
        if not res:
            res = 'No result.'
        await ctx.send(res)

    @commands.command()
    async def restart(self, ctx):
        ctx.bot.logout()
        with open('main.py') as file:
            os.fsync(file)
        os.execl(sys.executable, os.path.abspath(__file__), *sys.argv)

def setup(bot):
    bot.add_cog(REPL())
<|endoftext|>


<commit_before>import random
import re
from typing import Any, Mapping

import discord
from discord.ext import commands
from discord.ext.commands import Cog

from bot import BeattieBot
from context import BContext
from utils.exceptions import ResponseError

class XKCD(Cog):
    def __init__(self) -> None:
        with open("data/why.txt", encoding="utf8") as file:
            self.questions: tuple[str, ...] = tuple(file.readlines())

    @commands.command()
    async def why(self, ctx: BContext) -> None:
        """Asks a question."""
        await ctx.send(random.choice(self.questions))

    @commands.group()
    async def xkcd(self, ctx: BContext, *, inp: str = None) -> None:
        """Commands for getting xkcd comics"""
        async with ctx.typing():
            url = "https://xkcd.com/info.0.json"
            async with ctx.bot.get(url) as resp:
                self.xkcd_data = await resp.json()
            if not inp or inp == "random":
                await self.random(ctx)
            elif inp in ("latest", "current"):
                await self.latest(ctx)
            else:
                await self.comic(ctx, inp=inp)

    @xkcd.command()
    async def random(self, ctx: BContext) -> None:
        """Gets a random xkcd comic."""
        await self.comic(ctx, inp=str(random.randint(1, self.xkcd_data["num"])))

    @xkcd.command()
    async def latest(self, ctx: BContext) -> None:
        """Gets the latest xkcd comic."""
        await ctx.send(embed=format_comic(self.xkcd_data))

    @xkcd.command()
    async def comic(self, ctx: BContext, *, inp: str) -> None:
        """Gets an xkcd comic by number or content."""
        try:
            number = int(inp)
        except ValueError:
            url = "https://duckduckgo.com/html/"
            params = {"q": f"{inp} xkcd"}
            async with ctx.bot.get(url, params=params) as resp:
                text = await resp.text()
            match = re.search(r"xkcd\.com/(\d+)/\s", text)
            if match:
                number = int(match.groups()[0])
            else:
                await ctx.send("No comic found.")
                return
        else:
            if number > self.xkcd_data["num"]:
                await ctx.send("No comic found.")
                return

        url = f"https://xkcd.com/{number}/info.0.json"
        try:
            async with ctx.bot.get(url) as resp:
                data = await resp.json()
        except ResponseError:
            data = {
                "title": "404",
                "img": "http://www.explainxkcd.com/wiki/images/9/92/not_found.png",
                "alt": "Comic not found.",
                "num": 404,
            }
        await ctx.send(embed=format_comic(data))

    @commands.command(hidden=True)
    async def sudo(self, ctx: BContext, *_: Any) -> None:
        if await ctx.bot.is_owner(ctx.author):
            await ctx.send("Operation successful.")
        else:
            await ctx.send("Unable to lock /var/lib/dpkg/, are you root?")

def format_comic(data: Mapping[str, Any]) -> discord.Embed:
    embed = discord.Embed()
    embed.title = data["title"]
    embed.set_image(url=data["img"])
    embed.set_footer(text=data["alt"])
    embed.url = f"https://www.xkcd.com/{data['num']}/"
    return embed

def setup(bot: BeattieBot) -> None:
    bot.add_cog(XKCD())
<commit_msg>Correct xkcd url<commit_after>import random
import re
from typing import Any, Mapping

import discord
from discord.ext import commands
from discord.ext.commands import Cog

from bot import BeattieBot
from context import BContext
from utils.exceptions import ResponseError

class XKCD(Cog):
    def __init__(self) -> None:
        with open("data/why.txt", encoding="utf8") as file:
            self.questions: tuple[str, ...] = tuple(file.readlines())

    @commands.command()
    async def why(self, ctx: BContext) -> None:
        """Asks a question."""
        await ctx.send(random.choice(self.questions))

    @commands.group()
    async def xkcd(self, ctx: BContext, *, inp: str = None) -> None:
        """Commands for getting xkcd comics"""
        async with ctx.typing():
            url = "https://xkcd.com/info.0.json"
            async with ctx.bot.get(url) as resp:
                self.xkcd_data = await resp.json()
            if not inp or inp == "random":
                await self.random(ctx)
            elif inp in ("latest", "current"):
                await self.latest(ctx)
            else:
                await self.comic(ctx, inp=inp)

    @xkcd.command()
    async def random(self, ctx: BContext) -> None:
        """Gets a random xkcd comic."""
        await self.comic(ctx, inp=str(random.randint(1, self.xkcd_data["num"])))

    @xkcd.command()
    async def latest(self, ctx: BContext) -> None:
        """Gets the latest xkcd comic."""
        await ctx.send(embed=format_comic(self.xkcd_data))

    @xkcd.command()
    async def comic(self, ctx: BContext, *, inp: str) -> None:
        """Gets an xkcd comic by number or content."""
        try:
            number = int(inp)
        except ValueError:
            url = "https://duckduckgo.com/html/"
            params = {"q": f"{inp} xkcd"}
            async with ctx.bot.get(url, params=params) as resp:
                text = await resp.text()
            match = re.search(r"xkcd\.com/(\d+)/\s", text)
            if match:
                number = int(match.groups()[0])
            else:
                await ctx.send("No comic found.")
                return
        else:
            if number > self.xkcd_data["num"]:
                await ctx.send("No comic found.")
                return

        url = f"https://xkcd.com/{number}/info.0.json"
        try:
            async with ctx.bot.get(url) as resp:
                data = await resp.json()
        except ResponseError:
            data = {
                "title": "404",
                "img": "http://www.explainxkcd.com/wiki/images/9/92/not_found.png",
                "alt": "Comic not found.",
                "num": 404,
            }
        await ctx.send(embed=format_comic(data))

    @commands.command(hidden=True)
    async def sudo(self, ctx: BContext, *_: Any) -> None:
        if await ctx.bot.is_owner(ctx.author):
            await ctx.send("Operation successful.")
        else:
            await ctx.send("Unable to lock /var/lib/dpkg/, are you root?")

def format_comic(data: Mapping[str, Any]) -> discord.Embed:
    embed = discord.Embed()
    embed.title = data["title"]
    embed.set_image(url=data["img"])
    embed.set_footer(text=data["alt"])
    embed.url = f"https://xkcd.com/{data['num']}/"
    return embed

def setup(bot: BeattieBot) -> None:
    bot.add_cog(XKCD())
<|endoftext|>


<commit_before>from __future__ import division
import numpy as np

class UCB1(object):
    def __init__(self, c):
        self.c = c

    def __call__(self, action_node):
        if self.c == 0:  # assert that no nan values are returned for action_node.n = 0
            return action_node.q

        return (action_node.q +
                self.c * np.sqrt(2 * np.log(action_node.parent.n) /
                                 action_node.n))

def ucb1(action_node, parent, c):
    """
    The Upper Confidence Bounds computation
    :param action_node:
    :param parent:
    :param c:
    :return:
    """
    if c == 0:  # assert that no nan values are returned for action_node.n = 0
        return action_node.q

    return action_node.q + c * np.sqrt(2 * np.log(parent.n) / action_node.n)
<commit_msg>remove non-class UCB1<commit_after>from __future__ import division
import numpy as np

class UCB1(object):
    def __init__(self, c):
        self.c = c

    def __call__(self, action_node):
        if self.c == 0:  # assert that no nan values are returned
                        # for action_node.n = 0
            return action_node.q

        return (action_node.q +
                self.c * np.sqrt(2 * np.log(action_node.parent.n) /
                                 action_node.n))
<|endoftext|>


<commit_before>#!/usr/bin/env python2.7
#
'''Collector - Test results collector

Collector scans a test project directory, parses all result files found and
saves all parsed results as a self-described data sheet in a file. One can then
use Analyser or other tools to investigate the resultant data sheet.

To use collector, simply following the argument document. To use the generated
data sheet, keep in mind that the data sheet is ralational database table alike
and the concrete format is backend specific. For sqlite3 backend, the result is
stored in a table named 'result'. The data sheet is designed to be easily
parsable by pandas, so the recommendation is to use pandas to investigate the
data.
'''

import os
import sys
import re
import string
import argparse
import json
import sqlite3
from collections import OrderedDict

class TestProjectScanner:
    '''TestProjectScanner - Scan a test project for test cases'''

    def __init__(self, project_root):
        '''Create a scanner object for project at 'project_dir' '''
        self.project_root = os.path.abspath(project_root)
        conf_fn = os.path.join(self.project_root, "TestConfig.json")
        if not os.path.exists(conf_fn):
            raise RuntimeError("Invalid project directory: %s" % project_root)
        conf = json.load(file(conf_fn))
        assert "project" in conf
        project_info = conf["project"]
        self.dim_names = project_info["dimensions"]

    def scan(self):
        '''Scan the project directory and return a list of test cases

        Return: A dict containing the following fields:
            {'root': string, 'dim_names': list, 'cases': list}

        Each case in 'cases' is the following dict:
            {'project_root': string, 'vpath': OrderedDict, 'spec': dict}
        '''
        test_cases = []

        def do_scan(vpath, level):
            curr_dir = os.path.join(self.project_root, *vpath.values())
            conf_fn = os.path.join(curr_dir, "TestConfig.json")
            assert os.path.exists(conf_fn)
            conf = json.load(file(conf_fn))
            if level == len(self.dim_names):
                assert "test_case" in conf
                case = {
                    "spec": conf["test_case"],
                    "vpath": vpath,
                    "project_root": self.project_root
                }
                test_cases.append(case)
            else:
                assert "sub_directories" in conf
                dirs = conf["sub_directories"]
                dir_list = []
                if isinstance(dirs, list):
                    dir_list = dirs
                elif isinstance(dirs, dict):
                    assert "directories" in dirs
                    dir_list = dirs["directories"]
                else:
                    raise RuntimeError("Invalid sub_directory spec")
                for path in dir_list:
                    new_vpath = OrderedDict(vpath)
                    new_vpath[self.dim_names[level]] = path
                    do_scan(new_vpath, level + 1)

        do_scan(OrderedDict(), 0)
        return {
            "root": self.project_root,
            "dim_names": self.dim_names,
            "cases": test_cases
        }

def parse_jasminlog(fn):
    '''parse_jasminlog - jasmin time manager log parser

    This function parses jasmin timer manager performance reports. It
    splits detected jasmin timer logs into several measures and assign
    them unique names.

    Assume the following log::

      ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      TOTAL
      WALLCLOCK TIME
      ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      Timer Name                                       Proc: 0     Max
      alg::NumericalIntegratorComponent::computing()   3.55 (100%) 7.3(90%)
      ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    Examples
    --------

    The use of this function is fairly straight forward:
    >>> info = parse_jasminlog("demo_jasminlog.txt")
    >>> info[-1]["data"][0]["timer_name"]
    TOTAL_RUN_TIME
    >>> info[-1]["data"][0]["summed"]
    5.14626

    '''

    def tokenlize(s):
        '''Convert string into a valid lower case pythonic token'''
        invalid_chars = ":-+*/#\n"
        trans_table = string.maketrans(invalid_chars, " " * len(invalid_chars))
        return "_".join(map(string.lower, s.translate(trans_table).split()))

    avail_types = {
        "timer_name": str,
        "proc_0": float,
        "proc_0_percent": float,
        "summed": float,
        "summed_percent": float,
        "max": float,
        "max_percent": float,
        "proc": int
    }
    content = file(fn, "r").read()
    logtbl_ptn = re.compile(
        "^\+{80}$(?P<name>.*?)^\+{80}$" + ".*?(?P<header>^.*?$)" +
        "(?P<content>.*?)^\+{80}$", re.M + re.S)
    result = []
    for match in logtbl_ptn.finditer(content):
        log_table = match.groupdict()
        table_name = tokenlize(log_table["name"])
        # We only handle table "TOTAL WALLCLOCK TIME"
        if table_name != "total_wallclock_time":
            continue
        # Extract table header
        header_ptn = re.compile(r"(Timer Name|Proc: \d+|Summed|Proc|Max)")
        header = map(tokenlize, header_ptn.findall(log_table["header"]))
        # Parse table rows
        table_contents = []
        for ln in log_table["content"].strip().split("\n"):
            rec_ptn = re.compile(r"^\s*(TOTAL RUN TIME:|\S+)\s*(.*)$")
            tl, tr = rec_ptn.search(ln).groups()
            timer_name = tl.strip()
            if timer_name == "TOTAL RUN TIME:":
                timer_name = "TOTAL_RUN_TIME"
            timer_rec = {"timer_name": avail_types["timer_name"](timer_name)}
            flt_ptn = r"[-+]?(?:\d+(?:\.\d*)?)(?:[Ee][-+]?\d+)?"
            seg_ptn = re.compile(r"({0})\s*(\({0}%\))?".format(flt_ptn))
            for i, seg in enumerate(seg_ptn.finditer(tr)):
                # Example: 99.9938 (97%)
                a, b = seg.groups()
                cn = header[i + 1]
                timer_rec[cn] = avail_types[cn](a)
                if b:
                    pn = "{0}_percent".format(cn)
                    timer_rec[pn] = avail_types[pn](b[1:-2]) * 0.01
            table_contents.append(timer_rec)
        # Fix table header when there are XX% records
        for k in table_contents[0].iterkeys():
            if k not in header:
                header.append(k)
        # Make final result
        types = {x: avail_types[x] for x in header}
        table = {
            "columns": header,
            "column_types": types,
            "data": table_contents
        }
        result.append(table)
    return result

class JasminParser:
    def parse(self, fn):
        return parse_jasminlog(fn)

def parse_jasmin4log(fn):
    '''parse_jasmin4log - jasmin 4.0 time manager log parser

    This function parses jasmin 4.0 timer manager performance reports. It
    splits detected jasmin timer logs into several measures and assign
    them unique names.

    Assume the following log::

    *************************** TIME STATISTICS  ****************************
    -------------------------------------------------------------------------
                                                       Name           MaxTime
    -------------------------------------------------------------------------
                                             TOTAL RUN TIME   0.9065(100.00%)
    algs::SimpleHierarchyTimeIntegrator::advanceHierarchy()    0.8624(95.14%)
    -------------------------------------------------------------------------

    Examples
    --------

    The use of this function is fairly straight forward:
    >>> info = parse_jasmin4log("demo_jasmin4log.txt")
    >>> info[-1]["data"][0]["Name"]
    TOTAL RUN TIME
    >>> info[-1]["data"][0]["Accesses"]
    1

    '''

    avail_types = {
        "Name": str,
        "MaxTime": float,
        "MaxTime_percent": float,
        "AvgTime": float,
        "AvgTime_percent": float,
        "LoadBalance": float,
        "Accesses": int,
        "Overhead": float
    }
    content = file(fn, "r").read()
    logtbl_ptn = re.compile(
        r"^\*+ (?P<name>.*?) \*+$\n-{10,}\n" + r"^(?P<header>^.*?$)\n-{10,}\n"
        + r"(?P<content>.*?)^-{10,}\n", re.M + re.S)
    result = []
    for match in logtbl_ptn.finditer(content):
        log_table = match.groupdict()
        table_name = log_table["name"]
        # Extract table header
        header = log_table["header"].split()
        # Parse table rows
        table_contents = []
        for ln in log_table["content"].strip().split("\n"):
            timer_rec = {}
            seg_ptn = re.compile(r"(TOTAL RUN TIME|\S+)")
            for i, seg in enumerate(seg_ptn.finditer(ln)):
                cn = header[i]
                val = seg.group(1)
                flt_ptn = r"[-+]?(?:\d+(?:\.\d*)?)(?:[Ee][-+]?\d+)?|[+-]?nan"
                m = re.match(r"({0})\(({0})%\)".format(flt_ptn), val)
                if m:
                    pn = "{0}_percent".format(cn)
                    a, b = map(float, [m.group(1), m.group(2)])
                    b = b * 0.01
                    timer_rec[cn], timer_rec[pn] = a, b
                    continue
                m = re.match(r"({0})%".format(flt_ptn), val)
                if m:
                    timer_rec[cn] = float(m.group(1)) * 0.01
                    continue
                timer_rec[cn] = avail_types[cn](val)
            table_contents.append(timer_rec)
        # Fix table header when there are XX% records
        for k in table_contents[0].iterkeys():
            if k not in header:
                header.append(k)
        # Make final result
        types = {x: avail_types[x] for x in header}
        table = {
            "columns": header,
            "column_types": types,
            "data": table_contents
        }
        result.append(table)
    return result

class Jasmin4Parser:
    def parse(self, fn):
        return parse_jasmin4log(fn)

class UnifiedJasminParser:
    def __init__(self):
        jasmin4_ptn = re.compile(
            r"^\*+ (?P<name>.*?) \*+$\n-{10,}\n" +
            r"^(?P<header>^.*?$)\n-{10,}\n" + r"(?P<content>.*?)^-{10,}\n",
            re.M + re.S)
        jasmin3_ptn = re.compile(
            "^\+{80}$(?P<name>.*?)^\+{80}$" + ".*?(?P<header>^.*?$)" +
            "(?P<content>.*?)^\+{80}$", re.M + re.S)

        def detector(fn):
            content = file(fn).read()
            if jasmin3_ptn.search(content):
                return "jasmin3"
            elif jasmin4_ptn.search(content):
                return "jasmin4"
            else:
                msg = "File %s is neither jasmin 3 nor jasmin 4 log" % fn
                raise RuntimeError(msg)

        self.filetype_detector = detector
        self.parser_funcs = {
            "jasmin3": parse_jasminlog,
            "jasmin4": parse_jasmin4log
        }

    def parse(self, fn):
        return self.parser_funcs[self.filetype_detector(fn)](fn)

class SqliteSerializer:
    typemap = {
        None: "NULL",
        int: "INTEGER",
        long: "INTEGER",
        float: "REAL",
        str: "TEXT",
        unicode: "TEXT",
        buffer: "BLOB"
    }

    def __init__(self, db):
        conn = sqlite3.connect(db)
        obj = conn.execute("DROP TABLE IF EXISTS result")
        conn.commit()
        self.conn = conn

    def serialize(self, columns, column_types, data):
        # Build table creation and insertion SQL statements
        table_columns = []
        for c in columns:
            t = column_types.get(c, str)
            assert t in SqliteSerializer.typemap
            tn = SqliteSerializer.typemap[t]
            table_columns.append("{0} {1}".format(c, tn))
        table_columns_sql = ", ".join(table_columns)
        create_table_sql = "CREATE TABLE result ({0})".format(
            table_columns_sql)
        ph_sql = ", ".join(["?"] * len(columns))
        insert_row_sql = "INSERT INTO result VALUES ({0})".format(ph_sql)
        # Create table and insert data items
        cur = self.conn.cursor()
        cur.execute(create_table_sql)
        for item in data:
            assert isinstance(item, list) or isinstance(item, tuple)
            assert len(item) == len(columns)
            cur.execute(insert_row_sql, item)
        self.conn.commit()
        self.conn.close()

class TestResultCollector:
    '''TestResultCollector - Collect test results and save them'''

    def __init__(self, serializer):
        self.serializer = serializer

    def collect(self, project_root, parser):
        project_info = TestProjectScanner(project_root).scan()
        all_results = []
        for case in project_info["cases"]:
            cwd = os.path.join(case["project_root"], *case["vpath"].values())
            # TODO: support collecting from multiple result file
            result_fn = os.path.join(cwd, case["spec"]["results"][0])
            if not os.path.exists(result_fn):
                continue
            content = parser.parse(result_fn)
            all_results.append((case["vpath"], content))

        def data_item_generator():
            for vpath, content in all_results:
                # Only use the last record, TODO: use all records
                data = content[-1]
                for row in data["data"]:
                    row_list = [row.get(x, None) for x in data["columns"]]
                    yield vpath.values() + row_list

        ref_vpath, ref_content = all_results[0]
        columns = ref_vpath.keys()
        column_types = {x: type(ref_vpath[x]) for x in ref_vpath.keys()}
        for c in ref_content[0]["columns"]:
            columns.append(c)
            column_types[c] = ref_content[0]["column_types"].get(c, str)

        self.serializer.serialize(columns, column_types, data_item_generator())

def make_parser(name, *args, **kwargs):
    if name == "jasmin3":
        return JasminParser(*args, **kwargs)
    elif name == "jasmin4":
        return Jasmin4Parser(*args, **kwargs)
    elif name == "jasmin":
        return UnifiedJasminParser(*args, **kwargs)
    else:
        raise ValueError("Unsupported parser: %s" % name)

def make_serializer(name, *args, **kwargs):
    if name == "sqlite3":
        return SqliteSerializer(*args, **kwargs)
    else:
        raise ValueError("Unsupported serializer: %s" % name)

def main():
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("project_root", help="Test project root directory")
    parser.add_argument("data_file", help="Data file to save results")
    parser.add_argument("--serializer",
                        choices=["sqlite3"],
                        default="sqlite3",
                        help="Serializer to dump results (default: sqlite3)")
    parser.add_argument("--parser",
                        choices=["jasmin3", "jasmin4", "jasmin"],
                        default="jasmin",
                        help="Parser for raw result files (default: jasmin)")

    args = parser.parse_args()
    parser = make_parser(args.parser)
    serializer = make_serializer(args.serializer, args.data_file)
    collector = TestResultCollector(serializer)
    collector.collect(args.project_root, parser)

if __name__ == "__main__":
    main()
<commit_msg>Refactor collector<commit_after>#!/usr/bin/env python2.7
#
'''Collector - Test results collector

Collector scans a test project directory, parses all result files found and
saves all parsed results as a self-described data sheet in a file. One can then
use Analyser or other tools to investigate the resultant data sheet.

To use collector, simply following the argument document. To use the generated
data sheet, keep in mind that the data sheet is ralational database table alike
and the concrete format is backend specific. For sqlite3 backend, the result is
stored in a table named 'result'. The data sheet is designed to be easily
parsable by pandas, so the recommendation is to use pandas to investigate the
data.
'''

import os
import sys
import re
import string
import argparse
import json
import sqlite3
from collections import OrderedDict

class TestProjectReader:

    '''Scan a test project for test cases'''

    def __init__(self, project_root):
        '''Create a scanner object for project at 'project_dir' '''
        self.project_root = os.path.abspath(project_root)
        conf_fn = os.path.join(self.project_root, "TestProject.json")
        if not os.path.exists(conf_fn):
            raise RuntimeError("Invalid project directory: %s" % project_root)
        conf = json.load(file(conf_fn))
        self.name = conf["name"]
        self.test_factors = conf["test_factors"]
        self.data_files = conf["data_files"]
        test_cases = conf["test_cases"]
        self.test_cases = zip(test_cases["test_vectors"],
                              test_cases["case_paths"])

    def itercases(self):
        for test_vector, case_path in self.test_cases:
            case_spec_fullpath = os.path.join(
                self.project_root,
                case_path,
                "TestCase.json")
            case_spec = json.load(file(case_spec_fullpath))
            yield {
                "case_path": os.path.join(self.project_root, case_path),
                "run_spec": case_spec,
                "test_vector": test_vector
            }

def parse_jasminlog(fn):
    '''parse_jasminlog - jasmin time manager log parser

    This function parses jasmin timer manager performance reports. It
    splits detected jasmin timer logs into several measures and assign
    them unique names.

    Assume the following log::

      ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      TOTAL
      WALLCLOCK TIME
      ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      Timer Name                                       Proc: 0     Max
      alg::NumericalIntegratorComponent::computing()   3.55 (100%) 7.3(90%)
      ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    Examples
    --------

    The use of this function is fairly straight forward:
    >>> info = parse_jasminlog("demo_jasminlog.txt")
    >>> info[-1]["data"][0]["timer_name"]
    TOTAL_RUN_TIME
    >>> info[-1]["data"][0]["summed"]
    5.14626

    '''

    def tokenlize(s):
        '''Convert string into a valid lower case pythonic token'''
        invalid_chars = ":-+*/#\n"
        trans_table = string.maketrans(invalid_chars, " " * len(invalid_chars))
        return "_".join(map(string.lower, s.translate(trans_table).split()))

    avail_types = {
        "timer_name": str,
        "proc_0": float,
        "proc_0_percent": float,
        "summed": float,
        "summed_percent": float,
        "max": float,
        "max_percent": float,
        "proc": int
    }
    content = file(fn, "r").read()
    logtbl_ptn = re.compile(
        "^\+{80}$(?P<name>.*?)^\+{80}$" + ".*?(?P<header>^.*?$)" +
        "(?P<content>.*?)^\+{80}$", re.M + re.S)
    result = []
    for match in logtbl_ptn.finditer(content):
        log_table = match.groupdict()
        table_name = tokenlize(log_table["name"])
        # We only handle table "TOTAL WALLCLOCK TIME"
        if table_name != "total_wallclock_time":
            continue
        # Extract table header
        header_ptn = re.compile(r"(Timer Name|Proc: \d+|Summed|Proc|Max)")
        header = map(tokenlize, header_ptn.findall(log_table["header"]))
        # Parse table rows
        table_contents = []
        for ln in log_table["content"].strip().split("\n"):
            rec_ptn = re.compile(r"^\s*(TOTAL RUN TIME:|\S+)\s*(.*)$")
            tl, tr = rec_ptn.search(ln).groups()
            timer_name = tl.strip()
            if timer_name == "TOTAL RUN TIME:":
                timer_name = "TOTAL_RUN_TIME"
            timer_rec = {"timer_name": avail_types["timer_name"](timer_name)}
            flt_ptn = r"[-+]?(?:\d+(?:\.\d*)?)(?:[Ee][-+]?\d+)?"
            seg_ptn = re.compile(r"({0})\s*(\({0}%\))?".format(flt_ptn))
            for i, seg in enumerate(seg_ptn.finditer(tr)):
                # Example: 99.9938 (97%)
                a, b = seg.groups()
                cn = header[i + 1]
                timer_rec[cn] = avail_types[cn](a)
                if b:
                    pn = "{0}_percent".format(cn)
                    timer_rec[pn] = avail_types[pn](b[1:-2]) * 0.01
            table_contents.append(timer_rec)
        # Fix table header when there are XX% records
        for k in table_contents[0].iterkeys():
            if k not in header:
                header.append(k)
        # Make final result
        types = {x: avail_types[x] for x in header}
        table = {
            "columns": header,
            "column_types": types,
            "data": table_contents
        }
        result.append(table)
    return result

class JasminParser:
    def parse(self, fn):
        return parse_jasminlog(fn)

def parse_jasmin4log(fn):
    '''parse_jasmin4log - jasmin 4.0 time manager log parser

    This function parses jasmin 4.0 timer manager performance reports. It
    splits detected jasmin timer logs into several measures and assign
    them unique names.

    Assume the following log::

    *************************** TIME STATISTICS  ****************************
    -------------------------------------------------------------------------
                                                       Name           MaxTime
    -------------------------------------------------------------------------
                                             TOTAL RUN TIME   0.9065(100.00%)
    algs::SimpleHierarchyTimeIntegrator::advanceHierarchy()    0.8624(95.14%)
    -------------------------------------------------------------------------

    Examples
    --------

    The use of this function is fairly straight forward:
    >>> info = parse_jasmin4log("demo_jasmin4log.txt")
    >>> info[-1]["data"][0]["Name"]
    TOTAL RUN TIME
    >>> info[-1]["data"][0]["Accesses"]
    1

    '''

    avail_types = {
        "Name": str,
        "MaxTime": float,
        "MaxTime_percent": float,
        "AvgTime": float,
        "AvgTime_percent": float,
        "LoadBalance": float,
        "Accesses": int,
        "Overhead": float
    }
    content = file(fn, "r").read()
    logtbl_ptn = re.compile(
        r"^\*+ (?P<name>.*?) \*+$\n-{10,}\n" + r"^(?P<header>^.*?$)\n-{10,}\n"
        + r"(?P<content>.*?)^-{10,}\n", re.M + re.S)
    result = []
    for match in logtbl_ptn.finditer(content):
        log_table = match.groupdict()
        table_name = log_table["name"]
        # Extract table header
        header = log_table["header"].split()
        # Parse table rows
        table_contents = []
        for ln in log_table["content"].strip().split("\n"):
            timer_rec = {}
            seg_ptn = re.compile(r"(TOTAL RUN TIME|\S+)")
            for i, seg in enumerate(seg_ptn.finditer(ln)):
                cn = header[i]
                val = seg.group(1)
                flt_ptn = r"[-+]?(?:\d+(?:\.\d*)?)(?:[Ee][-+]?\d+)?|[+-]?nan"
                m = re.match(r"({0})\(({0})%\)".format(flt_ptn), val)
                if m:
                    pn = "{0}_percent".format(cn)
                    a, b = map(float, [m.group(1), m.group(2)])
                    b = b * 0.01
                    timer_rec[cn], timer_rec[pn] = a, b
                    continue
                m = re.match(r"({0})%".format(flt_ptn), val)
                if m:
                    timer_rec[cn] = float(m.group(1)) * 0.01
                    continue
                timer_rec[cn] = avail_types[cn](val)
            table_contents.append(timer_rec)
        # Fix table header when there are XX% records
        for k in table_contents[0].iterkeys():
            if k not in header:
                header.append(k)
        # Make final result
        types = {x: avail_types[x] for x in header}
        table = {
            "columns": header,
            "column_types": types,
            "data": table_contents
        }
        result.append(table)
    return result

class Jasmin4Parser:
    def parse(self, fn):
        return parse_jasmin4log(fn)

class UnifiedJasminParser:
    def __init__(self):
        jasmin4_ptn = re.compile(
            r"^\*+ (?P<name>.*?) \*+$\n-{10,}\n" +
            r"^(?P<header>^.*?$)\n-{10,}\n" + r"(?P<content>.*?)^-{10,}\n",
            re.M + re.S)
        jasmin3_ptn = re.compile(
            "^\+{80}$(?P<name>.*?)^\+{80}$" + ".*?(?P<header>^.*?$)" +
            "(?P<content>.*?)^\+{80}$", re.M + re.S)

        def detector(fn):
            content = file(fn).read()
            if jasmin3_ptn.search(content):
                return "jasmin3"
            elif jasmin4_ptn.search(content):
                return "jasmin4"
            else:
                msg = "File %s is neither jasmin 3 nor jasmin 4 log" % fn
                raise RuntimeError(msg)

        self.filetype_detector = detector
        self.parser_funcs = {
            "jasmin3": parse_jasminlog,
            "jasmin4": parse_jasmin4log
        }

    def parse(self, fn):
        return self.parser_funcs[self.filetype_detector(fn)](fn)

class SqliteSerializer:
    typemap = {
        None: "NULL",
        int: "INTEGER",
        long: "INTEGER",
        float: "REAL",
        str: "TEXT",
        unicode: "TEXT",
        buffer: "BLOB"
    }

    def __init__(self, db):
        conn = sqlite3.connect(db)
        obj = conn.execute("DROP TABLE IF EXISTS result")
        conn.commit()
        self.conn = conn

    def serialize(self, columns, column_types, data):
        # Build table creation and insertion SQL statements
        table_columns = []
        for c in columns:
            t = column_types.get(c, str)
            assert t in SqliteSerializer.typemap
            tn = SqliteSerializer.typemap[t]
            table_columns.append("{0} {1}".format(c, tn))
        table_columns_sql = ", ".join(table_columns)
        create_table_sql = "CREATE TABLE result ({0})".format(
            table_columns_sql)
        ph_sql = ", ".join(["?"] * len(columns))
        insert_row_sql = "INSERT INTO result VALUES ({0})".format(ph_sql)
        # Create table and insert data items
        cur = self.conn.cursor()
        cur.execute(create_table_sql)
        for item in data:
            assert isinstance(item, list) or isinstance(item, tuple)
            assert len(item) == len(columns)
            cur.execute(insert_row_sql, item)
        self.conn.commit()
        self.conn.close()

class TestResultCollector:
    '''TestResultCollector - Collect test results and save them'''

    def __init__(self, serializer):
        self.serializer = serializer

    def collect(self, project_root, parser):
        project = TestProjectReader(project_root)
        all_results = []
        for case in project.itercases():
            case_path = case["case_path"]
            # TODO: support collecting from multiple result file
            result_fn = os.path.join(case_path, case["run_spec"]["results"][0])
            if not os.path.exists(result_fn):
                continue
            content = parser.parse(result_fn)
            all_results.append((case["test_vector"], content))

        def data_item_generator():
            for test_vector, content in all_results:
                # Only use the last record, TODO: use all records
                data = content[-1]
                for row in data["data"]:
                    row_list = [row.get(x, None) for x in data["columns"]]
                    yield test_vector.values() + row_list

        ref_vector, ref_content = all_results[0]
        columns = ref_vector.keys()
        column_types = {x: type(ref_vector[x]) for x in ref_vector.keys()}
        for c in ref_content[0]["columns"]:
            columns.append(c)
            column_types[c] = ref_content[0]["column_types"].get(c, str)

        self.serializer.serialize(columns, column_types, data_item_generator())

def make_parser(name, *args, **kwargs):
    if name == "jasmin3":
        return JasminParser(*args, **kwargs)
    elif name == "jasmin4":
        return Jasmin4Parser(*args, **kwargs)
    elif name == "jasmin":
        return UnifiedJasminParser(*args, **kwargs)
    else:
        raise ValueError("Unsupported parser: %s" % name)

def make_serializer(name, *args, **kwargs):
    if name == "sqlite3":
        return SqliteSerializer(*args, **kwargs)
    else:
        raise ValueError("Unsupported serializer: %s" % name)

def main():
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("project_root", help="Test project root directory")
    parser.add_argument("data_file", help="Data file to save results")
    parser.add_argument("--serializer",
                        choices=["sqlite3"],
                        default="sqlite3",
                        help="Serializer to dump results (default: sqlite3)")
    parser.add_argument("--parser",
                        choices=["jasmin3", "jasmin4", "jasmin"],
                        default="jasmin",
                        help="Parser for raw result files (default: jasmin)")

    args = parser.parse_args()
    parser = make_parser(args.parser)
    serializer = make_serializer(args.serializer, args.data_file)
    collector = TestResultCollector(serializer)
    collector.collect(args.project_root, parser)

if __name__ == "__main__":
    main()
<|endoftext|>


<commit_before>"""
Meraki Devices API Resource
"""

import urllib
from .meraki_api_resource import MerakiAPIResource
from .switch_ports import SwitchPorts
from .utils import clean

class Devices(MerakiAPIResource):
    """ Meraki API Devices resource. """

    resource = "devices"

    parameters = ["name", "tags", "lat", "lng", "address", "moveMapMarker"]

    clients_parameters = ["timespan"]

    claim_parameters = ["serial"]

    def __init__(self, key, prefix=None, resource_id=None):
        MerakiAPIResource.__init__(self, key, prefix, resource_id)

    def switch_ports(self, switch_port_id=None):
        """ Returns a Device Switch Ports API Resource. """
        self.check_for_resource_id()
        return SwitchPorts(self.key, self.endpoint(), switch_port_id)

    def clients(self, query):
        """
        List the clients of a device, up to a maximum of a month ago. The
        usage of each client is returned in kilobytes. If the device is a
        switch, the switchport is returned; otherwise the switchport field
        is null.
        """
        if query is None:
            raise ValueError("You must set the timespan query value.")
        query = clean(query, self.clients_parameters)
        return self.get("/clients?" + urllib.parse.urlencode(query))

    def uplink(self):
        """ Return uplink status. """
        if self.resource_id is None:
            raise ValueError("Cant't call this endpoint if the serial is not\
defined")
        return self.get("/uplink")

    def claim(self, query):
        """ Claim a device. """
        if self.resource_id is not None:
            raise ValueError("Can't claim a device already assigned.")
        if query is None or query.get("serial") is None:
            raise ValueError("Can't claim a device without its serial.")
        query = clean(query, self.claim_parameters)
        return self.post("/claim", query)

    def remove(self):
        """ Remove a single device. """
        if self.resource_id is None:
            raise ValueError("Can't remove a device without its serial.")
        return self.post("/remove")
<commit_msg>Agregated the loss and Latency History Feature<commit_after>"""
Meraki Devices API Resource
"""

import urllib
from .meraki_api_resource import MerakiAPIResource
from .switch_ports import SwitchPorts
from .utils import clean

class Devices(MerakiAPIResource):
    """ Meraki API Devices resource. """

    resource = "devices"

    parameters = ["name", "tags", "lat", "lng", "address", "moveMapMarker"]

    clients_parameters = ["timespan"]

    claim_parameters = ["serial"]

    def __init__(self, key, prefix=None, resource_id=None):
        MerakiAPIResource.__init__(self, key, prefix, resource_id)

    def switch_ports(self, switch_port_id=None):
        """ Returns a Device Switch Ports API Resource. """
        self.check_for_resource_id()
        return SwitchPorts(self.key, self.endpoint(), switch_port_id)

    def clients(self, query):
        """
        List the clients of a device, up to a maximum of a month ago. The
        usage of each client is returned in kilobytes. If the device is a
        switch, the switchport is returned; otherwise the switchport field
        is null.
        """
        if query is None:
            raise ValueError("You must set the timespan query value.")
        query = clean(query, self.clients_parameters)
        return self.get("/clients?" + urllib.parse.urlencode(query))

    def uplink(self):
        """ Return uplink status. """
        if self.resource_id is None:
            raise ValueError("Cant't call this endpoint if the serial is not\
defined")
        return self.get("/uplink")

    def lossAndLatencyHistory(self, query):
        "Get the uplink loss percentage and latency in milliseconds for a wired network device."
        if query is None:
            raise ValueError("You must set the timespan query value.")
        query = clean(query, self.clients_parameters)
        return self.get("/lossAndLatencyHistory?" + urllib.parse.urlencode(query) + "&ip=8.8.8.8&uplink=wan1")

    def claim(self, query):
        """ Claim a device. """
        if self.resource_id is not None:
            raise ValueError("Can't claim a device already assigned.")
        if query is None or query.get("serial") is None:
            raise ValueError("Can't claim a device without its serial.")
        query = clean(query, self.claim_parameters)
        return self.post("/claim", query)

    def remove(self):
        """ Remove a single device. """
        if self.resource_id is None:
            raise ValueError("Can't remove a device without its serial.")
        return self.post("/remove")
<|endoftext|>