__author__ = 'eugenel'
import json
import httplib
# internal project configuration
import config

def quote_quick(venue, stock):
    connection = httplib.HTTPSConnection(config.site)
    connection.request("GET", "/ob/api/venues/"+venue+"/stocks/"+stock+"/quote", headers=config.head)
    response = connection.getresponse()
    result = None
    if response.status == 200:
        result = json.loads(response.read())
    connection.close()
    return response.status, result

# Get Order status returns true if closed and false if still open
def get_order_status(venue, stock, orderID):
    result = None
    connection = httplib.HTTPSConnection(config.site)
    connection.request("GET", "/ob/api/venues/"+venue+"/stocks/"+stock+"/orders/"+str(orderID), headers=config.head)
    response=connection.getresponse()
    if response.status == 200:
        result=json.loads(response.read())
        connection.close()
        if not result['open']:
            return True, result
    else:
        connection.close()
    return False, result

def set_order(venue, stock, account, price, qty, direction="buy", order_type="limit"):
    connection = httplib.HTTPSConnection(config.site)
    order = {"account":account,"price":price,"qty":qty,"direction":direction,"orderType":order_type}
    connection.request("POST","/ob/api/venues/"+venue+"/stocks/"+stock+"/orders",json.dumps(order),config.head)
    response = connection.getresponse()
    result = None
    if response.status == 200:
        result = json.loads(response.read())
    connection.close()
    return response.status, result

def cancel_order(venue, stock, orderID):
    connection = httplib.HTTPSConnection(config.site)
    connection.request("DELETE", "/ob/api/venues/"+venue+"/stocks/"+stock+"/orders/"+str(orderID), headers=config.head)
    response=connection.getresponse()
    result = None
    if response.status == 200:
        result=json.loads(response.read())
    connection.close()
    return response.status, result


person1 = "Gerard"
person2 = "Finchy"

print(person1)
print(person2)
#> can be hard to guess which one is person1 and person2 when checking the output

#this can be a solution, but it is tedious
print("person1:", person1)
print("person2:", person2)

#time for some icecream: a Python library that makes print debugging more readable with minimal code.
#icecream will show the output, the function and its arguments
# pip install icecream

from icecream import ic

def power_up(num,pow):
    return num **pow

ic(power_up(5,2))
ic(power_up(4,4))

#checking code execution without the need to add prints

def welcomeMaster(sithLord:"Herpoelaert"):
    if sithLord=="Herpoelaert":
        print("Welcome, master")
    else:
        print("Do not enter this realm")

welcomeMaster("Herpoelaert")

def welcomeIceCreamMaster(sithLord:"Herpoelaert"):
    if sithLord=="Herpoelaert":
        ic()
    else:
        ic()
welcomeIceCreamMaster("Herpoelaert") #you will see that line 36 has been executed

#adding a custom icecream prefix (eg time, ie the sprinkles on the icecream)
from datetime import datetime
import time

#mistake: this will set the prefix once, so each timestamp will be the same
ic.configureOutput(prefix=f'{datetime.now()}|>>> ')

time.sleep(3)
welcomeIceCreamMaster("Herpoelaert")
time.sleep(5)
welcomeIceCreamMaster("Duyck")

#setting up a dynamic prefix
def getnow():
    return f'{datetime.now()}>>>'

ic.configureOutput(prefix=getnow)
time.sleep(3)
welcomeIceCreamMaster("Herpoelaert")
time.sleep(5)
welcomeIceCreamMaster("Duyck")

#adding more context:
ic.configureOutput(includeContext=True)
time.sleep(2)
welcomeIceCreamMaster("Duyck")


from paquete import operaciones

def main():
    sumar = operaciones.suma(3, 5)
    restar = operaciones.resta(7, 2)
    multiplicar = operaciones.mult(4, 5)
    dividir = operaciones.div(15, 3)
    print(f"suma: {sumar}\nresta: {restar}\nmultiplicacion: {multiplicar}\ndivision: {dividir}")

main()


#!/usr/bin/python

# LIBRARY IMPORTS
from scapy.all import *
from sys import *
from math import *

# Jack Cardwell
# March 27, 2018

# automation.py is a script that will automate the finding of parser differentials...it crafts IPv6
# packets, without regard to how nonsensical some of the meanings are

# generate a random order of extension headers
def createExtHeaders(num):
    options = [0,60,43,44]  # contains all of the next header IDs

    sample = random.sample(options,num) # select a random number of them

    hdr = None

    # create the first
    if sample[0] ==  0:
        hdr = IPv6ExtHdrHopByHop()
    elif sample[0] == 60:
        hdr= IPv6ExtHdrDestOpt()
    elif sample[0] == 43:
        hdr = IPv6ExtHdrRouting()
    elif sample[0] == 44:
        hdr = IPv6ExtHdrFragment()

    # create the chained headers
    for pos in sample[1:]:

        if pos ==  0:
            hdr = hdr/IPv6ExtHdrHopByHop()
        elif pos == 60:
            hdr= hdr/IPv6ExtHdrDestOpt()
        elif pos == 43:
            hdr = hdr/IPv6ExtHdrRouting()
        elif pos == 44:
            hdr = hdr/IPv6ExtHdrFragment()

    # return the chained headers
    return hdr

# the helper method that will generate the IPv6 packets
def generatePackets(argv):

    # verify the arguments, should be a source and a destination
    if len(argv)!=3:
        print("Error: Usage: ./ipv6fuzz [SOURCE] [TGT]")
        exit(2)

    src = argv[1]
    dst = argv[2]

    # at this point attempt to run some baseline tests to benchmark the stack's response

    # BASE CASE: ping
    ping = IPv6(src=src,dst=dst)/ICMPv6EchoRequest()
    print(ping.summary())
    send(ping)

    # TEST CASE 1: chained extension headers
    for i in range(1,5):
        hdrs = createExtHeaders(i)

        print("Header Sent: "+ hdrs.summary())
        case1 = IPv6(src=src,dst=dst)/hdrs/ICMPv6EchoRequest()
        send(case1)

    # TEST CASE 2: next extension header signaled but not present
    case2 = IPv6(src=src,dst=dst)/IPv6ExtHdrHopByHop(nh=60)/ICMPv6EchoRequest()
    print(case2.summary())
    send(case2)
    case2 = IPv6(src=src,dst=dst)/IPv6ExtHdrDestOpt(nh=0)/ICMPv6EchoRequest()
    print(case2.summary())
    send(case2)
    case2 = IPv6(src=src,dst=dst)/IPv6ExtHdrRouting(nh=44)/ICMPv6EchoRequest()
    print(case2.summary())
    send(case2)
    case2 = IPv6(src=src,dst=dst)/IPv6ExtHdrFragment(nh=43)/ICMPv6EchoRequest()
    print(case2.summary())
    send(case2)

    # TEST CASE 3: Play around with the options for the extension headers
    ip = IPv6(src=src, dst =dst)
    extHdr = IPv6IPv6ExtHdrHopByHop()
    pad = padN(optlen=255)
    extHdr.options = pad
    icmp = ICMPv6EchoRequest()

    a = ip/extHdr/icmp
    send(a)

generatePackets(sys.argv)


"""
2185. 统计包含给定前缀的字符串
给你一个字符串数组 words 和一个字符串 pref 。
返回 words 中以 pref 作为 前缀 的字符串的数目。
字符串 s 的 前缀 就是 s 的任一前导连续字符串。
"""
from typing import *

def prefixCount(words: List[str], pref: str) -> int:
    result = 0
    for word in words:
        if word[:len(pref)] == pref:
            result += 1
    return result

2185. 统计包含给定前缀的字符串.py


def mySolution(matrix):
    row_lenth=len(matrix)
    col_lenth=len(matrix[0])
    ans_arr=[[0 for _ in range(col_lenth)]for _ in range(row_lenth)]
    if matrix[0][0]==0:
        ans_arr[0][0]=1
    for i in range(1,row_lenth):
        if matrix[i][0]==0:
            ans_arr[i][0]=ans_arr[i-1][0]
    for i in range(1,col_lenth):
        if matrix[0][i]==0:
            ans_arr[0][i]=ans_arr[0][i-1]
    for i in range(1, row_lenth):
        for j in range(1,col_lenth):
            if matrix[i][j]==0:
                ans_arr[i][j]=ans_arr[i-1][j]+ans_arr[i][j-1]
    return ans_arr[-1][-1]
A = [[0, 0, 0],
        [0, 1, 0],
        [0, 0, 0] ]
print(mySolution(A))

DP/Unique paths in a Grid with Obstacles.py


# Copyright (c) 2020-2021, Abhishek N. Kulkarni <abhi.bp1993@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
Project: spotondocker
URL: https://github.com/abhibp1993/spotondocker
File: server.py
Description:
    The file defines `SpotOnDockerHandler` class which exposes some functionality from spot (see: https://spot.lrde.epita.fr/).
    It manages the communication via Apache Thrift server.

Author: Abhishek N. Kulkarni <abhi.bp1993@gmail.com>
"""

import sys
sys.path.append('genpy')
from spotondocker import SpotOnDocker

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

import argparse
import spot

class SpotOnDockerHandler:
    def __init__(self):
        self.log = {}

    def Ping(self):
        print("Ping()")

    def MpClass(self, formula):
        return spot.mp_class(formula, 'v')

    def Contains(self, formula1, formula2):
        f1 = spot.formula(formula1)
        f2 = spot.formula(formula2)
        return spot.contains(f1, f2)

    def IsEquivalent(self, formula1, formula2):
        f1 = spot.formula(formula1)
        f2 = spot.formula(formula2)
        return spot.are_equivalent(f1, f2)

    def RndLTL(self, numAP, rndSeed):
        f = spot.randltl(numAP, output='ltl', seed=rndSeed).relabel(spot.Abc).simplify()
        return next(f).to_str('spot')

    def GetAP(self, formula):
        f = spot.formula(formula)
        return [f.to_str('spot') for f in spot.atomic_prop_collect(f)]

    def ToLatexString(self, formula):
        return spot.formula(formula).to_str("sclatex")

    def Translate(self, formula):
        aut = spot.translate(formula, "BA", "High", "SBAcc", "Complete")
        bdict = aut.get_dict()

        autGraph = SpotOnDocker.TGraph()
        print(type(aut.get_acceptance()))
        autGraph.acceptance = str(aut.get_acceptance())
        autGraph.numAccSets = int(aut.num_sets())
        autGraph.numStates = int(aut.num_states())
        autGraph.initStates = [int(aut.get_init_state_number())]
        autGraph.apNames = [str(ap) for ap in aut.ap()]
        autGraph.formula = str(aut.get_name())
        autGraph.isDeterministic = bool(aut.prop_universal() and aut.is_existential())
        autGraph.isTerminal = bool(aut.prop_terminal())
        autGraph.hasStateBasedAcc = bool(aut.prop_state_acc())

        states = []
        edges = []
        for src in range(0, aut.num_states()):
            n = SpotOnDocker.TNode()
            n.id = int(src)

            for edge in aut.out(src):
                e = SpotOnDocker.TEdge()
                e.srcId = int(edge.src)
                e.dstId = int(edge.dst)
                e.label = str(spot.bdd_format_formula(bdict, edge.cond))
                n.isAcc = not (edge.acc is None)

                edges.append(e)

            states.append(n)

        autGraph.nodes = states
        autGraph.edges = edges

        return autGraph

if __name__ == '__main__':
    # Parse input args
    parser = argparse.ArgumentParser()
    parser.add_argument("ip", type=str, nargs='?', default="*", help="IP address to connect to.")
    parser.add_argument("port", type=str, nargs='?', default="7159", help="Port to connect to.")
    args = parser.parse_args()

    # initialize server
    handler = SpotOnDockerHandler()
    processor = SpotOnDocker.Processor(handler)
    transport = TSocket.TServerSocket(host=args.ip, port=args.port)
    tfactory = TTransport.TBufferedTransportFactory()
    pfactory = TBinaryProtocol.TBinaryProtocolFactory()
    server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
    try:
        server.serve()
    except KeyboardInterrupt:
        pass


import optparse
import os
import sys
import __main__
from datetime import datetime

#################
##-  globals  -##
#################

# Time:
global time
now = datetime.now()
time = now.strftime("%y%m%d_%H%M%S")

# Global paths:
global workdir # absolute path to THIS script
workdir = os.path.dirname(os.path.abspath(__main__.__file__))

global logpath
logpath = workdir + '/' + 'logs/'

##############
##-  Init  -##
##############

if not os.path.exists(workdir + '/logs'): os.makedirs(workdir + '/logs')
if not os.path.exists(workdir + '/bashFiles'): os.makedirs(workdir + '/bashFiles')
if not os.path.exists(workdir + '/condorFiles'): os.makedirs(workdir + '/condorFiles')

###################
##-  Functions  -##
###################

def identifyCMSSW(command):

    """ Function to identify release of a given path """
    cmsswpart = False
    for part in command.split():
        if 'CMSSW' in part:
            cmsswpart = part

    cmsswpath = ''
    for level in cmsswpart.split('/'):
        cmsswpath += level + '/'
        if 'CMSSW' in level:
            cmsswpath += 'src'
            break

    return cmsswpath

def makeBashFile(command):

    cmssw = identifyCMSSW(command)

    bashTemplate = """#!/bin/bash
pushd {0}
eval `scramv1 runtime -sh`
pushd
""".format(cmssw)

    bashTemplate += command

    bashpath = workdir + '/bashFiles/' + 'bash_' + time + '.sh'
    _f = open(bashpath, 'w')
    _f.write(bashTemplate)
    _f.close()

    return bashpath

def makeCondorFile(queue, bashpath):

    condorTemplate = """
universe                = vanilla
executable              = $(filename)
output                  = {0}$(ClusterId).$(ProcId).out
error                   = {0}$(ClusterId).$(ProcId).err
log                     = {0}$(ClusterId).log
Notify_user             = fernance@cern.ch
+JobFlavour = "{1}"
queue filename matching {2}
""".format(logpath, queue, bashpath)

    condorpath = workdir + '/condorFiles/' + 'condor_' + time + '.sh'
    _f = open(condorpath, 'w')
    _f.write(condorTemplate)
    _f.close()

    return condorpath

##############
##-  main  -##
##############

if __name__ == "__main__":

    ## -> Exit if there are missed arguments
    if not len(sys.argv) > 2:
        sys.exit("Usage: toCONDOR [queue] [command to launch]")

    ## Collect parameters:
    queue = sys.argv[1]
    command = ''
    for i in range(2, len(sys.argv)):
        command += sys.argv[i]
        if not i == (len(sys.argv) - 1): command += ' '

    ## Verbose:
    print(" > Launching to queue: " + queue)
    print(" > Command to launch: " + command)

    ## Create bash and condor files for submission:
    bashFile = makeBashFile(command)
    condorFile = makeCondorFile(queue, bashFile)

    ## Launch job.
    os.system('chmod +x ' + bashFile)
    os.system('chmod +x ' + condorFile)
    os.system('condor_submit ' + condorFile)


#!/usr/local/bin/python3
# Objects used in CsvManager
# Update 20200827
# Update 20210225 - Make it work with GovCloud

import boto3
import botocore
import time
import re
import logging
from botocore import exceptions
from botocore.exceptions import ClientError
from boto3.session import Session

# The current supported version
_CURRENT_VERSION = "2021-01-01"

_DEFAULT_LOGGING_LEVEL = logging.INFO
""" Default logging level """

# Set up logging
logging.basicConfig(level=_DEFAULT_LOGGING_LEVEL)

# Retrieve the logging instance
_LOGGER = logging.getLogger()
_LOGGER.setLevel(_DEFAULT_LOGGING_LEVEL)

# Simplify the extraction of error detail from botocore.exception.ClientError
errorCode = lambda exception: exception.response \
    .get("Error", {}) \
    .get("Code", "INVALID") if getattr(exception, "response") \
    else type(exception).__name__

################################################################################
#
################################################################################
class FindingValueError (Exception):
    """
    This exception is thrown if some value in a finding is out of bounds, or
    if there are other problems in the import or update of a finding.
    """
    pass
################################################################################
#
################################################################################
class FindingColumn:
    """
    Map a SecurityHub finding dictionary to a set of CSV column. This object
    represents a single column.

    If you will be using more than one list of FindingColumns, you must start
    each successive list with a reset=True parameter.

    Parameters
    ----------
    columnName : str
        The name assigned to the CSV column
    keys : list of str
        A list of keys used to access the value for this column in a
        securityhub:get_findings API response.
    isKey : boolean
        True if this column will act as a key to uniquely identify a finding
    isUpdatable : boolean
        True if this column can be updated using securityhub:batch_update_findings
    d2l : callable, None, or other
        A transformation between the API dictionary and the CSV record value
    d2lParameters : dict
        A ** dictionary to be passed to the d2l transform if it is callable
    l2d : callable, None, or other
        A transformation between the cSV record and API dictionary value
    l2dParameters : dict
        A ** dictionary to be passed to the l2d transform if it is callable

    Notes
    -----
    The d2l and l2d transforms should be combined, since in all cases so far
    identified, the transform is the same in both directions.
    """
    #---------------------------------------------------------------------------
    def __init__ (self, columnNumber=0, columnName=None, keys=None, isKey=False,
        isUpdatable=False, d2l=None, d2lParameters={}, l2d=None, l2dParameters={}):
        """
        See class definition for details.
        """
        self.columnNumber = columnNumber
        self.columnName = columnName
        self.keys = keys
        self.isKey = isKey
        self.isUpdatable = isUpdatable
        self.d2lParameters = d2lParameters
        self.d2l = d2l
        self.l2dParameters = l2dParameters
        self.l2d = l2d
        self.transform = None
        self.parameters = {}
        self._value = None
    #---------------------------------------------------------------------------
    @property
    def rawValue (self):
        """
        The raw value that came from the API finding dictionary or the CSV
        column, depending on how this finding was initialized.
        """
        return self._value
    #---------------------------------------------------------------------------
    @property
    def value (self):
        """
        Return the value transformed according to its source (the API finding
        or the CSV column)
        """
        return self._value
    #---------------------------------------------------------------------------
    @value.setter
    def value (self, initializer=None):
        """
        The setter is invoked against a dictionary of values, or a list of values
        as in:

            self.value = { "key":  "value", "key2"; "value2" }
            self.value = [ "value", "value2" ]

        If a list is specified, values are subject to l2d transformations; If a
        dict is specified, values are subject to d2l transformations.

        Object attributes are set to facilitate the processing of the value
        when the value property is used.

        1. If the transform is missing, the original value is used
        2. If the transform is callable, the transform return value is used
        3. Else, the transform value is used
        """
        _LOGGER.debug("496010d FindingColumn.value %s" % initializer)

        # An API result dict is supplied
        if isinstance(initializer, dict):
            candidate = self.deep(initializer)

            _LOGGER.debug("496020d processing an API result %s = '%s'" % \
                (self.columnName, candidate))

            self.transform = self.d2l
            self.parameters = self.d2lParameters
        # A CSV list is supplied
        elif isinstance(initializer, list):
            try:
                candidate = initializer[self.columnNumber]
            except (ValueError, IndexError):
                candidate = None

            _LOGGER.debug("496030d processing a CSV column %d (%s) = '%s'" % \
                (self.columnNumber, self.columnName, candidate))

            self.transform = self.l2d
            self.parameters = self.l2dParameters
        # Neither is an error
        else:
            raise FindingValueError("496040t must be passed a list or dict")

        # Case 1 - No transform
        if not self.transform:
            answer = candidate

        # Case 2 - Callable transform
        elif callable(self.transform):
            answer = self.transform(candidate, **self.parameters)

        # Case 3 - Fixed transform
        else:
            answer = self.transform

        _LOGGER.debug("496050d %s source is %s candidate is [%s]" \
            % (self.columnName, type(initializer).__name__, candidate))

        self._value = answer if answer != '' else None

        _LOGGER.debug("496060d %s transformed is [%s]" \
            % (self.columnName, answer))
    #---------------------------------------------------------------------------
    def key (self):
        """
        If this column is a key column, return its value. This method will
        return "none" if the self.value setter is not called first!
        """
        return None if not self.isKey else self.value
    #---------------------------------------------------------------------------
    def update (self):
        """
        If this column is an updatable column, return its value. This method
        will return "None" if self.value setter is not called first!
        """
        return None if not self.isUpdatable else self.value
    #---------------------------------------------------------------------------
    def deep (self, dictionary = {}):
        """
         Retrieve a nested item from a dict. For example, given { "A" : "B": {1}}
         and a self.keys attribute of ["A", "B"], this will return 1.
        """
        for key in self.keys:
            dictionary = dictionary.get(key, None)

            if not dictionary:
                break

        return dictionary
################################################################################
#
################################################################################
class FindingColumnMap:
    """
    Maps all API values to CSV columns, essentially flattening the API dict
    into a list that can be appended to a CSV file. This class automatically
    numbers the columns.
    """
    #---------------------------------------------------------------------------
    def __init__ (self, map=[]):
        """
        See the class definition for details
        """
        self.columns = 0
        self.itemList = []
        self.itemMap = {}

        for item in map:
            if not isinstance(item, FindingColumn):
                raise FindingValueError("496070t all FindingColumnMap items " + \
                    "must be FindingColumn objects")

            # Set the column number automatically
            item.columnNumber = self.columns

            self.itemList.append(item)
            self.itemMap[item.columnName] = item
            self.columns += 1

            _LOGGER.debug("496080d mapped column number %d to column name %s"
                % (item.columnNumber, item.columnName))
    #---------------------------------------------------------------------------
    def __getitem__ (self, item=None):
        """
        Return an item if indexed by a column number object[number] or a dict
        key object[key].
        """
        if isinstance(item, int) and (item >= 0) and (item <= len(self.itemList)):
            answer = self.itemList[item]
        elif item != None:
            answer = self.itemMap[item]
        else:
            answer = None

        return answer
    #---------------------------------------------------------------------------
    def __len__ (self):
        """
        Return the length of the column map.
        """
        return len(self.itemList)
################################################################################
#
################################################################################
class Finding:
    """
    Represents an AWS SecurityHub finding.

    Parameters
    ----------
    initializer : dict or list
        *   The dictionary for a SecurityHub finding from the
            securityhub:get_findings API call.

        *   A list containing the values of a CSV record containing finding
            fields in specific columns

    Attributes
    ----------
    mapping : list of FindingColumn
        A list of FindingColumn objects mapping the securityhub:get_findings
        dictionary to a CSV record with particular column names
    rowList : list
        A list of values representing a CSV row
    rowMap : dict
        A dict keyed by the column name, with each value being the value for
        that column.
    finding : dict
        The nested dict returned by the securityhub:get_findings API, or built
        from a CSV initializer (see below)
    source : type
        Set to dict if initialized by a finding dict, and list if it is initialized
        from a CSV row list
    """
    #---------------------------------------------------------------------------
    def __init__ (self, initializer = None, actor=None):
        """
        See class description for details
        """
        self.actor = actor
        self.mapping = self.fullMap()
        self.findingColumn = {}

        # These values msut exist in the fullMap
        self.Id = None
        self.ProductArn = None

        if isinstance(initializer, dict):
            self.rowMap = self.mapColumns(initializer)
            self.rowList = [ value for key, value in self.rowMap.items() ]
            self.finding = initializer
            self.source = dict
        elif isinstance(initializer, (list, tuple)):
            self.rowList = list(initializer)
            self.rowMap = dict(zip(self.columns, self.rowList))
            self.finding = self.mapFinding(initializer)
            self.source = list
        else:
            raise FindingValueError("496090s initializer must be dict or list")
    #---------------------------------------------------------------------------
    @property
    def keys (self):
        """
        Return a dict of key values for this finding. Key values are attributes
        with the isKey attribute that uniquely identify the finding.
        """
        answer = {}

        for map in self.mapping:
            if map.isKey:
                answer[map.columnName] = getattr(self, map.columnName)

        return answer
    #---------------------------------------------------------------------------
    @property
    def columns (self):
        """
        Return a list of CSV column names.
        """
        return [ map.columnName for map in self.mapping ]
    #---------------------------------------------------------------------------
    def mapColumns (self, initializer = None):
        """
        Convert a SecurityHub findings dictionary to a CSV row
        """
        row = {}

        for descriptor in self.mapping:
            # There is magic here -- see the FindingColumn class value setter
            descriptor.value = initializer

            value = descriptor.value
            name = descriptor.columnName

            row[name] = value

            setattr(self, name, value)
            self.findingColumn[name] = descriptor

            _LOGGER.debug("496100d mapped %s to column %s with value [%s]" \
                % (descriptor.keys, name, value))

        return row
    #---------------------------------------------------------------------------
    def mapFinding (self, initializer=None):
        """
        Convert an CSV Manager record to a SecurityHub finding dictionary
        """
        finding = {}

        for value, descriptor in zip(initializer, self.mapping):
            # There is magic here -- see the FindingColumn class value setter
            descriptor.value = initializer

            value = descriptor.value
            name = descriptor.columnName

            setattr(self, name, value)
            self.findingColumn[name] = descriptor

            # Now we build up the finding
            _LOGGER.debug("496110d %s deep set %s = %s" % \
                (name, descriptor.keys, value))

            Finding._deepSet(finding, descriptor.keys, value)

        return finding
    #---------------------------------------------------------------------------
    def getFindingColumn (self, name=None):
        """
        Return the FindingColumn object associated with a named column.
        """
        return self.findingColumn.get(name)
    #---------------------------------------------------------------------------
    @staticmethod
    def _deepSet (dictionary={}, keys=[], value=None, skipNone=False):
        """
        Given a nested dictionary and a list of keys, set the given value in
        the nested ditionary based on the key list. I.e.,
        _deepset({}, ["a","b"], 1) results in a dictionary {"a": {"b": 1 }}
        """

        if not keys:
            _LOGGER.debug("496120d deepset called with no keys")
        elif not skipNone or (value != None):
            key = keys[0]

            if len(keys) <= 1:
                dictionary[key] = value
            else:
                keys = keys[1:]

                if not(key in dictionary):
                    dictionary[key] = {}
                elif not isinstance(dictionary[key], dict):
                    dictionary[key] = {}

                Finding._deepSet(dictionary[key], keys, value)

        return dictionary
    #---------------------------------------------------------------------------
    def fullMap (self):
        """
        This list maps CSV column names to sequences of nested keys in the
        Security Hub findings dictionary. See the FindingColumn object for
        details.
        """
        map = FindingColumnMap([
            FindingColumn(
				columnName="Id",
                keys=["Id"],
                isKey=True
            ),
            FindingColumn(
                columnName="ProductArn",
                keys=["ProductArn"],
                isKey=True
            ),
            #### BEGIN Updatable fields using securityhub:batch_update_findings()
            FindingColumn(
                columnName="Criticality",
                keys=["Criticality"],
                isUpdatable=True,
                d2l=FindingActions.forceInteger,
                l2d=FindingActions.forceInteger
            ),
            FindingColumn(
                columnName="Confidence",
                keys=["Confidence"],
                isUpdatable=True,
                d2l=FindingActions.forceInteger,
                l2d=FindingActions.forceInteger,
            ),
            FindingColumn(
                columnName="NoteText",
                keys=["Note", "Text"],
                isUpdatable=True
            ),
            FindingColumn(
                columnName="NoteUpdatedBy",
                keys=["Note", "UpdatedBy"],
                isUpdatable=True,
                d2l=FindingActions.noteUpdater,
                d2lParameters={"actor" : self.actor , "finding": self },
                l2d=FindingActions.noteUpdater,
                l2dParameters={"actor" : self.actor , "finding": self }
            ),
            FindingColumn(
                columnName="CustomerOwner",
                keys=["UserDefinedFields", "Owner"],
                isUpdatable=True
            ),
            FindingColumn(
                columnName="CustomerIssue",
                keys=["UserDefinedFields", "Issue"],
                isUpdatable=True
            ),
            FindingColumn(
				columnName="CustomerTicket",
                keys=["UserDefinedFields", "Ticket"],
                isUpdatable=True
            ),
            FindingColumn(
				columnName="ProductSeverity",
                keys=["Severity", "Product"],
                isUpdatable=True,
                d2l=FindingActions.checkSeverity,
                l2d=FindingActions.checkSeverity,
            ),
            FindingColumn(
				columnName="NormalizedSeverity",
                keys=["Severity", "Normalized"],
                isUpdatable=True,
                d2l=FindingActions.checkSeverity,
                l2d=FindingActions.checkSeverity
            ),
            FindingColumn(
				columnName="SeverityLabel",
                keys=["Severity", "Label"],
                isUpdatable=True,
                d2l=FindingActions.checkSeverityLabel,
                l2d=FindingActions.checkSeverityLabel
            ),
            FindingColumn(
				columnName="VerificationState",
                keys=["VerificationState"],
                isUpdatable=True,
                d2l=FindingActions.checkVerificationState,
                l2d=FindingActions.checkVerificationState
            ),
            FindingColumn(
				columnName="Workflow",
                keys=["Workflow", "Status"],
                isUpdatable=True,
                d2l=FindingActions.checkWorkflow,
                l2d=FindingActions.checkWorkflow
            ),
            #### END Updatable fields using securityhub:batch_update_findings()
            FindingColumn(
				columnName="UpdateVersion",
                keys=[],
                d2l=str(_CURRENT_VERSION),
                l2d=str(_CURRENT_VERSION)
            ),
            FindingColumn(
				columnName="GeneratorId",
                keys=["GeneratorId"]
            ),
            FindingColumn(
				columnName="AwsAccountId",
                keys=["AwsAccountId"]
            ),
            FindingColumn(
				columnName="Types",
                keys=["Types"],
                d2l=FindingActions.delist
            ) ,
            FindingColumn(
				columnName="FirstObservedAt",
                keys=["FirstObservedAt"]
            ),
            FindingColumn(
				columnName="LastObservedAt",
                keys=["LastObservedAt"]
            ),
            FindingColumn(
				columnName="CreatedAt",
                keys=["CreatedAt"]
            ),
            FindingColumn(
				columnName="UpdatedAt",
                keys=["UpdatedAt"]
            ),
            FindingColumn(
				columnName="Title",
                keys=["Title"]
            ),
            FindingColumn(
				columnName="Description",
                keys=["Description"]
            ),
            FindingColumn(
				columnName="StandardsArn",
                keys=["ProductFields", "StandardsArn"]
            ),
            FindingColumn(
				columnName="StandardsSubscriptionArn",
                keys=["ProductFields", "StandardsSubscriptionArn"]
            ),
            FindingColumn(
				columnName="ControlId",
                keys=["ProductFields", "ControlId"]
            ),
            FindingColumn(
				columnName="RecommendationUrl",
                keys=["ProductFields", "RecommendationUrl"]
            ),
            FindingColumn(
				columnName="StandardsControlArn",
                keys=["ProductFields", "StandardsControlArn"]
            ),
            FindingColumn(
				columnName="ProductName",
                keys=["ProductFields", "aws/securityhub/ProductName"]
            ),
            FindingColumn(
				columnName="CompanyName",
                keys=["ProductFields", "aws/securityhub/CompanyName"]
            ),
            FindingColumn(
				columnName="Annotation",
                keys=["ProductFields", "aws/securityhub/annotation"]
            ),
            FindingColumn(
				columnName="FindingId",
                keys=["ProductFields", "aws/securityhub/FindingId"]
            ),
            FindingColumn(
				columnName="Resources",
                keys=["Resources"],
                d2l=FindingActions.resources
            ),
            FindingColumn(
				columnName="ComplianceStatus",
                keys=["Compliance", "Status"]
            ),
            FindingColumn(
				columnName="WorkflowState",
                keys=["WorkflowState"]
            ),
            FindingColumn(
				columnName="RecordState",
                keys=["RecordState"]
            )
        ])

        return map
################################################################################
#
################################################################################
class FindingActions:
    """
    A class containing static methods used to pre- and post-process values
    used in SecurityHub finding dictionaries and CSV records
    """
    _SEVERITY_LABELS = [
        "INFORMATIONAL",
        "LOW",
        "MEDIUM",
        "HIGH",
        "CRITICAL"
    ]

    _VERIFICATION_STATES = [
         "UNKNOWN",
         "TRUE_POSITIVE",
         "FALSE_POSITIVE",
         "BENIGN_POSITIVE"
    ]

    _WORKFLOWS = [
        "NEW",
        "NOTIFIED",
        "RESOLVED",
        "SUPPRESSED"
    ]
    #---------------------------------------------------------------------------
    @staticmethod
    def noteUpdater (value=None, actor=None, finding=None):
        """
        Return the principal ID of the user who is updating a note. This value
        is only set if there is a corresponding update to the NoteText
        attribute of the finding.
        """
        if not isinstance(actor, Actor) or not isinstance(finding, Finding):
            _LOGGER.warning("496130w missing actor or finding for '%s'" % value)
            answer = None
        else:
            if finding.NoteText:
                answer = actor.principal.get("UserId")
            else:
                answer = None

        return answer
    #---------------------------------------------------------------------------
    @staticmethod
    def delist (list = []):
        """
        Convert a list into a newline-separated string
        """
        return "".join(list)
    #---------------------------------------------------------------------------
    @staticmethod
    def resources (resources = []):
        """
        Convert a list of SecurityHub resources to a newline-separated string
        """
        answer = []

        for resource in resources:
            _type = resource.get("Type")
            _id = resource.get("Id")
            _partition = resource.get("Partition")
            _region = resource.get("Region")

            answer.append("%s, %s, %s, %s" % (_type, _id, _partition, _region))

        return "".join(answer)
    #---------------------------------------------------------------------------
    @staticmethod
    def checkSeverity (value=0):
        """
        Verify a Security Hub severity value, which must be an integer between
        0 and 100, or a floating point number provided by the source application
        """
        if value == None:
            answer = None
        elif isinstance(value, str):
            try:
                answer = int(value)

                if (answer < 0) or (answer > 100):
                    raise FindingValueError(
                        "%d is not an int between 0 and 100" % value
                    )
            except:
                try:
                    answer = float(value)
                except:
                    answer = None
        elif isinstance(value, float):
            answer = value
        elif isinstance(value, int) and (value >= 0) and (value <= 100):
            answer = value
        else:
            raise FindingValueError(
                "%d is not a float or int between 0 and 100" % value
            )

        return answer
    #---------------------------------------------------------------------------
    @staticmethod
    def checkSeverityLabel (value=None):
        """
        Verify a Security Hub severity label which must be one of the values in
        the list below. The comparison is case insensitive and an uppercase
        value is always returned.

        Valid values are stored in FindingActions._SEVERITY_LABELS
        """
        if not isinstance(value, str) or value == '':
            answer = None
        else:
            if value.upper() in FindingActions._SEVERITY_LABELS:
                answer = value.upper()
            else:
                raise FindingValueError(
                    "'%s' is not a valid severity label" % value
                )

        return answer
    #---------------------------------------------------------------------------
    @staticmethod
    def checkVerificationState (value=None):
        """
        Verify a Security Hub verification state which must be one of the values
        in the list below. The comparison is case insensitive, converts
        whitespace to a single underscore ("_") and an uppercase value is always
        returned.

        Valid values are in FindingActions._VERIFICATION_STATES
        """
        if not isinstance(value, str) or value == '':
            answer = None
        else:
            candidate = re.sub(r'\s+', "_", value).upper()

            if candidate in FindingActions._VERIFICATION_STATES:
                answer = candidate
            else:
                raise FindingValueError(
                    "'%s' is not a valid verification state" % value
                )

        return answer
    #---------------------------------------------------------------------------
    @staticmethod
    def checkWorkflow (value=None):
        """
        Verify a Security Hub workflow label which must be one of the values in
        the list below. The comparison is case insensitive and an uppercase
        value is always returned.

        Valid values are in FindingActions._WORKFLOWS
        """
        if not isinstance(value, str) or value == '':
            answer = None
        else:
            if value.upper() in FindingActions._WORKFLOWS:
                answer = value.upper()
            else:
                raise FindingValueError(
                    "'%s' is not a valid workflow state" % value
                )

        return answer
    #---------------------------------------------------------------------------
    @staticmethod
    def forceInteger (value=None):
        """
        Force a value to be an integer.
        """
        try:
            answer = int(value)
        except:
            answer = None

        return answer
################################################################################
#
################################################################################
class ActorException (Exception):
    pass
################################################################################
#
################################################################################
class Actor:
    _REGION_MODE_SINGLE = 1    # A simple, single-region client
    _REGION_MODE_MULTIPLE = 1   # Requires a ServiceRegionBroker
    """
    An abstract class for API functions. The class defines clients in each of
    a set of supported regions, and then carries out actions using those
    clients.

    The following APIs are used by the abstract class:
    sts:AssumeRole
    sts:GetCallerIdentity
    """
    #---------------------------------------------------------------------------
    def __init__ (self, service=None, region=None, role=None):
        """
        See the class definition for details.
        """
        self.role = role
        self.authorized = False
        self.accessKeyId = None
        self.accessKey = None
        self.sessionToken = None
        self.client = {}
        self.principal = None
        self.service = service

        # Some things depend on whether we were passed a region or a list
        if isinstance(region, list):
            self.regions = region
            self.mode = Actor._REGION_MODE_MULTIPLE

            _LOGGER.debug(f'496140d service {service} set to _REGION_MODE_MULTIPLE')

        elif isinstance(region, str):
            self.regions = [ region ]
            self.mode = Actor._REGION_MODE_SINGLE

        else:
            raise ActorException("496150t region must be a region name or list of regions [%s]" % region)

        # Get authorization
        self.authorize(regions=self.regions)

        # Create a client for the specified regions
        for region in self.regions:
            _LOGGER.debug("496160d create %s client in region %s"
                % (self.service, region))

            self.client[region] = self.getClient(region)
    #---------------------------------------------------------------------------
    def getPartition(self, region:str) -> str:
        """
        Get an AWS partition name from a given region. This must be possible to
        do more elegantly later
        """
        candidate = region if region else self.primaryRegion
        answer = "aws"

        if re.match(r'^us-gov.*', candidate):
            answer = "aws-us-gov"

        _LOGGER.debug(f'496180d mapped region {candidate} to partition {answer}')

        return answer
    #---------------------------------------------------------------------------
    def getSupportedRegions(self, region:str=None, service:str=None) -> list:
        """
        Return a list of supported regions for this servce
        """
        region = self.primaryRegion if not region else region
        partition = self.getPartition(region)
        service = self.service if not service else service

        answer = Session(region_name=region) \
            .get_available_regions(
                service,
                partition_name=partition
            )

        return answer
    #---------------------------------------------------------------------------
    def getClient (self, region:str) -> object:
        """
        Create an AWS API client associated with a specific region
        """
        try:
            client = boto3.client(
                self.service,
                aws_access_key_id=self.accessKeyId,
                aws_secret_access_key=self.accessKey,
                aws_session_token=self.sessionToken ,
                region_name=region
           )

        except Exception as thrown:
            _LOGGER.critical(f'496190s error obtaining client for {self.service} ' +
                f'in {region}: {thrown}')
            client = None

        return client
    #---------------------------------------------------------------------------
    @property
    def primaryRegion (self):
        """
        Return the SRB's primary region or just use the supplied region
        """
        return self.regions[0]
    #---------------------------------------------------------------------------
    @property
    def primaryClient (self):
        """
        Return the client for the primary region
        """
        return self.client[self.primaryRegion]
    #---------------------------------------------------------------------------
    def authorize (self, regions=None):
        """
        If no role is supplied to the actor, the authorization is implicit
        through the credentials already in the environment. Otherwise, use
        sts:assume_role to gain the privileges associated with the supplied
        role ARN.
        """
        _LOGGER.debug("496200d request to authorize %s client region %s"
            % (self.service, regions[0]))

        # Obtain an STS client
        try:
            # Obtain an STS client
            client = boto3.client("sts", region_name=regions[0])
            _LOGGER.debug("496210d obtained STS client %s" % client)

            # No role supplied - use environment credentials
            if not self.role:
                self.authorized = True
                _LOGGER.debug("496220d authorized from environment")

            # Role supplied, try to assume the role
            else:
                _LOGGER.debug("496230d attempt to assume role %s" % self.role)

                answer = client.assume_role(
                    RoleArn=self.role,
                    RoleSessionName=("%s-access" % self.service)
                )

                self.accessKeyId = answer["Credentials"]["AccessKeyId"]
                self.accessKey = answer["Credentials"]["SecretAccessKey"]
                self.sessionToken = answer["Credentials"]["SessionToken"]
                self.authorized = True

                _LOGGER.info("496240i assumed role %s for service %s" \
                    % (self.role, self.service))

            # Now get the principal name of the authorized identity
            self.principal = client.get_caller_identity()

        # Catch client errors
        except ClientError as thrown:
            _LOGGER.critical(f'496250d threw {errorCode(thrown)}: {thrown}')
            self.authorized = False

        # If we got this far, we're authorized
        else:
            self.authorized = True

        # Complain if we aren't authorized
        if not self.authorized:
            raise ActorException('496260t authorization failed')

        return self
################################################################################
#
################################################################################
class SsmActor (Actor):
    """
    Perform systems manager (SSM) actions. The following SSM APIs are used
    in this concrete class:

    ssm:PutParameter
    ssm:GetParameters
    """
    _PARAMETERS = [
        "/csvManager/bucket",
        "/csvManager/folder/code",
        "/csvManager/folder/findings",
        "/csvManager/object/codeArchive",
        "/csvManager/partition",
        "/csvManager/regionList"
    ]
    #---------------------------------------------------------------------------
    def __init__ (self, region=None, role=None, resolve=_PARAMETERS):
        """
        See the class definition for details.
        """
        super().__init__(
            service="ssm",
            region=region,
            role=role
        )

        if resolve:
            answers = self.getValue(resolve)

            for name, value in answers.items():
                setattr(self, name, value)
    #---------------------------------------------------------------------------
    # Set an SSM parameter value
    def putValue (self, name=None, description=None, value=None, type="String"):
        """
        Set the value of an SSM parameter.
        """
        try:
            answer = self.primaryClient.put_parameter(
                Name=name,
                Description=description,
                Type=type,
                Value=value,
                Overwrite=True
           )

        except Exception as thrown:
            _LOGGER.info(f'496270s cannot set parameter: {thrown}')

            answer = None

        return answer
    #---------------------------------------------------------------------------
    # Get SSM parameter values
    def getValue (self, names:list[str]=[]):
        """
        Retrieve the value of an SSM parameter.
        """
        if isinstance(names, list):
            single = False
            answer = {}
        else:
            names = [names]
            single = True
            answer = None

        try:
            answer = self.primaryClient.get_parameters(Names=names)

            _LOGGER.debug("496280d result from ssm:get_parameters %s" % answer)

            for candidate in answer["Parameters"]:
                name = candidate["Name"]
                value = candidate["Value"]

                if single:
                    answer = value
                else:
                    answer[name] = value

                _LOGGER.debug(f'496290i SSM {name} = {value}')

            for name in answer["InvalidParameters"]:
                _LOGGER.info("496300d parameter '%s' not found" % name)
                answer[name] = None

        except Exception as thrown:
            _LOGGER.error("496310e cannot get parameters: %s" % str(thrown))

            if not single:
                for name in names:
                    _LOGGER.info("496320d parameter '%s' set to None" % name)
                    answer[name] = None

        return answer
################################################################################
#
################################################################################
class S3Actor(Actor):
    """
    Perform AWS Simple Storage Service (S3) API operations. The following S3
    API operations are used by this concrete class:

    s3:PutObject
    s3:GetObject
    """
    _PREFIX = "SecurityHub"
    _SUFFIX = ".csv"
    _FOLDER = "SecurityHub"
    #---------------------------------------------------------------------------
    def __init__ (self, bucket=None, folder=_FOLDER, prefix=_PREFIX,
        suffix=_SUFFIX, region=None, role=None):
        """
        See the class definition for details
        """
        super().__init__("s3", region=region, role=role)

        self.prefix = prefix
        self.suffix = suffix
        self.folder = folder
        self.bucket = bucket
        self._filename = None
    #---------------------------------------------------------------------------
    def buildFilename (self, bucket=None, folder=None, name=None,
        extension=None):
        """
        Construct an fully qualified S3 name from the bucket, folder,
        filename, and extention.
        """
        answer = (bucket if bucket else self.bucket) + "/" + \
            (folder if folder else self.folder) + "/" + \
            ((name + "." + extension) if extension else name)

        return answer
    #---------------------------------------------------------------------------
    @property
    def filename (self):
        """
        Return the unique S3 key associated with this object.
        """
        if self._filename:
            answer = self._filename
        else:
            answer = self.prefix + "-" + \
                time.strftime("%Y%m%d-%H%M%S") + \
                self.suffix

            self._filename = answer

        return answer
    #---------------------------------------------------------------------------
    def filePath (self, directory = "/tmp"):
        """
        Return a local fully qualified file path.
        """
        return "/".join([directory, self.filename])
    #---------------------------------------------------------------------------
    @property
    def objectKey (self):
        """
        Return an S3 object key from the "folder" and unique filename.
        """
        return "/".join([self.folder, self.filename])
    #---------------------------------------------------------------------------
    def put (self, inputFile = None, outputObject = None ):
        """
        Store an object in the S3 bucket. The inputFile is read from
        the local filesystem and stored to S3 as outputObject.
        """
        source = inputFile if inputFile else self.filePath()
        target = outputObject if outputObject else self.objectKey

        try:
            with open(source, "rb") as source:
                answer = self.primaryClient.put_object(
                    Bucket=self.bucket,
                    Key=target,
                    Body=source
                )

        except botocore.exceptions.ClientError as thrown:
            answer = None
            _LOGGER.critical("496330s cannot put object %s to bucket %s: %s" \
                % (target, self.bucket, str(thrown)))

        return answer
    #---------------------------------------------------------------------------
    def parseS3Url (self, url=None):
        """
        Parse an S3 url into bucket and key components.
        """
        # This pattern will match s3://[bucket]/[key]
        pattern = re.compile(
            r'^s3://(?!^(\d{1,3}\.){3}\d{1,3}$)(^[a-z0-9]([a-z0-9-]*(\.[a-z0-9])?)*$)(/*(.*))',
            flags=re.IGNORECASE
        )

        # Perform the match
        match = pattern.match(url) if url else None

        if not match:
            answer = None
        else:
            answer = ( match.group(1), match.group(3) )

        return answer
    #---------------------------------------------------------------------------
    def get (self, file=None, bucket=None, key=None, split=False):
        """
        Retrieve an object from S3 or a local file and return the entire body.

        Parameters
        ----------
        file : str
            A local file path
        bucket : str
            Mutually exclusive with file, specifies an S3 bucket name
        key : str
            Mutually exclusive with file, specifies an S3 object key
        """
        # Specifying a local file overrides S3
        if file:
            source = file

            try:
                with open(source, "r") as input:
                    candidate = input.read()

                if not split:
                    answer = candidate
                else:
                    answer = [ line.strip() for line in candidate.splitlines() ]
            except Exception as thrown:
                answer = None
                _LOGGER.critical("496340s cannot read file %s: %s" \
                    % (source, str(thrown)))
        else:
            bucket = bucket if bucket else self.bucket
            key = key if key else self.objectKey

            try:
                response = self.primaryClient.get_object(
                    Bucket=bucket,
                    Key=key
                )

                candidate = response.get("Body").read().decode("utf-8")

                if not split:
                    answer = candidate
                else:
                    answer = [ line.strip() for line in candidate.splitlines() ]
            except botocore.exceptions.ClientError as thrown:
                answer = None
                _LOGGER.critical("496350s cannot get object %s from bucket %s: %s" \
                    % (key, bucket, str(thrown)))

        return answer
################################################################################
#
################################################################################
class HubActor (Actor):
    """
    Perform Security Hub API actions. The following API actions are used
    by this concrete class:

    securityhub:GetFindings
    securityhub:BatchUpdateFindings
    """
    #---------------------------------------------------------------------------
    def __init__ (self, region=None, role = None):
        """
        See class definition for details/
        """
        super().__init__(
            "securityhub",
            region=region,
            role=role
        )

        self.findings = []
        self.count = 0
    #---------------------------------------------------------------------------
    def updateFindings (self, region=None, parameters=None):
        """
        Update a finding. Parameters are generated by the MinimalUpdateList
        parameterSets method. This method returns the untouched response
        structure from the API call.
        """
        client = self.getClient(region)

        try:
            response = client.batch_update_findings(**parameters)
        except Exception as thrown:
            response = None

            _LOGGER.critical("496360t securityhub:batch_update_findings: %s" \
                % str(thrown))

        return response
    #---------------------------------------------------------------------------
    def downloadFindings (self, regions=None, filters={}, limit=0):
        """
        Get findings from Security Hub using the securityhub:get_findings API,
        applying filters as necessary, and limiting results as necessary.
        """
        regions = self.regions

        self.findings = []
        downloaded = 0

        # Get findings for each region
        for region in regions:
            _LOGGER.info(f'496370i retrieving findings from region {region}')

            # Get SecurityHub client for this region
            client = self.client[region]

            try:
                token = None

                while True:
                    if not token:
                        answer = client.get_findings(
                            Filters=filters,
                            MaxResults=100
                        )
                    else:
                        answer = client.get_findings(
                            Filters=filters,
                            MaxResults=100,
                            NextToken=token
                        )

                    token = answer.get("NextToken", None)
                    findings = answer.get("Findings", [])
                    downloaded += len(findings)

                    if (downloaded % 1000) == 0:
                        _LOGGER.info("496380i ... %8d findings retrieved" \
                            % downloaded)

                    self.findings += findings

                    # This is the last set of findings if there is no "nexttoken"
                    if not token:
                        break

                    # If we've exceeded the finding limit, we're done
                    if (limit != 0) and (downloaded > limit):
                        _LOGGER.info("496390i %d findings exceeds limit of %d" \
                            % (downloaded, limit))
                        break

                self.count = len(self.findings)

                if (limit != 0) and (downloaded > limit):
                    break

            except client.exceptions.InvalidAccessException as thrown:
                _LOGGER.error('496400e cannot retrieve findings for '
                    + f'region {region}: {thrown.response["Error"]["Message"]}')

        _LOGGER.info("496410i retrieved %d total findings from all regions" \
            % downloaded)

        return self.findings
    #---------------------------------------------------------------------------
    def getFinding (self):
        """
        Generator yields each successive finding from a previous
        downloadFindings operation.
        """
        for finding in self.findings:
            yield finding
################################################################################
#
################################################################################
class MalformedUpdate (Exception):
    """
    There were errors in the uppdate request
    """
    pass
################################################################################
#
################################################################################
class FindingUpdate:
    """
    Represents an update to a Security Hub finding. The update is separated
    into a key signature and an update signature. See MinimumUpdateList for
    more details.
    """
    #---------------------------------------------------------------------------
    def __init__ (self, finding=None):
        """
        See the class description for details.
        """
        if not isinstance(finding, Finding):
            raise MalformedUpdate("496420t findings must be Finding objects")

        self.finding = finding
        self.changes = 0
        self.update = {}
        self.attributes = []
        self.keys = {}

        # Handle the updates - map is assigned to a FindingColumn object
        for column in finding.mapping:
            name = column.columnName
            value = getattr(finding, name)

            # Keep track of keys
            if column.isKey:
                _LOGGER.debug("496430d column %s value '%s' is a key" \
                    % (name, value))

                self.keys[name] = getattr(finding, name)

            # Do not process non-updatable columns
            if not column.isUpdatable:
                _LOGGER.debug("496440d skipping column %s - not updatable" \

                    % name)
                continue

            # Do not process empty strings or None
            if (value != 0) and not value:
                _LOGGER.debug("496450d skipping column %s value '%s'" \
                    % (name, value))

                continue

            _LOGGER.debug("496460d column %s value '%s'" % (name, value))

            # Save the change (we know this is an updatable column)
            self.attributes.append(name)

            Finding._deepSet(
                self.update,
                finding.getFindingColumn(name).keys,
                value
            )

            self.changes += 1

            setattr(self, name, value)

        _LOGGER.debug("496470d finding %s\n\tsignature %s\n\tchanges %d" % \
            (self.keyString, self.signature, self.changes))
    #---------------------------------------------------------------------------
    @property
    def updateRegion (self):
        """
        Return the region associated with this finding. The region is parsed
        from the Id value of the associated finding, which should be an ARN.
        """
        identity = getattr(self.finding, "Id", None)
        answer  = None

        if not identity:
            raise MalformedUpdate("496480t finding does not contain an Id")
        else:
            try:
                answer = identity.split(":")[3]
            except Exception as thrown:
                raise MalformedUpdate("496490t malformed finding ID %s: %s" \
                    % (identity, thrown))

        return answer
    #---------------------------------------------------------------------------
    @property
    def signature (self):
        """
        Returns an update signature as a string composed of the sorted
        attribute names to be changed and their proposed values.
        """
        answer = []

        for attribute in sorted(self.attributes):
            value = getattr(self, attribute)

            if value:
                answer.append("%s=%s" % (attribute, value))

        answer = self.updateRegion + "|".join(answer)

        return answer
    #---------------------------------------------------------------------------
    @property
    def keyString (self):
        """
        Returns a string containing the key values for the finding as
        key=value pairs separated by vertical bars.
        """
        answer = []

        for key in sorted(self.keys.keys()):
            answer.append("%s=%s" % (key, self.keys.get(key)))

        answer = "|".join(answer)

        return answer
################################################################################
#
################################################################################
class StartNextUpdateBatch (Exception):
    """
    Raise this exception when an update set has 100 findings
    """
    pass
################################################################################
#
################################################################################
class MinimumUpdateList:
    """
    Accumulate finding updates and structure the updates so that multiple
    findings with the same changes will only be submitted once.
    """
    #---------------------------------------------------------------------------
    def __init__ (self):
        """
        See class definition for details.
        """
        self.update = {}
        self.findings = {}
        self.regions = {}
        self.sets = 0
    #---------------------------------------------------------------------------
    def add (self, finding=None):
        """
        Add a finding to the minimum update list. Findings are aggregated by
        their update signature (a unique string of keys and values to be
        changed). Multiple findings with the same update signature will be
        submitted as a single update to Security Hub.
        """
        update = FindingUpdate(finding)

        if update.changes > 0:
            region = update.updateRegion
            signature = region + "|" + update.signature

            _LOGGER.debug("496500d found signature '%s'" % signature)

            # This is the first time we've seen this signature
            if not (signature in self.update):
                _LOGGER.debug("496510d saved update for signature '%s'" \
                    % signature)

                self.update[signature] = update

            # This is the first time we've seen this finding
            if not (signature in self.findings):
                _LOGGER.debug("496520d initialized findings and region for '%s'" \
                    % signature)

                self.findings[signature] = []
                self.regions[signature] = region
                self.sets += 1

            # Track all findings for a signature
            self.findings[signature].append(finding)
            _LOGGER.debug("496530d added finding to '%s'" % signature)
    #---------------------------------------------------------------------------
    @staticmethod
    def updateCount (update=[]):
        """
        How many updates are in this batch
        """
        return len(update.get("FindingIdentifiers", []))
    #---------------------------------------------------------------------------
    def parameterSets (self):
        """
        Generator to yield each update as a set of parameters to the
        securityhub:batch_update_findings API.
        """
        signatures = 0

        # Go through each update signature and findings
        for signature, findings in self.findings.items():
            region = self.regions[signature]
            update = self.update.get(signature).update

            update["FindingIdentifiers"] = []

            _LOGGER.debug(f'496540d signature {signature} update {update}')

            signatures += 1

            _LOGGER.info(f'496550i processing update set {signatures}...')

            # Now loop through findings to build an update set
            for finding in findings:
                update["FindingIdentifiers"].append(finding.keys)
                _LOGGER.debug(f'496560d adding {len(finding.keys)} to update now {self.updateCount(update)}')

                # The upate set can be contain no more than 100 finding IDs --
                # yield the updaet set and then clear it out
                if self.updateCount(update) >= 100:
                    _LOGGER.debug(f'496570d batch has >=100 finding IDs, yielding to {region}')
                    yield region, update

                    update["FindingIdentifiers"] = []

            # We end up here whenever the number of findings is < 100
            if self.updateCount(update) > 0:
                _LOGGER.debug(f'496580d yielding {self.updateCount(update)} finding IDs to {region}')
                yield region, update
    #----------------------------------------------------------------------------
    @staticmethod
    def apply (update=None, region=None, actor=None):
        """
        Static method to apply an update to SecurityHub findings. The update
        must be the value yielded by the parameterSets generator, and the
        actor must be a HubActor object.

        This method should probably be moved to HubActor as an object method,
        it but suffices for now.
        """
        response = {}

        if not isinstance(actor, HubActor):
            raise MalformedUpdate("496590t MinimumUpdateList.apply requires a " + \
                "HubActor object")
        try:
            response = actor.updateFindings(region=region, parameters=update)
        except Exception as thrown:
            response = None
            _LOGGER.critical("496600s unexpected error %s" % str(thrown))

        if (response == None):
            _LOGGER.critical("496610s bad things in MinimumUpdateList.apply")

        return response


from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('api/activities/', include(('activities.urls', 'activities'), namespace='activities') ),
    path('api/colors/', include(('colors.urls', 'colors'), namespace='colors') ),
    path('api/teams/', include(('teams.urls', 'teams'), namespace='teams') ),
    path('api/users/',include(('users.urls','users'), namespace='users')),

]


from __future__ import division
import logging
import numpy as np

# set up a global logger
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

#--------------------------------------------------------------------------------------
def get_coefficients_from_sums(PESUM, ETSUM, RSUM, PRSUM, SPSUM, ROSUM, PLSUM, TLSUM):

    # create "zero limits", i.e. values to use when handling zero or missing values in the following calculations
    two_limits = [1.0, 0.0]
    single_limit = [0.0]

    # divide and limit actuals over potentials
    alpha = divide_and_limit(ETSUM, PESUM, two_limits)
    beta = divide_and_limit(RSUM, PRSUM, two_limits)
    gamma = divide_and_limit(ROSUM, SPSUM, two_limits)
    delta = divide_and_limit(TLSUM, PLSUM, single_limit)

#     # original Matlab code transposed the arrays before returning them, not sure if this is necessary with Python/numpy
#     alp = alp'
#     bet = bet'
#     gam = gam'
#     delta = delta'

    return alpha, beta, gamma, delta

#---------------------------------------------------------------------------
def divide_and_limit(numerators, denominators, zero_limits):

    # make sure we have compatible arrays for the following computations
    if denominators.shape != numerators.shape:
        message = 'numerator and denominator arrays do not share the same shape'
        logger.error(message)
        raise ValueError(message)

    # get the original shape so that later we can reshape the arrays
    # that we'll flatten back into their original shape
    original_shape = denominators.shape
    denominators = denominators.flatten()
    numerators = numerators.flatten()

    # create an array of values corresponding to the shape of the input arrays
    results = np.full(denominators.shape, np.NAN)

    # get a column vector of indices where denominator is not zero,
    # so as to avoid divide by zero in the following calculation
    not_zeros = np.where(denominators != 0)
    if len(not_zeros[0]) > 0:
        results[not_zeros] = numerators[not_zeros] / denominators[not_zeros]

    # get array of index values corresponding to the denominators array,
    # for example if array is 4 elements long the we get an indices array: [0, 1, 2, 3]
    index_values = np.array(range(len(denominators)), np.int)

    # perform an XOR on the array indices and the 'not zeros' array of indices
    # to get the indices of the array where the value is zero
    zeros = np.setxor1d(index_values, not_zeros)
    if zeros.size > 0:

        # we have a zero denominator sum value so we can't perform the normal calculation at these points,
        # so we limit the value to the zero limits

        if len(zero_limits) == 1:

            results[zeros] = zero_limits[0]

        elif len(zero_limits) == 2:

            # find indices where the value is zero, set the value at these indices to the first zero limit
            limits = np.where(numerators[zeros] == 0)
            if limits[0].size > 0:
                results[zeros[limits]] = zero_limits[0]

            # find indices where the value is not zero, set the value at these indices to the second zero limit
            limits = np.where(numerators[zeros] != 0)
            if limits[0].size > 0:
                results[zeros[limits]] = zero_limits[1]

        else:
            message = 'Invalid zero limits argument, must contain 1 or 2 values'
            logger.error(message)
            raise ValueError(message)

    # reshape the results back to our original shape and return
    return np.reshape(results, original_shape)


from re import compile as re_compile
from re import DOTALL, VERBOSE

# Constants
# Reasonably well-behaved sentence end regular expression
SENTENCE_END_REGEX = re_compile(r'''
        # Require a leading non-whitespace character for the sentence
        \S
        # Then, anything goes, but don't be greedy
        .*?
        # Anchor the sentence at...
        (:?
            # One (or multiple) terminal character(s)
            #   followed by one (or multiple) whitespace
            (:?(\.|!|\?|。|！|？)+(?=\s+))
        | # Or...
            # Newlines, to respect file formatting
            (:?(?=\n+))
        | # Or...
            # End-of-file, excluding whitespaces before it
            (:?(?=\s*$))
        )
    ''', DOTALL | VERBOSE)
# Only newlines can end a sentence to preserve pre-processed formatting
SENTENCE_END_NEWLINE_REGEX = re_compile(r'''
        # Require a leading non-whitespace character for the sentence
        \S
        # Then, anything goes, but don't be greedy
        .*?
        # Anchor the sentence at...
        (:?
            # One (or multiple) newlines
            (:?(?=\n+))
        | # Or...
            # End-of-file, excluding whitespaces before it
            (:?(?=\s*$))
        )
    ''', DOTALL | VERBOSE)
###

def _refine_split(offsets, original_text):
    # Postprocessor expects newlines, so add. Also, replace
    # sentence-internal newlines with spaces not to confuse it.
    new_text = '\n'.join((original_text[o[0]:o[1]].replace('\n', ' ')
                          for o in offsets))

    from sspostproc import refine_split
    output = refine_split(new_text)

    # Align the texts and see where our offsets don't match
    old_offsets = offsets[::-1]
    # Protect against edge case of single-line docs missing
    #   sentence-terminal newline
    if len(old_offsets) == 0:
        old_offsets.append((0, len(original_text), ))
    new_offsets = []
    for refined_sentence in output.split('\n'):
        new_offset = old_offsets.pop()
        # Merge the offsets if we have received a corrected split
        while new_offset[1] - new_offset[0] < len(refined_sentence) - 1:
            _, next_end = old_offsets.pop()
            new_offset = (new_offset[0], next_end)
        new_offsets.append(new_offset)

    # Protect against missing document-final newline causing the last
    #   sentence to fall out of offset scope
    if len(new_offsets) != 0 and new_offsets[-1][1] != len(original_text) - 1:
        start = new_offsets[-1][1] + 1
        while start < len(original_text) and original_text[start].isspace():
            start += 1
        if start < len(original_text) - 1:
            new_offsets.append((start, len(original_text) - 1))

    # Finally, inject new-lines from the original document as to respect the
    #   original formatting where it is made explicit.
    last_newline = -1
    while True:
        try:
            orig_newline = original_text.index('\n', last_newline + 1)
        except ValueError:
            # No more newlines
            break

        for o_start, o_end in new_offsets:
            if o_start <= orig_newline < o_end:
                # We need to split the existing offsets in two
                new_offsets.remove((o_start, o_end))
                new_offsets.extend(((o_start, orig_newline, ),
                                    (orig_newline + 1, o_end), ))
                break
            elif o_end == orig_newline:
                # We have already respected this newline
                break
        else:
            # Stand-alone "null" sentence, just insert it
            new_offsets.append((orig_newline, orig_newline, ))

        last_newline = orig_newline

    new_offsets.sort()
    return new_offsets

def _sentence_boundary_gen(text, regex):
    for match in regex.finditer(text):
        yield match.span()

def regex_sentence_boundary_gen(text):
    for o in _refine_split([_o for _o in _sentence_boundary_gen(
            text, SENTENCE_END_REGEX)], text):
        yield o

def newline_sentence_boundary_gen(text):
    for o in _sentence_boundary_gen(text, SENTENCE_END_NEWLINE_REGEX):
        yield o

"""Basic sentence splitter using brat segmentation to add newlines to input
text at likely sentence boundaries."""

import sys
from os.path import join as path_join
from os.path import dirname
from sys import path as sys_path

def _text_by_offsets_gen(text, offsets):
    for start, end in offsets:
        yield text[start:end]

def _normspace(s):
    import re
    return re.sub(r'\s', ' ', s)

def sentencebreaks_to_newlines(text):
    # print("text: ",text)
    offsets = [o for o in regex_sentence_boundary_gen(text)]

    # break into sentences
    sentences = [s for s in _text_by_offsets_gen(text, offsets)]
    # print(sentences)

    # join up, adding a newline for space where possible
    orig_parts = []
    new_parts = []

    sentnum = len(sentences)
    for i in range(sentnum):
        sent = sentences[i]
        orig_parts.append(sent)
        new_parts.append(sent)

        if i < sentnum - 1:
            orig_parts.append(text[offsets[i][1]:offsets[i + 1][0]])

            if (offsets[i][1] < offsets[i + 1][0] and
                    text[offsets[i][1]].isspace()):
                # intervening space; can add newline
                new_parts.append(
                    '\n' + text[offsets[i][1] + 1:offsets[i + 1][0]])
            else:
                new_parts.append(text[offsets[i][1]:offsets[i + 1][0]])

    if len(offsets) and offsets[-1][1] < len(text):
        orig_parts.append(text[offsets[-1][1]:])
        new_parts.append(text[offsets[-1][1]:])

    # sanity check
    # assert text == ''.join(orig_parts), "INTERNAL ERROR:\n    '%s'\nvs\n    '%s'" % (
    #     text, ''.join(orig_parts))

    splittext = ''.join(new_parts)

    # sanity
    # assert len(text) == len(splittext), "INTERNAL ERROR"
    # assert _normspace(text) == _normspace(splittext), "INTERNAL ERROR:\n    '%s'\nvs\n    '%s'" % (
    #     _normspace(text), _normspace(splittext))
    # print("splittext: ",splittext)

    return splittext

def main(argv):
    while True:
        text = sys.stdin.readline()
        if len(text) == 0:
            break
        sys.stdout.write(sentencebreaks_to_newlines(text))

if __name__ == "__main__":
    sys.exit(main(sys.argv))


# -*- coding: utf-8 -*-
"""
Created on Wed Aug 14 19:03:35 2019

@author: 10539
"""

def n_matching(x,y):
    l=len(y)
    for i in range(0,l-len(x)):
        for j in range(len(x)):
            if y[j+i]!=x[j]:break
            if j==len(x)-1:return i
    return("do not exist")
print(n_matching("atax","iamatadorr"))


"""Find non-constructible changes from array of coins."""

# O(n^2) time
def nonConstructibleChange(coins):
    if len(coins) == 0:
        return 1
    # use a set to track existing changes and remove duplicates
    changes = set()
    for i in range(len(coins)):
        coin = coins[i]
        # looping over existing changes and add current coin
        for j in changes.copy():
            changes.add(j + coin)
        # add current coin
        changes.add(coin)
    for k in range(1, sum(changes)):
        if k not in changes:
            return k
    return max(changes) + 1

# O(log(n)) time | O(1) space
def nonConstructibleChange2(coins):
    coins.sort()
    # start with 0 and increasing
    maxChange = 0
    for coin in coins:
        # we cannot make maxChange + 1 because coin is bigger than maxChange + 1
        if maxChange + 1 < coin:
            return maxChange + 1
        else:
            maxChange += coin
    return maxChange + 1

import unittest

class TestProgram(unittest.TestCase):
    def test_case_1(self):
        input = [5, 7, 1, 1, 2, 3, 22]
        expected = 20
        actual = nonConstructibleChange(input)
        self.assertEqual(expected, actual)
        actual = nonConstructibleChange2(input)
        self.assertEqual(expected, actual)


import threading
import time
import serial
import sys
import glob
from headServer.settings import LINUX, ARDUINOPLUG

class Singleton(type):
	_instances = {}

	def __call__(cls, *args, **kwargs):
		if cls not in cls._instances:
			cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
		return cls._instances[cls]

class SerialCom(threading.Thread):
	def __init__(self, port='COM3', baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS):
		if LINUX:
			port = 'tty0'
		print("created")
		super(SerialCom, self).__init__(daemon=True)
		self.running = True
		self.open = False
		self.available = False
		availablePort = self.serial_ports()
		print(availablePort)
		if ARDUINOPLUG:
			if LINUX:
				for p in availablePort:
					if p.startswith('/dev/tty') and p != '/dev/ttyAMA0':
						print("start with good start")
						self.available = True
			else:
				if len(availablePort) > 0:
					self.available = True
		else:
			print("ici")
			self.available = False
		if self.available:
			try:
				self.ser = serial.Serial(
					port=port,
					baudrate=baudrate,
					parity=parity,
					stopbits=stopbits,
					bytesize=bytesize
				)
				"""parity=parity,
					stopbits=stopbits,
					bytesize=bytesize"""
				#self.ser.write("testOpen")
			except IOError as e:
				print(e)

		else:
			print("no arduino available")
			self.ser = None

	def run(self):
		while self.running:
			if self.available:
				self.open = self.initSerial()

				try:
					while self.open:
						input = self.ser.readline()
						if(input != ""):
							print(bytes.decode(input))
				except IOError:
					pass
				self.closeSerial()
			else:
				#TODO CHECK IF NEW ARDUINO CARD PLUGGED IN EVERY 3 SECONDS SOR SOMETHING LIKE THAT
				self.running = False

	def write(self, data):
		if self.open:
			#maybe need ths line:
			#self.ser.write(data + '\r\n')
			print("send this : " + str(data))
			self.ser.write(data)
			#self.ser.write(str(data))

	def initSerial(self):
		if self.available:
			self.ser.open()
			self.open = True
		return self.open

	def closeSerial(self):
		if self.available:
			self.open = False
			self.running = False
			self.ser.close()

	def serial_ports(self):
		""" Lists serial port names

			:raises EnvironmentError:
				On unsupported or unknown platforms
			:returns:
				A list of the serial ports available on the system
		"""
		if sys.platform.startswith('win'):
			ports = ['COM%s' % (i + 1) for i in range(256)]
		elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
			# this excludes your current terminal "/dev/tty"
			ports = glob.glob('/dev/tty[A-Za-z]*')
		elif sys.platform.startswith('darwin'):
			ports = glob.glob('/dev/tty.*')
		else:
			raise EnvironmentError('Unsupported platform')

		result = []
		for port in ports:
			try:
				s = serial.Serial(port)
				s.close()
				result.append(port)
			except (OSError, serial.SerialException):
				pass
		return result

class MotorCommunication(SerialCom, metaclass=Singleton):
	def __init__(self, port='COM3', baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS):
		super(MotorCommunication, self).__init__(port, baudrate, parity, stopbits, bytesize)

	def sendJson(self, json):
		if not isinstance(json, str):
			json = str(json)
		self.write(bytes(json, "utf-8"))

	def sendMove(self, motorNumber, position):
		json = {"motorNumber": motorNumber, "position": position}

if __name__ == '__main__':
	serialCom = SerialCom()

	serialCom.daemon = True
	serialCom.start()

	while True:
		time.sleep(2)
		""""#print(serialCom.serial_ports())
		#serialCom.write("a" + "\r\n")
		serialCom.write(bytes("a", "utf-8"))
		time.sleep(2)
		#serialCom.write("z" + "\r\n")
		serialCom.write(bytes("z", "utf-8"))"""
		# {"action":"moveHeadRight", "degrees": 10}
		# {"action":"moveHeadUp", "degrees": 20}
		serialCom.write(bytes("{\"action\":\"moveHeadRight\", \"degrees\": 10}", "utf-8"))
		time.sleep(20)


## CodeEval
## String and Arrows

import sys

f = open(sys.argv[1], 'r')

for line in f:

    line = line.strip()
    if line:
        cnt = 0
        for i in xrange(len(line)-5+1):
            if line[i:i+5] == '>>-->' or line[i:i+5] == '<--<<':
                cnt += 1
        sys.stdout.write(str(cnt) + '\n')

f.close()


#!/usr/bin/env python2

import rospy
from cv_bridge import CvBridge
from sensor_msgs.msg import Image
import cv2
import numpy as np

pub = rospy.Publisher('/lepton_processed', Image, queue_size=10)

CAMERA_MATRIX = np.array([[157.57555346, 0.00000000e+00, 79.06174387],
                          [0.00000000e+00, 157.6567718, 64.20473234],
                          [0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
DISTORTION_COEFFICIENTS = np.array([-0.36583139,  0.34630364, -0.00260575,  0.00041044, -0.01098873])
LEPTON_RESOLUTION = (160, 120)
OUTPUT_MATRIX, OUTPUT_ROI = cv2.getOptimalNewCameraMatrix(CAMERA_MATRIX, DISTORTION_COEFFICIENTS, LEPTON_RESOLUTION, 1, LEPTON_RESOLUTION)

DO_UNDISTORT = True
DO_CONTRAST = True
DO_RESIZE = False

bridge = CvBridge()

def callback(data):
    image = bridge.imgmsg_to_cv2(data)
    processed_image = process_lepton_image(image)
    processed_data = bridge.cv2_to_imgmsg(processed_image, "mono8")
    pub.publish(processed_data)

def main():
    rospy.init_node('lepton_processor', anonymous=False)
    rospy.Subscriber("lepton", Image, callback)
    rospy.spin()

def process_lepton_image(image):
    if(DO_UNDISTORT):
        image = cv2.undistort(image, CAMERA_MATRIX, DISTORTION_COEFFICIENTS, None, OUTPUT_MATRIX)
        x, y, w, h = OUTPUT_ROI

        # Comment out this line to leave the undistorted area in the image
        #image = image[y:y+h, x:x+w]
    if(image.dtype == 'uint16'):
        if(DO_CONTRAST):
            clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
            image = clahe.apply(image)
        image = (image / 256).astype(np.uint8)
    if(DO_RESIZE):
        width = int(image.shape[1] / 3)
        height = int(image.shape[0] / 3)
        image = cv2.resize(image, (width, height))

    return image

if __name__ == "__main__":
    try:
        main()
    except rospy.ROSInterruptException:
        pass