#!/usr/bin/env python

import json
from mimetypes import guess_type
import os
from re import findall
import subprocess

from flask import abort, make_response
from werkzeug.datastructures import Headers

import app_config
import copytext
from flask import Blueprint
from render_utils import BetterJSONEncoder, flatten_app_config

static = Blueprint('static', __name__)

# Render JST templates on-demand
@static.route('/js/templates.js')
def _templates_js():
    r = subprocess.check_output(["node_modules/universal-jst/bin/jst.js", "--template", "underscore", "jst"])

    return make_response(r, 200, { 'Content-Type': 'application/javascript' })

# Render LESS files on-demand
@static.route('/less/<string:filename>')
def _less(filename):
    if not os.path.exists('less/%s' % filename):
        abort(404)

    r = subprocess.check_output(["node_modules/less/bin/lessc", "less/%s" % filename])

    return make_response(r, 200, { 'Content-Type': 'text/css' })

# Render application configuration
@static.route('/js/app_config.js')
def _app_config_js():
    config = flatten_app_config()
    js = 'window.APP_CONFIG = ' + json.dumps(config, cls=BetterJSONEncoder)

    return make_response(js, 200, { 'Content-Type': 'application/javascript' })

# Render copytext
@static.route('/js/copy.js')
def _copy_js():
    copy = 'window.COPY = ' + copytext.Copy(app_config.COPY_PATH).json()

    return make_response(copy, 200, { 'Content-Type': 'application/javascript' })

# Audio route to serve range headers for Safari.
@static.route('/assets/audio/<string:filename>')
def audio(filename):
    from flask import Response, request

    path = 'www/assets/audio/%s' % filename
    with open(path) as f:
        headers = Headers()
        headers.add('Content-Disposition', 'attachment', filename=filename)
        headers.add('Content-Transfer-Encoding','binary')

        status = 200
        size = os.path.getsize(path)
        begin = 0
        end = size - 1

        if request.headers.has_key('Range'):
            ranges = findall(r'\d+', request.headers['Range'])
            begin = int(ranges[0])
            if len(ranges) > 1:
                end = int(ranges[1])

            if begin != 0 or end != size - 1:
                status = 206
                headers.add('Accept-Ranges', 'bytes')
                headers.add('Content-Range', 'bytes %i-%i/%i' % (begin, end, end - begin) )

        headers.add('Content-Length', str( (end - begin) + 1) )

        response = Response(
            file(path),
            status = status,
            mimetype = 'application/octet-stream',
            headers = headers,
            direct_passthrough = True
        )

        return response

# Server arbitrary static files on-demand
@static.route('/<path:path>')
def _static(path):
    try:
        with open('www/%s' % path) as f:
            return make_response(f.read(), 200, { 'Content-Type': guess_type(path)[0] })
    except IOError:
        abort(404)


#
# @lc app=leetcode.cn id=1640 lang=python3
#
# [1640] 能否连接形成数组
#
# https://leetcode-cn.com/problems/check-array-formation-through-concatenation/description/
#
# algorithms
# Easy (64.96%)
# Likes:    40
# Dislikes: 0
# Total Accepted:    14.4K
# Total Submissions: 22.2K
# Testcase Example:  '[85]\n[[85]]'
#
# 给你一个整数数组 arr ，数组中的每个整数 互不相同 。另有一个由整数数组构成的数组 pieces，其中的整数也 互不相同 。请你以 任意顺序 连接
# pieces 中的数组以形成 arr 。但是，不允许 对每个数组 pieces[i] 中的整数重新排序。
#
# 如果可以连接 pieces 中的数组形成 arr ，返回 true ；否则，返回 false 。
#
#
#
# 示例 1：
#
#
# 输入：arr = [15,88], pieces = [[88],[15]]
# 输出：true
# 解释：依次连接 [15] 和 [88]
#
#
# 示例 2：
#
#
# 输入：arr = [49,18,16], pieces = [[16,18,49]]
# 输出：false
# 解释：即便数字相符，也不能重新排列 pieces[0]
#
#
# 示例 3：
#
#
# 输入：arr = [91,4,64,78], pieces = [[78],[4,64],[91]]
# 输出：true
# 解释：依次连接 [91]、[4,64] 和 [78]
#
#
#
# 提示：
#
#
# 1 <= pieces.length <= arr.length <= 100
# sum(pieces[i].length) == arr.length
# 1 <= pieces[i].length <= arr.length
# 1 <= arr[i], pieces[i][j] <= 100
# arr 中的整数 互不相同
# pieces 中的整数 互不相同（也就是说，如果将 pieces 扁平化成一维数组，数组中的所有整数互不相同）
#
#
#

from typing import List

# @lc code=start
class Solution:
    '''
    参考了网上人家的答案

    因为数组中的每个数都不相同，不妨考虑map，
    做一个map，对pieces中每个数组，其第一个元素作为key，
    其在pieces中的索引作为value，之后遍历arr，对每个元素
    在map中查找，找不到为false。找到之后再判断之后的元素，
    周而复始，直到整个数组都能够被找完。否则false。
    '''

    def canFormArray(self, arr: List[int], pieces: List[List[int]]) -> bool:

        di = {}  # 用来存储数组第一个元素和其index的map
        for i in range(len(pieces)):
            di[pieces[i][0]] = i

        i = 0
        while i < len(arr):

            # 找不到直接false
            if arr[i] not in di: return False

            # 找到了接着判断后面的
            li = pieces[di[arr[i]]]
            for j in li:
                if j != arr[i]: return False
                i += 1

        return True

# @lc code=end

1640.能否连接形成数组.py


import wx
from common import utils

class PreviewPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent, style=wx.BORDER_RAISED)
        # hbox_header = wx.GridBagSizer(hgap=10, vgap=0)
        hbox_header = wx.BoxSizer(wx.HORIZONTAL)
        lbl_header = wx.StaticText(self, -1, "Preview of data to be generated")
        # lbl_header.SetFont(wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.NORMAL))
        refresh_icon = utils.resize_images(r'.\icons\refresh-icon_1.png', 10, 10)
        btn_refresh = wx.BitmapButton(self, wx.ID_ANY, wx.Bitmap(refresh_icon))#, style=wx.NO_BORDER)
        hbox_header.Add(lbl_header, flag=wx.ALIGN_LEFT | wx.EXPAND)
        hbox_header.Add(btn_refresh, flag=wx.ALIGN_RIGHT | wx.EXPAND)
        header_line = wx.StaticLine(self, wx.ID_ANY)
        config_box = wx.BoxSizer(wx.VERTICAL)
        # self.SetMinSize((800,400))

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(hbox_header, 0, wx.ALIGN_TOP | wx.TOP | wx.EXPAND, 5)
        sizer.Add(header_line, 0, wx.ALIGN_TOP | wx.TOP | wx.EXPAND, 5)
        self.SetSizer(sizer)


from dummy_equation_solver import *

import sys
sys.path.append("../../CodeObserver")

from graphic_smoother import *
from dummy_equation_solver import *
from matplotlib import pyplot as plt
import statistics

median = statistics.mean(all_resistance_values)
sigma = statistics.stdev(all_resistance_values)

print("____________________________________________")
print("Mean value is:", median)
print("Standard deviation is:", sigma)

range_min_value = median - sigma
range_max_value = median + sigma

satisfies_range = lambda x: range_min_value <= x <= range_max_value

used_resistances = [value for value in all_resistance_values if satisfies_range(value)]
used_resistance_indexes = [index for (index, value) in enumerate(all_resistance_values) if satisfies_range(value)]

used_dispersion_sum = 0
for r_index in used_resistance_indexes:
	used_dispersion_sum += all_resistance_dispersions[r_index]

resistance_dispersion_average = used_dispersion_sum / len(used_resistance_indexes)
resultant_resistance_dispersion = resistance_dispersion_average / (len(used_resistance_indexes) / 2)
print("Resultant resistance dispersion:", resultant_resistance_dispersion)

print("Resistances left after dropping out: ", used_resistances)

if __name__ == '__main__':
	from equation_solving_optimization import optimal_r
	print("Optimal R is:", optimal_r)

	distribution_density = count_density(all_resistance_values, 0.1, 1000, smoothing_normal)
	# print(distribution_density)

	# Distribution:
	plt.plot([i[0] for i in distribution_density], [i[1] for i in distribution_density])

	# Points
	plt.scatter(all_resistance_values, [0] * len(all_resistance_values), color="red", edgecolors = "darkred", s=[200]*len(all_resistance_values))

	# Median:
	max_density_value = max([i[1] for i in distribution_density])
	print(f"Max density value is {max([i[1] for i in distribution_density])}")
	plt.plot([median, median], [0, max_density_value], linestyle = ":", label = "Median", color = "#0FD00F")

	# Median - sigma:
	plt.plot([range_min_value, range_min_value], [0, max_density_value], linestyle = "-.", color = "#FF1111", label = "Range start")

	# Median + sigma:
	plt.plot([range_max_value, range_max_value], [0, max_density_value], linestyle = "--", color = "#FF1111", label = "Range end")

	# Optimal R value:
	plt.plot([optimal_r, optimal_r], [0, max_density_value], linestyle = "-.", color = "cyan", label = "Optimal resistance", linewidth = 5)

	plt.legend()
	plt.show()


from typing import List

class Solution1:
    def __init__(self):
        self.result = []  # 存放组合集合
        self.path = []  # 符合条件的组合

    def isPalindrome(self, sub):
        start = 0
        end = len(sub) - 1
        for i in range(len(sub)):
            if sub[start] != sub[end]:
                return False

            start += 1
            end -= 1
        return True

    def backtracking(self, s, start_index):
        # 如果起始位置已经大于s的大小，说明已经找到了一组分割方案了
        if start_index >= len(s):
            self.result.append(self.path)
            return

        for i in range(start_index, len(s)):
            sub = s[start_index: i + 1]
            if self.isPalindrome(sub):  # 是回文子串
                self.path.append(sub)
            else:
                continue

            self.backtracking(s, i + 1)
            self.path = self.path[:len(self.path) - 1]

    def partition(self, s: str) -> [[str]]:
        """
        切割等同于组合，每次切割完判断是不是回文，然后添加再继续递归
        """

        self.backtracking(s, 0)

        return self.result

class Solution2:
    def __init__(self):
        self.result = []
        self.path = []

    def check(self, s):
        left = 0
        right = len(s) - 1

        while left < right:
            if s[left] != s[right]:
                return False
            left += 1
            right -= 1

        return True

    def backtracking(self, s, index):
        if index >= len(s):
            self.result.append(self.path)
            return

        for i in range(index, len(s)):
            sub = s[index: i + 1]
            if self.check(sub):
                self.path.append(sub)
                self.backtracking(s, i + 1)
                self.path = self.path[:-1]

        return

    def partition(self, s: str) -> List[List[str]]:

        self.backtracking(s, 0)

        return self.result

s = Solution1()
print(s.partition(s="aab"))


#!/usr/bin/env python3
import os

import click
import sys
import shelve
import operator

import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.lines as mlines

from math import ceil
from numpy import sqrt
from collections import Counter, OrderedDict
from topotool.graphs import Graph
from copy import deepcopy
from copy import Error
from jinja2 import Template
from itertools import chain

MAX_REPL_AGREEMENTS = 4
MIN_REPL_AGREEMENTS = 2

GRAPH_DATA = "graph_input_data"
GRAPH_OBJECT = "graph_object"
GRAPH_NX = "graph_networkx"
MASTER = "master_node"

DB_KEYS = [
    GRAPH_DATA,
    GRAPH_OBJECT,
    GRAPH_NX,
    MASTER,
]

SCALING_DEFAULTS = os.path.join(os.path.dirname(__file__), "./data/")

def load_context(ctx, storage):
    ctx.ensure_object(dict)
    if not ctx.obj:
        for db_key in DB_KEYS:
            try:
                with shelve.open(storage) as db:
                    ctx.obj[db_key] = db[db_key]
            except KeyError:
                pass

    return ctx

def save_data(path, data):
    """
    Writes data with file.write() to file specified by path
    if path is not specified use stdout
    """
    with open(path, "w") as data_file:
        data_file.write(data)

def load_jinja_template(path):
    with open(path) as file_:
        template = Template(file_.read())
    return template

def gen_metadata(
    topo_nodes,
    node_os,
    destination,
    project,
    run,
    job,
    template,
    tool_repo,
    tool_branch,
):
    metadata = template.render(
        topo_nodes=topo_nodes,
        node_os=node_os,
        project=project,
        run=run,
        job=job,
        tool_repo=tool_repo,
        tool_branch=tool_branch,
    )
    output_file = os.path.join(destination, "scaling_metadata.yaml")
    save_data(output_file, metadata)

def circle_topology(node_cnt, master="y0"):
    """Experimental approach we need to half the topology"""
    nodes = []
    for i in range(node_cnt):
        # FIXME master is not y0 ?
        nodes.append(f"{master[0]}{i}")

    G = nx.cycle_graph(nodes)

    # this connects the most distant node

    res = nx.single_source_dijkstra_path_length(G, master)

    max_dct = {"dist": 0, "node": master}

    for node, distance in res.items():
        # print("Node", node, "distance from master", distance)
        if distance > max_dct["dist"]:
            max_dct["dist"] = distance
            max_dct["node"] = node
        # print(max_dct["dist"])

    G.add_edge(max_dct["node"], master)

    degrees = dict(G.degree())
    sum_of_edges = sum(degrees.values())
    avg_degree = sum_of_edges / len(G)

    # print(max_dct["dist"], max_dct["node"])

    # use distance that is the most distant node
    #     and half the index for next node
    # eg. first is circle of 10 then 0-5
    # dist is 5
    # we take 5/2 which is 2.5
    # we round it,
    # connect 3 with 3+max dst

    # produce_output_image(G)

    node_to_back = []
    node_master = []
    master_found = False
    for node in list(G):
        if node == master:
            master_found = True

        if master_found:
            node_master.append(node)
        else:
            node_to_back.append(node)

    node_list = node_master + node_to_back

    possible_edges = []
    act_index = max_dct["dist"] / 2
    # dist = max_dct["dist"]
    # print(node_list)
    # print(dist / 2)

    while act_index > 1:
        idx = ceil(act_index)
        indexes = [i for i in range(idx, ceil(len(node_list) / 2), idx)]

        for node_pos in indexes:

            possible_edges.append(
                (node_list[node_pos], node_list[node_pos + max_dct["dist"]])
            )

        # print("RES", possible_edges)
        act_index = act_index / 2

    # to have more symmetric result we reverse order of edges
    possible_edges.reverse()
    # print(possible_edges)

    # 2.4 works fine with 10 nodes but we use 2.6
    while possible_edges and avg_degree < 2.6:
        next_edge = possible_edges.pop()
        G.add_edge(*next_edge)
        degrees = dict(G.degree())
        sum_of_edges = sum(degrees.values())
        avg_degree = sum_of_edges / len(G)
        # produce_output_image(G)

    return G

def create_level(level, max_levels, level_width):
    level_base = f"y{level}"
    # if the actual level is first or last return only level base (one server)
    if level == max_levels - 1 or level == 0:
        return [level_base]

    level = []
    # otherwise create level with number of servers equal to max_level_width
    for pos in range(level_width):
        level.append(f"{level_base}x{pos}")

    return level

def create_basic_topo(max_width, max_levels):
    topo = {
        "levels": {},
        "predecessors": {},
        "edges": [],
        "backbone_edges": [],
    }

    predecessors = {}
    for level in range(max_levels):
        topo["levels"][level] = create_level(level, max_levels, max_width)
        pred_idx = 0
        if level:
            for replica in topo["levels"][level]:
                predecessor = topo["levels"][level - 1][pred_idx]
                predecessors[replica] = predecessor

                if level != max_levels - 1:
                    topo["edges"] = topo["edges"] + [(predecessor, replica)]
                else:  # if max level then add all from previous level as pred
                    topo["edges"] = topo["edges"] + [
                        (pred, replica) for pred in topo["levels"][level - 1]
                    ]

                if level - 1 != 0 and level != max_levels - 1:
                    pred_idx += 1

    topo["backbone_edges"] = [(value, key) for key, value in predecessors.items()]
    topo["predecessors"] = predecessors

    return topo

@click.group(chain=True)
@click.option(
    "-s",
    "--storage",
    default="./.graph_storage.db",
    help="Location of the context db file",
    show_default=True,
)
@click.pass_context
def graphcli(ctx, storage):
    """
    topotool - a tool for tweaking and deploying FreeIPA replication topology
    """
    ctx = load_context(ctx, storage)

def levels_from_fist_successors(successors):
    return [[successors[0][0]], list(successors[0][1])]

def predecessors_from_first_levels(levels):
    res = {}
    for node in levels[-1]:
        res[node] = levels[0][0]
    return res

@graphcli.command()
@click.argument("input_file")
@click.option(
    "-m", "--master", default="y0", help="Specify a master node", show_default=True
)
@click.option(
    "-s",
    "--storage",
    default="./.graph_storage.db",
    help="Change a location for db file (to use different topology use load command)",
    show_default=True,
)
@click.option(
    "-t",
    "--type",
    "data_format",
    default="ipa",
    help="Switch between input file formats",
    show_default=True,
    type=click.Choice(["ipa", "edges"]),
)
@click.pass_context
def load(ctx, input_file, data_format, master, storage):
    """Loads graph data to context dictionary"""
    ctx.ensure_object(dict)

    with open(input_file, "r") as f:
        ctx.obj[GRAPH_DATA] = f.read().splitlines()

    data = deepcopy(ctx.obj[GRAPH_DATA])
    try:
        graph_data = Graph(data, data_format=data_format)
    except ValueError:
        sys.stderr.write(
            "Loading topology from file failed, please check the input file\n"
        )
        sys.exit(1)
    except Error as e:
        sys.stderr.write(f"Input error: {e}\n")
        sys.exit(1)
    except Exception:
        sys.stderr.write("Unexpected input file error\n")
        sys.exit(9)

    ctx.obj[GRAPH_OBJECT] = graph_data

    G = nx.Graph()
    G.name = f"Loaded graph of topology from {input_file}"
    for vertex in graph_data.vertices:
        G.add_node(vertex)

    if master not in G:
        sys.stderr.write(
            f"Loaded topology does not contain {master} master node,"
            " please specify master node using option `--master`\n"
        )
        sys.exit(1)

    G.add_edges_from(graph_data.edge_list)

    ctx.obj[GRAPH_NX] = G
    ctx.obj[MASTER] = master

    if nx.number_connected_components(G) != 1:
        sys.stderr.write(
            "Loaded topology is disconnected, please "
            "check your topology and load it again.\n"
        )
        sys.exit(1)

    with shelve.open(storage) as db:
        for key in ctx.obj:
            db[key] = ctx.obj[key]

    print("Topology has been loaded to shelve storage")

@graphcli.command()
@click.option(
    "-s",
    "--storage",
    default="./.graph_storage.db",
    help="Change a location for db file (to use different topology use load command)",
    show_default=True,
)
@click.option(
    "--branches",
    "-x",
    type=click.IntRange(3, 20),
    help="Topology branches <3-20> (x-axis) ignored when option '--nodes' is used",
)
@click.option(
    "--length",
    "-y",
    type=click.IntRange(3, 20),
    help="Topology length <3-20> (y-axis) ignored when option '--nodes' is used",
)
@click.option(
    "--nodes",
    "-n",
    type=click.IntRange(4, 60),
    help="Needed number of topology nodes <3-60> (server count)",
)
@click.option(
    "--master", "-m", default="y0", help="Master node name", show_default=True
)
@click.pass_context
def generate(ctx, storage, branches, length, nodes, master):
    """
    Generate one of two topology graphs based on given options

    --nodes and pair --branches --length trigger generation of the
    different type topologies (for more information see the documentation)
    """
    topo = {}
    if nodes:
        if branches or length:
            print(
                "Info: option --nodes has been passed, ignoring options "
                "--branches and --length"
            )
        G = circle_topology(nodes, master)
    else:
        if not branches:
            branches = 3
        if not length:
            length = 3
        topo = create_basic_topo(max_width=branches, max_levels=length)

        G = nx.Graph()
        G.add_edges_from(topo["edges"])

    if master not in G:
        sys.stderr.write(
            f"Generated topology does not contain {master} master node,"
            " please specify correct master node using option `--master`\n"
        )
        sys.exit(1)

    ctx.obj[GRAPH_NX] = G
    ctx.obj[GRAPH_DATA] = None
    ctx.obj[MASTER] = master

    with shelve.open(storage) as db:
        for key in ctx.obj:
            db[key] = ctx.obj[key]

    print("Topology has been generated to shelve storage")

def produce_backbone_image(G, backbone_edges, levels, circular=False, filename=None):
    G = deepcopy(G)

    plt.close()

    if not circular:
        pos = nx.spring_layout(G, k=0.3 * 1 / sqrt(len(G.nodes())), iterations=150)
    else:
        pos = nx.circular_layout(G)

    # https://stackoverflow.com/questions/50453043/networkx-drawing-label-partially-outside-the-box
    plt.figure(figsize=(11, 7))
    x_values, _y_values = zip(*pos.values())
    x_max = max(x_values)
    x_min = min(x_values)
    x_margin = (x_max - x_min) * 0.2
    plt.xlim(x_min - x_margin, x_max + x_margin)

    for edge in G.edges:
        G.remove_edge(*edge)
        if edge in backbone_edges:
            G.add_edge(*edge, color="magenta")
        else:
            G.add_edge(*edge, color="cyan")

    fixed_colors = [
        "#66FF00",
        "#1974D2",
        "#00CC99",
        "#E68FAC",
        "#DFFF00",
        "#00B7EB",
        "#D891EF",
        "#C32148",
        "#58427C",
        "#FFD300",
        "#F56FA1",
        "#666699",
        "#D4AF37",
        "#6F00FF",
        "#BF00FF",
        "#CCFF00",
        "#85754E",
        "#B2EC5D",
        "#ED2939",
        "#4C516D",
        "#FFD700",
    ]

    draw_edges = list(G.edges)

    for edge in G.edges:
        draw_edges.append((edge[1], edge[0]))

    DiG = nx.DiGraph(draw_edges)

    for level in levels:
        new_node_color = fixed_colors[level]
        nx.draw_networkx_nodes(
            G,
            pos,
            nodelist=levels[level],
            node_color=new_node_color,
            node_size=700,
            alpha=0.85,
        )

    colors = [G[u][v]["color"] for u, v in DiG.edges()]

    nx.draw_networkx_edges(
        DiG,
        pos,
        edge_color=colors,
        edgelist=DiG.edges,
        width=3.0,
        arrows=True,
        arrowstyle="-|>",
        arrowsize=20,
    )  # this HAX (DiGraph) is needed for arrows to be in drawings

    labels = {}

    for node in G:
        labels[node] = node

    nx.draw_networkx_labels(G, pos, labels)

    patches = []
    for level in levels:
        label = f"Level {level} replica" if level else "FreeIPA master"
        patches.append(
            mlines.Line2D(
                [0],
                [0],
                marker="o",
                alpha=0.8,
                markersize=15,
                color="w",
                markerfacecolor=fixed_colors[level],
                label=label,
                # https://matplotlib.org/gallery/text_labels_and_annotations/custom_legends.html#sphx-glr-gallery-text-labels-and-annotations-custom-legends-py
            )
        )

    patches.append(
        mlines.Line2D(
            [0],
            [0],
            linewidth=4.0,
            alpha=0.8,
            color="magenta",
            label="Backbone replication agreement",
        )
    )

    patches.append(
        mlines.Line2D(
            [0],
            [0],
            linewidth=4.0,
            alpha=0.8,
            color="cyan",
            label="Additional replication agreement",
        )
    )

    plt.legend(handles=patches)

    plt.axis("off")
    # plt.show()
    plt.savefig(os.path.abspath(filename), dpi=100)

def produce_output_image(G, filename=None, circular=False):
    plt.close()
    art_points = list(nx.articulation_points(G))

    draw_edges = list(G.edges)

    for edge in G.edges:
        draw_edges.append((edge[1], edge[0]))

    # this HAX (DiGraph) is needed for arrows to be in drawings
    DiG = nx.DiGraph(draw_edges)

    # https://stackoverflow.com/questions/49121491/issue-with-spacing-nodes-in-networkx-graph
    if not circular:
        pos = nx.spring_layout(G, k=0.3 * 1 / sqrt(len(G.nodes())), iterations=150)
    else:
        pos = nx.circular_layout(G)

    # https://stackoverflow.com/questions/50453043/networkx-drawing-label-partially-outside-the-box
    plt.figure(figsize=(11, 7))
    x_values, _y_values = zip(*pos.values())
    x_max = max(x_values)
    x_min = min(x_values)
    x_margin = (x_max - x_min) * 0.2
    plt.xlim(x_min - x_margin, x_max + x_margin)

    plot_nodes = {
        "Complying replicas": {"nodes": [], "color": "green"},
        "One replication agreement": {"nodes": [], "color": "pink"},
        "Articulation point": {"nodes": [], "color": "yellow"},
        "Overloaded replica": {"nodes": [], "color": "red"},
        "Overloaded articulation point": {"nodes": [], "color": "orange"},
    }

    for node in G:
        node_degree = nx.degree(G, node)
        if node in art_points and node_degree > 4:
            plot_nodes["Overloaded articulation point"]["nodes"].append(node)
        elif node in art_points:
            plot_nodes["Articulation point"]["nodes"].append(node)
        elif node_degree > 4:
            plot_nodes["Overloaded replica"]["nodes"].append(node)
        elif node_degree == 1 and not len(G) == 2:
            plot_nodes["One replication agreement"]["nodes"].append(node)
        else:
            plot_nodes["Complying replicas"]["nodes"].append(node)

    for node_type in plot_nodes:
        nx.draw_networkx_nodes(
            G,
            pos,
            nodelist=plot_nodes[node_type]["nodes"],
            node_color=plot_nodes[node_type]["color"],
            node_size=700,
            alpha=0.85,
        )

    # set colors for the edges to draw
    colors = []
    for u, v in DiG.edges():
        try:
            colors.append(G[u][v]["color"])
        except KeyError:
            colors.append("#ff7f0e")  # orange from FreeIPA

    nx.draw_networkx_edges(
        DiG,
        pos,
        edge_color=colors,
        edgelist=DiG.edges,
        width=3.0,
        arrows=True,
        arrowstyle="-|>",
        arrowsize=20,
    )

    labels = {}

    for node in G:
        labels[node] = node

    nx.draw_networkx_labels(G, pos, labels)

    patches = []
    for node_type in plot_nodes:
        patches.append(
            mlines.Line2D(
                [0],
                [0],
                marker="o",
                alpha=0.8,
                markersize=15,
                color="w",
                markerfacecolor=plot_nodes[node_type]["color"],
                label=node_type,
                # https://matplotlib.org/gallery/text_labels_and_annotations/custom_legends.html#sphx-glr-gallery-text-labels-and-annotations-custom-legends-py
            )
        )

    patches.append(
        mlines.Line2D(
            [0],
            [0],
            linewidth=4.0,
            alpha=0.8,
            color="#90ee90",
            label="Added replication agreement",
        )
    )

    patches.append(
        mlines.Line2D(
            [0],
            [0],
            linewidth=4.0,
            alpha=0.8,
            color="#0277bd",
            label="Forced replication agreement",
        )
    )

    patches.append(
        mlines.Line2D(
            [0],
            [0],
            linewidth=4.0,
            alpha=0.8,
            color="#ff7f0e",
            label="Original replication agreement",
        )
    )

    loc = 10 if circular else 0

    plt.legend(loc=loc, handles=patches)
    plt.axis("off")

    if filename:
        plt.savefig(os.path.abspath(filename), dpi=100)
    else:
        plt.show()

@graphcli.command()
@click.option(
    "-s",
    "--storage",
    default="./.graph_storage.db",
    help="Change a location for db file (to use different topology use load command)",
    show_default=True,
)
@click.option(
    "-i",
    "--interactive",
    is_flag=True,
    help="Open a mathplotlib window with the topology graph",
)
@click.option(
    "-c", "--circular", is_flag=True, help="Force the nodes figure to circle shape",
)
@click.option(
    "-f",
    "--filename",
    help="Change path of saving the file. [default: ./topology_drawing.png]",
)
@click.pass_context
def draw(ctx, storage, interactive, filename, circular):
    """Create a topology graph picture"""
    ctx = load_context(ctx, storage)

    try:
        G = ctx.obj[GRAPH_NX]
    except KeyError:
        sys.stderr.write("Please load or generate the topology first\n")
        sys.exit(1)

    if interactive:
        produce_output_image(G, circular=circular)

    if not interactive and not filename:
        produce_output_image(G, filename="topology_drawing.png", circular=circular)
        print("Image stored to file topology_drawing.png")

    if filename:
        produce_output_image(G, filename=filename, circular=circular)
        print(f"Image stored to file {filename}")

def get_segments(edges):
    segments = {}
    nodes = list(chain(*edges))
    most_common_nodes = Counter(nodes).most_common()
    node_iter = iter(most_common_nodes)
    processed = set()
    not_processed = set(edges)

    while not_processed:
        left = set()
        right = set()
        node, _ = next(node_iter)
        for a, b in not_processed:
            if node == a:
                left.add(b)
                processed.add((a, b))
            elif node == b:
                right.add(a)
                processed.add((a, b))

        not_processed -= processed

        if right.union(left):
            segments.update({node: right.union(left)})

    return segments

def print_topology(topology):
    # in general the topology second level (y1x*) should be there always
    topo_width = len(topology[1])
    for level in topology:
        if len(topology[level]) == 1:
            ws = "".join(["\t" for _ in range(int(topo_width / 2))])
            line = f"{ws}{topology[level][0]}"
        else:
            line = "\t".join(topology[level])

        print(line)

@graphcli.command()
@click.option(
    "-s",
    "--storage",
    default="./.graph_storage.db",
    help="Change a location for db file (to use different topology use load command)",
    show_default=True,
)
@click.option(
    "-j",
    "--jenkins-template",
    type=click.Path(exists=True),
    default=os.path.join(SCALING_DEFAULTS, "jenkinsjob.j2"),
    help="Jenkinsfile jinja2 template",
    show_default=True,
)
@click.option(
    "--base-metadata",
    type=click.Path(exists=True),
    default=os.path.join(SCALING_DEFAULTS, "metadata_template.j2"),
    help="Base metadata jinja2 template",
    show_default=True,
)
@click.option(
    "--inventory",
    type=click.Path(exists=True),
    default=os.path.join(SCALING_DEFAULTS, "inventory_template.j2"),
    help="Base inventory jinja2 template",
    show_default=True,
)
@click.option(
    "--ansible-install",
    type=click.Path(exists=True),
    default=os.path.join(SCALING_DEFAULTS, "install_primary_replicas.j2"),
    help="Ansible-freeipa install jinja2 template",
    show_default=True,
)
@click.option(
    "--out-dir",
    "-o",
    type=str,
    default="DEPLOYMENT_FILES",
    help="Output directory to store generated data",
)
@click.option(
    "--metadata-storage",
    type=str,
    default="idm-artifacts.usersys.redhat.com",
    help="Metadata storage server",
)
@click.option(
    "--idm-ci",
    type=str,
    default="https://gitlab.cee.redhat.com/identity-management/idm-ci.git",  # FIXME
    help="IDM-CI repo",
)
@click.option("--repo-branch", type=str, default="master", help="IDM-CI repo branch")
@click.option(
    "--tool-repo",
    type=str,
    default=(
        "https://gitlab.cee.redhat.com/identity-management/idm-performance-testing.git"
    ),  # FIXME
    help="SCALE tool repo",
)
@click.option(
    "--tool-branch", type=str, default="master", help="SCALE tool repo branch"
)
@click.option("--node-os", type=str, default="fedora-33", help="Host operating system")
@click.option(
    "--project",
    "-p",
    type=str,
    default="trigger_performance_scale/large-scale",
    help="Pipeline project for storing data",
)
@click.option(
    "--run", "-r", type=str, default="RUNNING", help="Pipeline run for storing data"
)
@click.option(
    "--job", "-j", type=str, default="JOB", help="Pipeline job for storing data"
)
@click.option("--freeipa-upstream-copr", type=str, help="freeipa-upstream-copr")
@click.option("--freeipa-downstream-copr", type=str, help="freeipa-downstream-copr")
@click.option("--freeipa-custom-repo", type=str, help="freeipa-custom-repo")
@click.option(
    "--ansible-freeipa-upstream-copr", type=str, help="ansible-freeipa-upstream-copr"
)
@click.option(
    "--ansible-freeipa-downstream-copr",
    type=str,
    help="ansible-freeipa-downstream-copr",
)
@click.option(
    "--ansible-freeipa-custom-repo", type=str, help="ansible-freeipa-custom-repo"
)
@click.option(
    "-c", "--circular", is_flag=True, help="Force the nodes figure to circle shape",
)
@click.pass_context
def deployment(
    ctx,
    jenkins_template,
    out_dir,
    storage,
    node_os,
    idm_ci,
    repo_branch,
    tool_repo,
    tool_branch,
    project,
    run,
    job,
    metadata_storage,
    base_metadata,
    inventory,
    ansible_install,
    freeipa_upstream_copr,
    freeipa_downstream_copr,
    freeipa_custom_repo,
    ansible_freeipa_upstream_copr,
    ansible_freeipa_downstream_copr,
    ansible_freeipa_custom_repo,
    circular,
):
    """
    Generate deployment files for the Jenkins automation
    """
    ctx = load_context(ctx, storage)

    try:
        G = ctx.obj[GRAPH_NX]
        master = ctx.obj[MASTER]
    except KeyError:
        sys.stderr.write("Please load or generate the topology first\n")
        sys.exit(1)

    all_successors = list(nx.bfs_successors(G, master))
    levels = levels_from_fist_successors(all_successors[:1])
    successors = all_successors[1:]

    new = []
    predecessors = predecessors_from_first_levels(levels)

    for s, f in successors:
        # print("------------------------------------")
        # print("s:", s, "f:", f, "level:", levels[-1])
        if s in levels[-1]:
            # print(s, f)
            new = new + f
        else:
            # print(s, f)
            levels.append(new)
            new = f
        for pred in f:
            predecessors[pred] = s

    if new:  # add level at the end of for cycle so last is added
        levels.append(new)

    key = 0
    levels_dict = {}
    for level in levels:
        levels_dict[key] = level
        key += 1

    backbone_edges = compatible_backbone_edges(G, master)
    levels = levels_dict

    print(f"Create temporary {out_dir} folder")
    os.makedirs(out_dir, exist_ok=True)

    print("Generate Jenkinsfile for whole topology")
    output_jenkins_job = os.path.join(out_dir, "Jenkinsfile")

    pipeline_template = load_jinja_template(jenkins_template)

    jenkinsfile = pipeline_template.render(
        levels=levels,
        node_os=node_os,
        idm_ci=idm_ci,
        repo_branch=repo_branch,
        metadata_storage=metadata_storage,
        project=project,
        run=run,
        job=job,
        freeipa_upstream_copr=freeipa_upstream_copr,
        freeipa_downstream_copr=freeipa_downstream_copr,
        freeipa_custom_repo=freeipa_custom_repo,
        ansible_freeipa_upstream_copr=ansible_freeipa_upstream_copr,
        ansible_freeipa_downstream_copr=ansible_freeipa_downstream_copr,
        ansible_freeipa_custom_repo=ansible_freeipa_custom_repo,
    )
    save_data(output_jenkins_job, jenkinsfile)

    print_topology(levels)  # to the terminal
    produce_backbone_image(
        G,
        backbone_edges,
        levels,
        circular=circular,
        filename=os.path.join(out_dir, "backbone_topology.png"),
    )
    # merge dictionary items to one list containing all nodes
    topo_nodes = list(chain(*[levels[level] for level in levels]))

    print("Generate metadata for topology nodes")
    # Load jinja template
    metadata_template = load_jinja_template(base_metadata)

    # Generate the metadata file for all nodes inside of FILES directory
    gen_metadata(
        topo_nodes,
        node_os,
        out_dir,
        project,
        run,
        job,
        metadata_template,
        tool_repo,
        tool_branch,
    )

    print("Generate ansible-freeipa inventory file")
    # Load jinja template
    job_inventory = load_jinja_template(inventory)

    # Generate ansible-freeipa inventory file
    output_job_inventory = os.path.join(out_dir, "perf-inventory")
    inventoryfile = job_inventory.render(
        master_server=topo_nodes[0], levels=levels, predecessors=predecessors
    )
    save_data(output_job_inventory, inventoryfile)

    print("Generate ansible-freeipa install file")
    missing_edges = set(G.edges) - set(backbone_edges)

    segments = get_segments(missing_edges)
    # Load jinja template
    ansible_install = load_jinja_template(ansible_install)
    # Generate ansible-freeipa install file
    output_install = os.path.join(out_dir, "perf-install.yml")
    installfile = ansible_install.render(levels=levels, missing=segments)
    save_data(output_install, installfile)

    produce_output_image(G, os.path.join(out_dir, "topology.png"))

def compatible_backbone_edges(G, master):
    """Return edges not based on direction of bfs walk"""
    all_edges = set(G.edges)
    b_edges = set(nx.bfs_edges(G, master))

    backbone_edges = set()
    for e in b_edges:
        if e in all_edges:
            backbone_edges.add(e)
        else:
            backbone_edges.add((e[1], e[0]))

    return backbone_edges

def add_list_item(record, item):
    if record is None:
        record = []

    record.append(item)
    return record

def sort_by_degree(G, nodes=None, reverse=False):
    sorted_nodes = OrderedDict()
    nodes_by_degree = {}

    if not nodes:
        nodes = G

    for node in nodes:
        cnt = nx.degree(G, node)
        try:
            record = nodes_by_degree[cnt]
        except KeyError:
            record = []

        nodes_by_degree[cnt] = add_list_item(record, node)

    if reverse:
        for degree in range(max(nodes_by_degree, key=int), 0, -1):
            try:
                sorted_nodes[degree] = sorted(nodes_by_degree[degree], reverse=reverse)
            except KeyError:
                pass
    else:
        for degree in range(max(nodes_by_degree, key=int) + 1):
            try:
                sorted_nodes[degree] = sorted(nodes_by_degree[degree], reverse=reverse)
            except KeyError:
                pass

    return sorted_nodes

@graphcli.command()
@click.option(
    "-s",
    "--storage",
    default="./.graph_storage.db",
    help="Change a location for db file (to use different topology use load command)",
    show_default=True,
)
@click.pass_context
def analyze(ctx, storage):
    """
    Print information about processed topology graph
    """
    ctx = load_context(ctx, storage)

    try:
        G = ctx.obj[GRAPH_NX]
        # master = ctx.obj[MASTER]
    except KeyError:
        sys.stderr.write("Please load or generate the topology first")
        sys.exit(1)

    print("----------------------------------------")
    print("General graph information:\n" + str(nx.info(G)))
    eccentricity = nx.eccentricity(G)
    print(
        "Maximum hop count distance from two random replicas:",
        max(eccentricity.items(), key=operator.itemgetter(1))[1],
    )
    print()
    print("Number of connected components:\t" + str(nx.number_connected_components(G)))
    print("----------------------------------------")
    print("Analyzing replicas as standalone entity")
    print("--------------------")

    issues = []
    for node in G:
        repl_agreements = nx.degree(G, node)

        if repl_agreements < 2:
            issues.append(f"One replication agreement\t| {node}")

        if repl_agreements > MAX_REPL_AGREEMENTS:
            issues.append(
                f"More than {MAX_REPL_AGREEMENTS} " f"replication agreements\t| {node}"
            )

    if issues and not len(G) == 2:
        print("ISSUE\t\t\t\t| REPLICA\n-----\t\t\t\t| ----")
        for issue in issues:
            print(issue)

    else:
        print("No issues found for the replicas")

    print("----------------------------------------")
    print("Analyzing topology connectivity")
    print("--------------------")
    print("Looking for articulation points")
    print(
        "Note: Optimal FreeIPA replication topology should "
        "not contain any articulation point"
    )

    art_points = sorted(list(nx.articulation_points(G)), reverse=True)
    components = list(nx.biconnected_components(G))

    if art_points or components:
        for art_point in art_points:
            print(f"Articulation point\t| {art_point}")

        print(f"Articulation point(s) found: {len(art_points)}")
        print("--------------------")
        print("Looking for biconnected components")
        print(
            "Note: Optimal FreeIPA replication topology should "
            "contain one biconnected component"
        )

        for comp in components:
            print(f"Biconected component: {comp}")

        print(f"Biconected component(s) found: {len(components)}")

    print("----------------------------------------")

def remove_articulation_points(G, step, omit_max, max_repl_agreements):

    # trying to remove articulation points
    added_edges = []
    can_not_add = []
    to_add = ""

    color = "#90ee90"  # default light green added edge color

    one_repl_agreement_replicas = []
    # we build list of candidates from 1 repl agreement nodes and connect them
    for node in G:
        if nx.degree(G, node) == 1:
            one_repl_agreement_replicas.append(node)

    # while there is more than 2 replicas to connect we connect them
    while len(one_repl_agreement_replicas) >= 2:
        left = one_repl_agreement_replicas.pop()
        right = one_repl_agreement_replicas.pop()
        edge_to_add = (left, right)
        step += 1
        G.add_edges_from([edge_to_add], color=color)
        produce_output_image(G, f"fixup_step_{step}_add_{left}_{right}.png")
        # produce_output_image(G)
        added_edges.append(edge_to_add)
        print("Adding replication agreement:", edge_to_add)

    # a case that only one replica has one repl agreement may occur after first fixup
    # however we will fix that with following algorithm

    all_components = list(nx.biconnected_components(G))

    while list(nx.articulation_points(G)):
        art_points = sorted(list(nx.articulation_points(G)), reverse=True)

        if len(list(nx.articulation_points(G))) == 0:
            # if there are no articulation points we stop
            sys.stdout.write("Info: Will not add more replication agreements\n")
            break

        # pick articulation point to solve issue for FIXME
        art_point = art_points.pop()
        color = "#90ee90"  # default light green added edge color

        connected_by_art_point = []
        remaining_components = []
        for comp in all_components:
            if art_point in comp:
                connected_by_art_point.append(comp)
            else:
                remaining_components.append(comp)

        candidates = []
        for comp in connected_by_art_point:
            sorted_nodes = sort_by_degree(G, nodes=comp, reverse=False)

            # get dictionary with keys corresponding to degrees and each key
            # will have a list of node with following degree as value
            sorted_nodes = sort_by_degree(G, nodes=comp, reverse=False)

            # we would choose a node with the fewest connection count
            # therefore the lowerest degree of the node

            to_pick_from = []
            # list with nodes, first in the list are with the lower degree
            for _degree, nodes in sorted_nodes.items():
                to_pick_from += nodes

            start = 0
            to_add = None
            while not to_add and (start < len(to_pick_from)):
                to_add = to_pick_from[start]
                start += 1

            # if the node is articulation point so it connets other biconnected
            # components we choose to connect lowest of the remaining nodes

            if to_add in art_points:
                remaining_nodes = list(tuple(chain(*(remaining_components))))
                # print(remaining_nodes)
                sorted_remaining = sort_by_degree(
                    G, nodes=remaining_nodes, reverse=False
                )
                new_pick = []
                for _degree, nodes in sorted_remaining.items():
                    new_pick += nodes

                for pick in new_pick:
                    if pick not in art_points:
                        to_add = pick

            min_degree = nx.degree(G, to_add)

            if min_degree < max_repl_agreements:
                candidates.append(to_add)
                color = "#90ee90" if len(candidates) > 2 else color
            else:
                added = False
                if omit_max:
                    candidates.append(to_add)
                    color = "#0277bd"  # blue edge color
                    added = True
                else:
                    can_not_add.append(to_add)

                added_str = "Added" if added else "Did not add"
                sys.stdout.write(
                    f"Warning: {added_str} replication agreement"
                    " to connect component "
                    f"({comp}) with articulation point {art_point} "
                    f"{to_add} has {max_repl_agreements} "
                    "or more replication agreements\n"
                )

                if not added and to_add in can_not_add:
                    sys.stdout.write(
                        "Hint: Try adding --omit-max option to add "
                        "replication agreement even when maximum "
                        " degree of node is reached\n"
                    )

            if to_add in can_not_add:
                neighs = G.neighbors(to_add)
                sys.stderr.write(
                    f"Unable to add more replication agreements to: {to_add}\n"
                )
                for nei in neighs:
                    sys.stderr.write(f"{to_add} has neighbor: {nei}\n")
                sys.exit(3)

        if len(candidates) >= 2:
            left = candidates[-2]
            right = candidates[-1]
            edge_to_add = (left, right)
            step += 1
            G.add_edges_from([edge_to_add], color=color)
            produce_output_image(G, f"fixup_step_{step}_add_{left}_{right}.png")
            # produce_output_image(G)
            added_edges.append(edge_to_add)
            print("Adding replication agreement:", edge_to_add)

        all_components = list(nx.biconnected_components(G))

    return G, added_edges, step

def remove_overloaded_nodes_edges(
    G, step, add_while_removing, omit_max, max_repl_agreements
):
    removed = []
    can_not_remove = []
    remove = set()
    added_edges = []  # used when add_while_removing is set to true

    sorted_nodes = sort_by_degree(G)
    max_degree = max(sorted_nodes, key=int)

    while max_degree > max_repl_agreements:
        # sort the G nodes by degree once!
        sorted_max_degree_nodes = iter(sorted_nodes[max_degree])

        # for max_degree_node in sorted_max_degree_nodes:
        max_degree_node = next(sorted_max_degree_nodes)

        if max_degree <= max_repl_agreements:
            sys.stdout.write("Info: Will not remove more replication agreements\n")
            break

        neighbors = sorted([n for n in G.neighbors(max_degree_node)])

        sort_neig_dict = sort_by_degree(G, nodes=neighbors)
        sort_neig = sorted(sort_neig_dict, reverse=True)

        remove = removed[-1] if removed else remove

        for neig in sort_neig_dict[sort_neig[0]]:
            to_remove = (max_degree_node, neig)

            if to_remove not in can_not_remove:
                remove = to_remove
                break

        if remove in can_not_remove:
            sys.stderr.write(
                "Error: Can not remove any replication agreement without "
                "creating articulation points\n"
            )
            sys.stderr.write(
                "Try to use option '--add-while-removing' to remove "
                "articulation points created by this removal\n"
            )
            sys.exit(4)

        if remove in removed:
            sys.stderr.write(
                f"Error: Replication agreement already removed - {remove}\n"
            )
            sys.exit(4)

        if remove:
            step += 1
            G.remove_edge(*remove)

        produce_output_image(
            G, filename=f"fixup_step_{step}_rm_{remove[0]}_{remove[1]}.png"
        )

        sorted_nodes = sort_by_degree(G)
        max_degree = max(sorted_nodes, key=int)

        removed.append(remove)
        print("Removing replication agreement:", remove)

        if len(list(nx.articulation_points(G))) == 0:
            # if there are no articulation points we continue removing
            continue
        else:
            sys.stdout.write(
                f"Warning: Removal of the {removed[-1]} replication "
                "agreement created articulation point\n"
            )
            if add_while_removing:
                sys.stdout.write("Info: Trying to fix new articulation points\n")
                G, added, step = remove_articulation_points(
                    G,
                    step=step,
                    omit_max=omit_max,
                    max_repl_agreements=max_repl_agreements,
                )
                added_edges += added

                for new in added:
                    if (new[0], new[1]) in removed or (new[1], new[0]) in removed:
                        sys.stderr.write(
                            f"Error Added replication agreement {new} has been"
                            f" found in already removed list: {removed}\n"
                        )
                        sys.exit(4)

            else:
                sys.stdout.write(
                    f"Info: Adding back replication agreement: {removed[-1]} \n"
                )
                G.add_edge(removed[-1][0], removed[-1][1])
                can_not_remove.append(removed[-1])
                sys.stdout.write(
                    "List of replication agreements "
                    f"which we can not remove: {can_not_remove}\n"
                )

    return G, removed, added_edges, step

@graphcli.command()
@click.option(
    "-m",
    "--max",
    "max_repl_agreements",
    default=MAX_REPL_AGREEMENTS,
    type=click.IntRange(2, 10),
    help="Change maximum number <2-10> of replication agreements per replica",
)
@click.option(
    "--omit-max",
    is_flag=True,
    default=False,
    help="Do not check the maximum number of the replication agreements"
    " while adding a new replication agreement to connect a topology",
)
@click.option(
    "--add-while-removing",
    is_flag=True,
    default=False,
    help="When removal of the replication agreement creates a articulation "
    "point add edge to fix the issue",
)
@click.option(
    "-s",
    "--storage",
    default="./.graph_storage.db",
    help="Change a location for db file (to use different topology use load command)",
    show_default=True,
)
@click.option(
    "--no-fqdn",
    is_flag=True,
    help="Change a templates from fqdn to non-fqdn",
    default=False,
)
@click.pass_context
def fixup(ctx, storage, max_repl_agreements, omit_max, add_while_removing, no_fqdn):
    """
    Generate an Ansible automation to remove topology weak spots
    """
    ctx = load_context(ctx, storage)

    try:
        G = ctx.obj[GRAPH_NX]
        master = ctx.obj[MASTER]
    except KeyError:
        sys.stderr.write("Please load or generate the topology first\n")
        sys.exit(1)

    print("========================================")

    step = 0
    produce_output_image(G, f"fixup_step_{step}_start.png")

    G, added_edges, step = remove_articulation_points(
        G, step=step, omit_max=omit_max, max_repl_agreements=max_repl_agreements,
    )

    G, removed, added_with_removal, step = remove_overloaded_nodes_edges(
        G,
        step=step,
        add_while_removing=add_while_removing,
        omit_max=omit_max,
        max_repl_agreements=max_repl_agreements,
    )

    added_edges += added_with_removal
    missing_segments = get_segments(added_edges)
    redundant_segments = get_segments(removed)

    print("Summary:")

    if missing_segments or redundant_segments:
        print(f"Added replication agreement(s) [{len(added_edges)}]:")
        for edge in added_edges:
            print(edge)

        print(f"Removed replication agreement(s) [{len(removed)}]:")
        for edge in removed:
            print(edge)

        print(
            "Replication agreement intersection:",
            set(added_edges).intersection(set(removed)),
        )
        print("----------------------------------------")
        print("Saving result graph image")
        produce_output_image(G, "fixup_result.png")
        print("----------------------------------------")
        print("Saving result graph data")
        # save new graph to context db
        ctx.obj[GRAPH_NX] = G
        with shelve.open(storage) as db:
            for key in ctx.obj:
                db[key] = ctx.obj[key]

        print("----------------------------------------")
        print("Generating fixup playbook")
        fqdn = "_fqdn" if not no_fqdn else ""
        fixup_playbook = load_jinja_template(
            os.path.join(SCALING_DEFAULTS, f"fixup_topology_segments{fqdn}.j2")
        )
        fixup = "fixup_result.yml"
        fixup_data = fixup_playbook.render(
            missing=missing_segments, redundant=redundant_segments,
        )
        save_data(fixup, fixup_data)

        print("Generating fixup inventory")
        fixup_inv = load_jinja_template(
            os.path.join(SCALING_DEFAULTS, f"fixup_inventory{fqdn}.j2")
        )
        inv = "fixup_result.inv"

        replicas = set(tuple(chain(*(removed + added_edges))))

        try:
            replicas.remove(master)
        except KeyError:
            pass  # when master node is not in list of replicas it is okay

        inv_data = fixup_inv.render(ipa_master=master, ipa_replicas=replicas,)

        save_data(inv, inv_data)
    else:
        print("Nothing to do")

    print("========================================")

if __name__ == "__main__":
    sys.exit(graphcli())


import logging
from logging import FileHandler, StreamHandler
from datetime import datetime
from pathlib import Path

from colorlog import ColoredFormatter

appLogger = logging.getLogger("app")
appLogger.setLevel(logging.INFO)

# 设置控制台输出
consoleHandler = StreamHandler()
log_colors = {
    "DEBUG": "white",
    "INFO": "green",
    "WARNING": "yellow",
    "ERROR": "red",
    "CRITICAL": "purple",
}
consoleFormatter = ColoredFormatter(
    "%(log_color)s%(asctime)s %(filename)s:%(lineno)d [%(levelname)s] %(message)s",
    log_colors=log_colors,
)
consoleHandler.setFormatter(consoleFormatter)
appLogger.addHandler(consoleHandler)

# 设置文件输出
logDir = Path("logs")
if not logDir.is_dir():
    logDir.mkdir()
logPath = Path(f"logs/app_{datetime.now().date()}.log")
logPath.touch()
fileHandler = FileHandler(logPath, encoding="utf-8")
fileFormatter = logging.Formatter(
    "%(asctime)s %(filename)s:%(lineno)d [%(levelname)s] %(message)s"
)
fileHandler.setFormatter(fileFormatter)
appLogger.addHandler(fileHandler)


# -*-coding:utf-8-*-
# @Author: Damon0626
# @Time  : 19-1-16 下午10:03
# @Email : wwymsn@163.com
# @Software: PyCharm

import cv2
import numpy as np
from collections import Counter

def detec_weiqi_color(img):
	txt = 'black'
	gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
	ret, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)  # 高于100的都设置为255

	c = Counter(list(threshold.flatten()))  # 对边缘像素数量进行计数
	print(c.most_common())  # 黑色:num, 白色:num

	if c.most_common()[0][0] != 0:  # 不存在0,即不存在黑色
		txt = 'white'
	return txt, threshold

img = cv2.imread('weiqi.png')
img = cv2.medianBlur(img, 5)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray)

circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param1=100, param2=30, minRadius=10, maxRadius=50)

if circles is None:
	exit(-1)

circles = np.uint16(np.around(circles))

cv2.waitKey(0)
font = cv2.FONT_HERSHEY_SIMPLEX

for i in circles[0, :]:
	cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2)
	cv2.circle(img, (i[0], i[1]), 2, (0, 0, 255), 3)

	x, y, r = i
	crop_img = img[y-r:y+r, x-r:x+r]  # 棋子方图

	# 检测颜色
	txt, threshold = detec_weiqi_color(crop_img)
	print("颜色：", '黑色' if txt == 'black' else '白色')

	cv2.putText(threshold, txt, (0, 0), font, 0.5, (0, 255, 0), 2)
	cv2.imshow('threshold', threshold)

	cv2.imshow('crop_img', crop_img)
	cv2.moveWindow('crop_img', x=0, y=img.shape[0])

	cv2.imshow('detectd chess', img)
	cv2.moveWindow('detected chess', y=0, x=img.shape[1])

	cv2.waitKey(1500)

cv2.waitKey(0)
cv2.destroyAllWindows()


import time
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.common.touch_action import TouchAction
from selenium.webdriver.common.actions.action_builder import ActionBuilder

from iOS.IBS.functions_iRD import *

dc = {
    "platformName": "iOS",
    "xcodeOrgId": "5K7J2838R5",
    "xcodeSigningId": "iPhone Developer",
    "deviceName": "Cara's iPhone",
    "automationName": "XCUITest",
    "bundleId": "com.gohidoc.caraeu",
    "udid": "cebfcd519bbf550c384f2fa30c201c86926ab935",
    "platformVersion": "15.3",
    "noReset": "false",
    "newCommandTimeout": 3000
}
# count = 0
driver = webdriver.Remote("http://localhost:4723", dc)
time.sleep(5)

dimension = driver.get_window_size()
print(dimension['width'])
print(dimension['height'])
x = int(dimension['width'] * 0.08533333333)
y = int(dimension['height'] * 0.36206896551)

print(x, y)
action = ActionBuilder(driver)
action.pointer_action.move_to_location(x, y)
action.pointer_action.click()
action.perform()

# el = driver.find_elements(AppiumBy.CLASS_NAME, "XCUIElementTypeOther")
# print(len(el))
# for check in range(10):
#     print(check)
# swipe_up(driver)
# swipe_down(driver)
# click_element(driver, index=0, path="studyConfirmationScreen_informedCheckBox")
# for p in range(len(el)):
#     time.sleep(1)
#     print(el[p])
#     actions = TouchAction(driver)
#     actions.tap(el[p])
#     actions.perform()
#     count = count + 1
#     print(count)
#


import click as _click
import cv2
import base64
from io import BytesIO
import json

f = open('config.json')
data = json.load(f)
frame_width = int(data['width'])
frame_height = int(data['height'])
f.close()

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, frame_width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_height)
cap.set(cv2.CAP_PROP_FPS, 15)

def get_frames(image_name):

    # using click package to make sure that path is interpreted correctly
    _click.command()
    _click.argument('image_name',type=_click.Path(exists=True,
                                dir_okay=True, readable=True))

    retval, image = cap.read()

    retval, buffer = cv2.imencode('.jpg', image)
    jpg_as_text = base64.b64encode(buffer)

    return(jpg_as_text)


import json, branca

geo_data = None
with open('./blocks.geojson') as geojson_file:
    geo_data = json.load(geojson_file)

data = {datum['id']: datum['properties'] for datum in geo_data['features']}

low_color_scale = branca.colormap.linear.Greens_05.scale(0, 150)
low_color_scale.caption = 'Land Value Per Square Foot'
mid_color_scale = branca.colormap.linear.Blues_05.scale(150, 1000)
mid_color_scale.caption = 'Land Value Per Square Foot'
high_color_scale = branca.colormap.linear.Reds_05.scale(1000, 10000)
high_color_scale.caption = 'Land Value Per Square Foot'

def style_function(feature):
    datum = data[feature['id']]
    value = datum['avg_per_sqft_extrapolated_land_value']
    min_val, max_val = 150, 1000
    fillColor = '#black'
    if value < min_val:
        fillColor = low_color_scale(value)
    elif value > max_val:
        fillColor = high_color_scale(value)
    else:
        fillColor = mid_color_scale(value)
    return {
        'fillOpacity': 0.7,
        'weight': 2,
        'color': fillColor,
        'fillColor': fillColor,
    }

sf_coords = (37.76, -122.43)
m =  folium.Map(sf_coords, zoom_start=12, tiles='cartodbpositron')

folium.GeoJson(
    geo_data,
    style_function=style_function,
    name='Extrapolated Land Value',
).add_to(m)

folium.LayerControl().add_to(m)
m.add_child(low_color_scale)
m.add_child(mid_color_scale)
m.add_child(high_color_scale)
m.save('sf_blocks.html')


import functools
import collections
import re

pat = re.compile('([0-9]*) ([a-z]* [a-z]*) bags?[.,]?')

Bag = collections.namedtuple('Bag', ['name', 'contains'])

contains_golden = []
bags = []

def alter_search(color: str, data):
    for line in data:
        print(line) if color in line.partition(' bags contain ')[2] else None

def search_for(color: str, data):
    dime = []
    for line in data:
        matches = pat.findall(line)
        if len(matches) > 0:
            for match in matches:
                if match:
                    #print(match)
                    if color in match[1]:
                        #print(f"{line.partition(' bags contain ')[0]} : {match[0]}: {match[1]}")
                        dime.append((line.partition(' bags contain ')[0], match[0]))

    if len(dime) == 0:
        return 1
    print(f"{color}: {dime}")
    #ret_val = [search_for(x[0], data) for x in dime]

    #print(f"RETVAL{ret_val}")
    retval = 0
    for x in dime:
        retval = retval + search_for(x[0], data) * int(x[1])
    print(f"RET: {retval}")
    return retval

with open('06_input.txt', 'r') as f:
    lines = f.read().strip('.').split('.\n')
    search_for('shiny gold', lines)
    # alter_search('shiny gold', lines)
    # print('------------------------')
    # alter_search('vibrant green', lines)
    # print('------------------------')
    # alter_search('plaid teal', lines)
    # print('------------------------')
    # alter_search('posh aqua', lines)

    # #print(lines)
    # for line in lines:
    #     #print(line if 'shiny gold' in line else None)
    #     #print(line)
    #     bag_color = line.partition(' bags contain ')[0]
    #     contains = line.partition(' bags contain')[2].split(', ')
    #     contains = [tuple(x.strip().split(' ', 1)) for x in contains]
    #     #print(bag_color + " " + str(contains))
    #     bags.append((bag_color, contains))
    # print(bags)
    # #for bag in bags:
    # #    print(bag if 'shiny gold' in bag)


#!/usr/bin/env python3

from nucleus.constants import NETS, DATASETS
import argparse
import sys
import numpy as np
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

def get_args():
    parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
    parser.add_argument("--net")
    parser.add_argument("--dataset")
    parser.add_argument("--test_root")
    parser.add_argument("--train_root")
    parser.add_argument("--model_dir")
    parser.add_argument("--epoch", type=int)
    parser.add_argument("--learning_rate", type=float)
    parser.add_argument("--solution_dir", default="")
    parser.add_argument("--batch_size", type=int, default=2)
    parser.add_argument("--input_height", type=int)
    parser.add_argument("--input_width", type=int)
    return parser.parse_args()

def print_train_info(net, dataset, test_root, train_root, model_dir, solution_dir, batch_size,
                     input_height, input_width, learning_rate,
                     train_data_count, test_data_count):
    msg_info = "+------------+\n"
    msg_info += "| Train Info |\n"
    msg_info += "+------------+\n"
    msg_info += "-- net: {}\n".format(net)
    msg_info += "-- dataset: {}\n".format(dataset)
    msg_info += "-- model directory: {}\n".format(model_dir)
    msg_info += "-- solution directory: {}\n".format(solution_dir)
    msg_info += "-- batch size: {}\n".format(batch_size)
    msg_info += "-- learning rate: {}\n".format(learning_rate)
    msg_info += "+-----------+\n"
    msg_info += "| Data Info |\n"
    msg_info += "+-----------+\n"
    msg_info += "-- test root: {}\n".format(test_root)
    msg_info += "-- train root: {}\n".format(train_root)
    msg_info += "-- input height: {}\n".format(input_height)
    msg_info += "-- input width: {}\n".format(input_width)
    msg_info += "-- train data count: {}\n".format(train_data_count)
    msg_info += "-- test data count: {}\n\n".format(test_data_count)
    print(msg_info)
    with open("{}.info".format(model_dir.rstrip("/")), mode="a") as f_info:
        f_info.write(msg_info)

def train(net_name, dataset, test_root, train_root, model_dir, epoch,
          solution_dir, batch_size, input_height, input_width, learning_rate):

    data = DATASETS[dataset]((input_height, input_width), test_root, train_root)
    input_height, input_width = data.img_size

    if os.path.exists(model_dir):
        net = NETS[net_name](input_height, input_width, learning_rate, init_vars=False)
        net.restore_model(model_dir, epoch)
    else:
        net = NETS[net_name](input_height, input_width, learning_rate)

    print_train_info(net_name, dataset, test_root, train_root, model_dir, solution_dir, batch_size,
                     input_height, input_width, learning_rate,
                     data.train_data_count, data.test_data_count)

    epoch_count = 0
    epoch_step = 0
    all_steps = 0
    while True:
        batch_xs, batch_ys = data.get_next_train_batch(batch_size,
                                                       solution_dir=solution_dir)

        net.session.run(net.train_step, feed_dict={net.x: batch_xs, net.y_: batch_ys})

        epoch_step += 1
        if epoch_step == (data.train_data_count // batch_size) or all_steps == 0:

            net.save_model(model_dir, epoch_count)

            train_cost = net.cost.eval(feed_dict={net.x: batch_xs, net.y_: batch_ys})
            train_accuracy = net.accuracy.eval(feed_dict={net.x: batch_xs, net.y_: batch_ys})

            test_cost = 0
            test_accuracy = 0
            for test_img in data.test_data:
                test_x, test_y = data.get_test_imgs(test_img, solution_dir)
                test_x = np.array([test_x])
                test_y = np.array([test_y])

                test_accuracy += net.accuracy.eval(feed_dict={net.x: test_x, net.y_: test_y})
                test_cost += net.cost.eval(feed_dict={net.x: test_x, net.y_: test_y})

            test_cost = test_cost / data.test_data_count
            test_accuracy = test_accuracy / data.test_data_count

            all_steps += epoch_step
            epoch_step = 0
            epoch_count += 1

            print("----------------------------------")
            print("Epoch {}\nStep: {}\n-- "
                  "Cost\n----> Train: {}\n----> Test: {}".format(epoch_count, all_steps, train_cost, test_cost))
            print("-- Accuracy\n----> Train {}\n----> Test: {}".format(train_accuracy, test_accuracy))
            print("----------------------------------")
            with open("{}.log".format(model_dir.rstrip("/")), mode="a") as log:
                log.write("{},{},{},{}\n".format(test_cost, train_cost, test_accuracy, train_accuracy))
        else:
            msg = "Current step: {}".format(epoch_step)
            sys.stdout.write(msg + len(msg) * "\r")

if __name__ == "__main__":
    args = get_args()
    train(args.net,
          args.dataset,
          args.test_root,
          args.train_root,
          args.model_dir,
          args.epoch,
          args.solution_dir,
          args.batch_size,
          args.input_height,
          args.input_width,
          args.learning_rate)


from copy import copy
from typing import List

class Triangulo:
    def __init__(self, lado1: float, lado2: float, lado3: float):
        self.lado3: float = lado3
        self.lado2: float = lado2
        self.lado1: float = lado1

    def is_triangulo(self):
        lados: List[float] = [self.lado1, self.lado2, self.lado3]

        for pos, lado in enumerate(lados):
            lados_copy = copy(lados)
            lados_copy.pop(pos)
            if not (abs(lados_copy[0] - lados_copy[1]) < lado < sum(lados_copy)):
                return False
        return True

    def print_lados(self):
        for pos, lado in enumerate([self.lado1, self.lado2, self.lado3]):
            print(f'lado{pos}: {lado}')

class TrianguloRetangulo(Triangulo):

    def __init__(self, cateto1: float = None, cateto2: float = None, hipotenusa: float = None):
        cateto1, cateto2, hipotenusa = self.validacao_inicial_dos_lados_informados(cateto1, cateto2, hipotenusa)
        super().__init__(cateto1, cateto2, hipotenusa)

    def validacao_inicial_dos_lados_informados(self, cateto1, cateto2, hipotenusa):
        if self.has_mais_de_dois_nulos([cateto1, cateto2, hipotenusa]):
            raise ValueError('É necessário ter pelo menos 2 valores não nulos')
        elif not hipotenusa:
            hipotenusa = self.calcular_hipotenusa(cateto1, cateto2)
        elif not cateto1:
            cateto1 = self.calcular_cateto(cateto2, hipotenusa)
        elif not cateto2:
            cateto1 = self.calcular_cateto(cateto1, hipotenusa)
        else:
            if not (hipotenusa == self.calcular_hipotenusa(cateto1, cateto2)):
                raise ValueError('Valor de hipotenuas inválido')
        return cateto1, cateto2, hipotenusa

    def has_mais_de_dois_nulos(self, lados: list) -> bool:
        contador_nulos = 0
        for lado in lados:
            if lado == None:
                contador_nulos += 1

            if contador_nulos >= 2:
                return True

        return False

    def calcular_hipotenusa(self, cateto1: float, cateto2: float) -> float:
        return round((cateto1 ** 2 + cateto2 ** 2) ** 0.5, 2)

    def calcular_cateto(self, cateto: float, hipotenusa: float) -> float:
        if cateto >= hipotenusa:
            raise ValueError('Cateto maior ou igual a Hipotenusa')

        return round((hipotenusa ** 2 - cateto ** 2) ** 0.5, 2)

    def is_triangulo_retangulo(self) -> bool:
        if not self.is_triangulo():
            return False

        lados: set = {self.lado1, self.lado2, self.lado3}
        cateto1 = lados.pop()
        cateto2 = lados.pop()
        hipotenusa = lados.pop()

        return hipotenusa == self.calcular_hipotenusa(cateto1, cateto2)

    def get_hipotenusa(self) -> float:
        lados: set = {self.lado1, self.lado2, self.lado3}
        return max(lados)

    def get_menor_cateto(self) -> float:
        lados: set = {self.lado1, self.lado2, self.lado3}
        return min(lados)

    def get_maior_cateto(self) -> float:
        lados = {self.lado1, self.lado2, self.lado3}
        lados.pop()
        return min(lados)

    def get_catetos(self) -> List[float]:
        return list({self.get_menor_cateto(), self.get_maior_cateto()})

    def print_lados(self):
        print(f'cateto_menor: {self.get_menor_cateto()}')
        print(f'cateto_maior: {self.get_maior_cateto()}')
        print(f'hipotenusa: {self.get_hipotenusa()}')


from datetime import datetime
import matplotlib.pyplot as plt

'''

    TIPO DE TEXTO:

    Weekdays 1:
    ('2017-12-01T06', 1) Users: 1
    ('2017-12-01T07', 1) Users: 21

'''

data_list_weekdays = []
data_list_weekends = []

histogramas = []
with open("sample_spark.txt", 'r') as file:
    histograma = []
    for line in file:
        line = line.strip() # suprimir \n
        if line.startswith('Week'):
            if histograma != []:
                histogramas.append(histograma)
            histograma = [line,[]]
        #if not (line.startswith('Week')): # pasar los titulos
        else:
            #encuentro los indices de los str para extraer los datos que quiero

            time_start_index = line.find('T') + 1
            time_end_index = line.find("'", time_start_index)

            time = line[time_start_index:time_end_index]

            user_type_start_index = line.find(", ") + 1
            user_type_end_index = line.find(")", user_type_start_index)

            user_type = line[user_type_start_index:user_type_end_index]

            users_start_index = line.find('U') + 6

            users = line[users_start_index:]
            histograma[1].append([int(time), int(users)])
            #separar entresemana y fin de semana
            #if datetime.strptime(line[2:12], "%Y-%m-%d").weekday() < 5:
            #hago una lista con los datos
            #    data_list_weekdays.append([int(time), int(users)])
            #else:
            #    data_list_weekends.append([int(time), int(users)])

#print(data_list_weekdays)
#print(data_list_weekends)

for histograma in histogramas:
        #Hago lista con sublistas[tiempo, usuarios] para las 24 horas
        suma_week = 24*[0]

        for sublist in histograma[1]:
            index = sublist[0]
            value = sublist[1]
            suma_week[index] += value  #sumo los usuarios en las mismas horas

        suma_list_week = [[i,suma_week[i]] for i in range(24)]
        #print(suma_weekdays)
        #print(f"SUMA {suma_list_weekdays}")

        x = [sublist[0] for sublist in suma_list_week] #TIEMPO  X - AXIS
        y = [sublist[1] for sublist in suma_list_week] #USUARIOS Y - AXIS

        if 'day' in histograma[0]:
            plt.bar(x, y, color = "green")
            plt.xlabel('Time')
            plt.ylabel('Usuarios')
            plt.title(f'Histograma {histograma[0]}')
            plt.savefig(f'Histograma {histograma[0]}')
            plt.show()
        else:
            plt.bar(x, y, color = "purple")
            plt.xlabel('Time')
            plt.ylabel('Usuarios')
            plt.title(f'Histograma {histograma[0]}')
            plt.savefig(f'Histograma {histograma[0]}')
            plt.show()


def MineCaptures(website = None, numericStartDate = None, numericEndDate = None,limitPerRequest = None):
    import requests
    from Utilities.SaveHDf5 import saveAt
    from Utilities.reserved import replaceReservedCharacters as rp

    if website == None:
        website = 'http://www.huffingtonpost.com/'
    if numericStartDate == None:
        numericStartDate = '20150101010101'
    if numericEndDate == None:
        numericEndDate = '20160101010101'
    if limitPerRequest == None:
        limitPerRequest = '50'

    returnSize = int(limitPerRequest) #so that the check for return is the same size as requested return

    stampList = []  #list to contain all time stamps for a given website

    while int(numericEndDate) > int(numericStartDate) and returnSize == int(limitPerRequest):
        urlString = 'http://web.archive.org/cdx/search/cdx?url='+website+\
                    '&output=json&fl=timestamp&filter=!statuscode:200&filter=!mimetype:text/html&from='+\
                    str(numericStartDate)+'&to='+str(numericEndDate)+'&collapse=timestamp:8&limit=' + str(limitPerRequest)
        response = requests.get(urlString)
        data = response.json()

        for id in range(0,len(data)):
            if(str(data[id])[2:-2] != 'timestamp'):
                strdat =str(data[id])
                stampList.append(int(strdat[2:-2]))

        print(stampList[-1]) #just prints the last return from each search
        returnSize = (len(data)-1) #used to check if the endDate has been reached (-1 is for the first element('timestamp')
        numericStartDate = int(stampList[-1]) + 1  #sets the startdate to end of the previous returns

    path = '../TAdata/CapturesAvailible/' + rp(website)
    saveAt(path, stampList, 'testset')


# 1894. 找到需要补充粉笔的学生编号
# 一个班级里有  n  个学生，编号为 0  到 n - 1  。每个学生会依次回答问题，编号为 0  的学生先回答，然后是编号为 1  的学生，以此类推，
# 直到编号为 n - 1  的学生，然后老师会重复这个过程，重新从编号为 0  的学生开始回答问题。
# 给你一个长度为 n  且下标从 0  开始的整数数组  chalk  和一个整数  k  。一开始粉笔盒里总共有  k  支粉笔。
# 当编号为  i  的学生回答问题时，他会消耗 chalk[i]  支粉笔。如果剩余粉笔数量 严格小于  chalk[i]  ，那么学生 i  需要 补充  粉笔。
# 请你返回需要 补充  粉笔的学生 编号  。
import itertools

def chalkReplacer(chalk: [int], k: int) -> int:
    chalkSumList = list(itertools.accumulate(chalk))
    chalkSum = sum(chalk)
    k = k % chalkSum
    left, right = 0, len(chalkSumList) - 1
    while left < right:
        mid = (left + right) // 2
        if k >= chalkSumList[mid]:
            left = mid + 1
        else:
            right = mid
    return left

chalkReplacer([5,1,5], 22)
chalkReplacer([3,4,1,2], 25)

1894. 找到需要补充粉笔的学生编号.py


import sys

from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QFileDialog, QFontDialog
from PyQt5.QtCore import Qt
import sqlite3
import datetime as dt
import os

class Notepad2(QMainWindow):
    def __init__(self):
        super().__init__()
        self.count = 0
        self.idofnote = 0
        uic.loadUi('notepad.ui', self)
        self.initUI()

    def initUI(self):
        self.setStyleSheet("background-color: white;")
        self.label_2.hide()
        self.lineEdit_2.hide()
        self.pushButton_4.hide()
        self.checkBox.stateChanged.connect(self.check)
        self.pushButton.clicked.connect(self.save)
        self.pushButton_2.clicked.connect(self.open)
        self.pushButton_4.clicked.connect(self.search)
        self.pushButton_5.clicked.connect(self.themes)
        self.pushButton_6.clicked.connect(self.font_change)
        self.pushButton_7.clicked.connect(self.delete_file)
        self.create()

    def create(self):
        self.con = sqlite3.connect("notes.db")
        create = self.con.cursor()
        create.execute("""CREATE TABLE IF NOT EXISTS Notes(
                       title TEXT NOT NULL,
                       id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                       date TEXT NOT NULL,
                       time TEXT NOT NULL);
                       """)
        self.con.commit()

    def save(self):
        text = self.textEdit.toPlainText()
        text = text.split("\n")
        self.title = self.lineEdit.text()
        if len(self.title) != 0:
            self.add_in_bd(self.title)
            file = open(f"{self.title}.txt", "w", encoding="utf-8")
            for line in text:
                print(line, file=file)
            QMessageBox.about(self, "Сохранение", f"Файл сохранён. Ему присвоен id {self.currentid}")
            file.close()

        else:
            self.title = "Безымянный"
            self.add_in_bd(self.title)
            file = open(f"{self.title}.txt", 'w', encoding="utf-8")
            for line in text:
                print(line, file=file)
            QMessageBox.about(self, "Сохранение", f"Файл сохранён. Ему присвоен id {self.currentid}")
            file.close()

    def add_in_bd(self, title, saving=False):
        self.title = title
        self.added_in_bd = False
        date = str(dt.datetime.now()).split()
        dat = date[0].split(":")
        dat = dat[0].split("-")
        dat = f"{dat[2]}-{dat[1]}-{dat[0]}"
        time = date[1]
        time = time.split(":")
        time = f"{time[0]}:{time[1]}"
        curs = self.con.cursor()
        curs2 = self.con.cursor()
        cursor = self.con.cursor()
        all_id = curs.execute("""SELECT id from Notes""").fetchall()
        titles = curs2.execute("""SELECT title from Notes""").fetchall()
        titles = [str(i[0]) for i in titles]
        all_id = [int(i[0]) for i in all_id]
        if all_id:
            self.idofnote = max(all_id)

        if all_id and self.title not in titles:
            self.idofnote += + 1
            self.currentid = self.idofnote
            cursor.execute("""INSERT INTO notes(title, date, time)
                        VALUES (?, ?, ?)""", (self.title, dat, time))
            if saving:
                self.added_in_bd = True
            self.con.commit()

        elif all_id and self.title in titles and saving is False:
            rewrite = QMessageBox.question(self, "Перезапись", "Файл с таким названием найден. Перезаписать?",
                                           QMessageBox.Yes, QMessageBox.No)
            if rewrite == QMessageBox.Yes:
                new_id = self.idofnote + 1
                self.currentid = new_id
                cursor.execute("""UPDATE Notes
                            SET id = ?
                            WHERE title = ?""", (new_id, self.title))
                self.con.commit()

            elif rewrite == QMessageBox.No:
                curs4 = self.con.cursor()
                alltitles = curs4.execute("""SELECT title from Notes
                                       WHERE title like ?""", ('{}(_)'.format(self.title),)).fetchall()
                lentitles = len(alltitles)
                if lentitles == 0:
                    lentitles += 1
                self.idofnote += 1
                self.currentid = self.idofnote
                self.title = f"{self.title}({lentitles})"
                cursor.execute("""INSERT INTO notes(title, date, time)
                                                    VALUES (?, ?, ?)""", (self.title, dat, time))
                self.con.commit()

        elif len(all_id) == 0:
            cursor.execute("""INSERT INTO notes(title, date, time)
                                    VALUES (?, ?, ?)""", (self.title, dat, time))
            self.currentid = cursor.execute("""SELECT id from notes WHERE title = ?""", (self.title,)).fetchone()[0]
            if saving:
                self.added_in_bd = True
            self.con.commit()

    def open(self):
        self.textEdit.clear()
        filename = QFileDialog.getOpenFileName(
            self, 'Выбрать файл', '',
            'Текстовый файл (*.txt)')[0]
        if filename:
            file = open(filename, encoding="utf-8")
            lines = file.readlines()
            lines = [line.rstrip() for line in lines]
            self.lineEdit.setText("")
            path = filename.split("/")
            name = path[-1].split(".txt")
            name = name[0]
            self.lineEdit.setText(name)
            for line in lines:
                self.textEdit.append(line)
            file.close()
            self.add_in_bd(name, True)
            if self.added_in_bd:
                QMessageBox.about(self, "Загрузка", f"Файл загружен. Его id {self.currentid}")

    def check(self, state):
        if state == Qt.Checked:
            self.label_2.show()
            self.lineEdit_2.show()
            self.pushButton_4.show()

        else:
            self.label_2.hide()
            self.lineEdit_2.hide()
            self.pushButton_4.hide()
            self.lineEdit_2.setText("")

    def font_change(self):
        font, ok_pressed = QFontDialog.getFont()
        if ok_pressed:
            self.textEdit.setFont(font)
            self.lineEdit.setFont(font)
            self.lineEdit_2.setFont(font)

    def search(self):
        search_text = self.lineEdit_2.text()
        con = sqlite3.connect("notes.db")
        curs5 = con.cursor()
        if search_text.isdigit():
            result_of_search = curs5.execute("""SELECT title from Notes
                                          WHERE id = ?""", (search_text,)).fetchall()
            if result_of_search:
                result_of_search = result_of_search[0][0]
                request = QMessageBox.question(self, "Поиск", f"Файл с id {search_text} найден. Загрузить?",
                                               QMessageBox.Yes, QMessageBox.No)
                if request == QMessageBox.Yes:
                    self.load(result_of_search)
            else:
                QMessageBox.about(self, "Поиск",
                                  "Файл не найден. Возможно его нет в базе данных или введён неверный id.")
        elif len(search_text) != 0:
            result_of_search = curs5.execute("""SELECT title from Notes
                                          WHERE title = ?""", (search_text,)).fetchall()
            if result_of_search:
                request = QMessageBox.question(self, "Поиск",
                                               f'Файл с названием "{result_of_search[0][0]}" найден. Загрузить?',
                                               QMessageBox.Yes, QMessageBox.No)
                if request == QMessageBox.Yes:
                    self.load(search_text)
            else:
                QMessageBox.about(self, "Поиск",
                                  "Файл не найден. Возможно его нет в базе данных или введено неверное название.")
        elif len(search_text) == 0:
            QMessageBox.about(self, "Поиск", "Введён неверный запрос.")

    def load(self, title):
        self.textEdit.clear()
        file = open(f"{title}.txt", encoding="utf-8")
        lines = file.readlines()
        lines = [line.rstrip() for line in lines]
        self.lineEdit.setText("")
        self.lineEdit.setText(title)
        for line in lines:
            self.textEdit.append(line)

    def themes(self):
        if self.count == 0:
            self.pushButton_5.setText(f"Светлая тема")
            self.setStyleSheet("background-color: black; color: white;")
            self.pushButton.setStyleSheet("background-color: grey")
            self.pushButton_2.setStyleSheet("background-color: grey")
            self.pushButton_4.setStyleSheet("background-color: grey")
            self.pushButton_5.setStyleSheet("background-color: grey")
            self.pushButton_6.setStyleSheet("background-color: grey")
            self.pushButton_7.setStyleSheet("background-color: grey")
            self.textEdit.setStyleSheet("background-color: rgb(190, 190, 190, 255)")
            self.lineEdit.setStyleSheet("background-color: rgb(190, 190, 190, 255)")
            self.lineEdit_2.setStyleSheet("background-color: rgb(190, 190, 190, 255)")
            self.count = 1

        else:
            self.pushButton_5.setText(f"Тёмная тема")
            self.setStyleSheet("background-color: white;")
            self.pushButton.setStyleSheet("background-color: white;")
            self.pushButton_2.setStyleSheet("background-color: white;")
            self.pushButton_4.setStyleSheet("background-color: white;")
            self.pushButton_5.setStyleSheet("background-color: white;")
            self.pushButton_6.setStyleSheet("background-color: white")
            self.pushButton_7.setStyleSheet("background-color: white")
            self.textEdit.setStyleSheet("background-color: white;")
            self.lineEdit.setStyleSheet("background-color: white")
            self.lineEdit_2.setStyleSheet("background-color: white")
            self.count = 0

    def delete_file(self):
        filename = QFileDialog.getOpenFileName(
            self, 'Выбрать файл для удаления', '',
            'Текстовый файл (*.txt)')[0]
        if filename:
            path = filename.split("/")
            name = path[-1].split(".txt")
            name = name[0]
            self.check_in_db(name)
            if self.check_in_db:
                delete_yes = QMessageBox.question(self, "Удаление",
                                               f'Удалить файл {name} ?',
                                               QMessageBox.Yes, QMessageBox.No)
                if delete_yes:
                    cursor = self.con.cursor()
                    cursor.execute("DELETE FROM notes WHERE title = ?", (name,))
                    self.con.commit()
                    os.remove(filename)
                    if self.lineEdit.text() == name:
                        self.lineEdit.setText("")
                        self.textEdit.clear()
                    QMessageBox.about(self, "Удаление", "Файл удалён.")
            else:
                delete_yes = QMessageBox.question(self, "Удаление",
                                                  f'Удалить файл {name} ?',
                                                  QMessageBox.Yes, QMessageBox.No)
                if delete_yes:
                    os.remove(filename)
                    if self.lineEdit.text() == name:
                        self.lineEdit.setText("")
                        self.textEdit.clear()
                    QMessageBox.about(self, "Удаление", "Файл удалён.")
        else:
            QMessageBox.about(self, "Удаление", "Файл не выбран.")

    def check_in_db(self, title):
        self.file_in_db = False
        cursor = self.con.cursor()
        titles = cursor.execute("""SELECT title from notes""")
        if title in titles:
            self.file_in_db = True

def except_hook(cls, exception, traceback):
    sys.__excepthook__(cls, exception, traceback)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Notepad2()
    ex.show()
    sys.exit(app.exec_())


from django.contrib.auth import get_user_model
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet

from apps.post.api.v1 import serializers
from apps.post.models import Post

User = get_user_model()

class PostViewSet(
    mixins.CreateModelMixin,
    mixins.UpdateModelMixin,
    mixins.DestroyModelMixin,
    mixins.RetrieveModelMixin,
    mixins.ListModelMixin,
    GenericViewSet
):

    serializer_classes = {
        'update': serializers.PostUpdateSerializer,
        'partial_update': serializers.PostUpdateSerializer,
        'create': serializers.PostCreateSerializer,
        'list': serializers.PostListSerializer,
        'retrieve': serializers.PostDetailSerializer,
    }

    def get_queryset(self):
        user = self.request.user
        if self.action == 'list':
            return Post.active_objects.active().filter(user__in=user.followings.filter(is_active=True))

        if self.action == 'retrieve':
            return Post.active_objects.active()

        return Post.active_objects.active().filter(user=user)

    def get_serializer_class(self):
        return self.serializer_classes.get(self.action)

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)


# coding: utf8
import pygame, sys
from pygame.locals import *
import random
from tiro import Tiro
class Inimigo(object):
	def __init__(self,DISPLAYSURF):
		self.DISPLAYSURF=DISPLAYSURF
		self.x=random.randint(1, 450);
		self.y=1
		self.posicao="DIREITA";
		self.bala=list()
		self.cont=1

	def controleDeMovimento(self):
		self.cont+=1
		if(self.cont>=1000):
			self.cont=1

		if(self.x>=450):
			self.x-=1
			self.posicao="CIMA";
		if(self.x<=10):
			self.x+=1
			self.posicao="BAIXO";
		if(self.y>=450):
			self.y-=1
			self.posicao="DIREITA";
		if(self.y<10):
			self.y+=1
			self.posicao="ESQUERDA";

		if(float(random.randint(1, 1000)) % 100 ==0):
			self.posicao="CIMA";
			self.bala.append(Tiro(self.x,self.y,self.posicao,(255,0,0)))
		if(float(random.randint(1, 1000)) % 200 ==0):
			self.posicao="BAIXO";
			self.bala.append(Tiro(self.x,self.y,self.posicao,(255,0,0)))
		if(float(random.randint(1, 1000)) % 300 ==0):
			self.posicao="DIREITA";
			self.bala.append(Tiro(self.x,self.y,self.posicao,(255,0,0)))
		if(float(random.randint(1, 1000)) % 400 ==0):
			self.posicao="ESQUERDA";
			self.bala.append(Tiro(self.x,self.y,self.posicao,(255,0,0)))

		if(self.cont % 20 ==0):
			self.bala.append(Tiro(self.x,self.y,self.posicao,(255,0,0)))

		for i in range(len(self.bala)):
			self.bala[i].atira()
			self.bala[i].drawBala(self.DISPLAYSURF)
			if(self.bala[i].x > 550 or self.bala[i].x < -50):
				del self.bala[i]
				break
			if(self.bala[i].y > 550 or self.bala[i].y < -50):
				del self.bala[i]
				break

		if(self.posicao=="DIREITA"):
			self.x+=3
		if(self.posicao=="ESQUERDA"):
			self.x-=3
		if(self.posicao=="CIMA"):
			self.y-=3
		if(self.posicao=="BAIXO"):
			self.y+=3

	def draw(self,tamanho):
		pygame.draw.rect(self.DISPLAYSURF,  (255, 0, 0), (self.x, self.y, tamanho, tamanho))
		if(self.posicao=="BAIXO"):
			pygame.draw.rect(self.DISPLAYSURF,  (255, 0, 0), (self.x+(tamanho/3), self.y+tamanho, tamanho/3, tamanho/3))
		elif(self.posicao=="CIMA"):
			pygame.draw.rect(self.DISPLAYSURF,  (255, 0, 0), (self.x+(tamanho/3), self.y-(tamanho/3), tamanho/3, tamanho/3))
		elif(self.posicao=="DIREITA"):
			pygame.draw.rect(self.DISPLAYSURF,  (255, 0, 0), (self.x+tamanho, self.y+(tamanho/3), tamanho/3, tamanho/3))
		elif(self.posicao=="ESQUERDA"):
			pygame.draw.rect(self.DISPLAYSURF,  (255, 0, 0), (self.x-(tamanho/3), self.y+(tamanho/3), tamanho/3, tamanho/3))