[docs]defensure_float(value):"""Make sure datetime values are properly converted to floats."""try:# the last 3 boolean checks are for arrays with datetime64 and with# a timezone, see these SO posts:# https://stackoverflow.com/q/60714568/4549682# https://stackoverflow.com/q/23063362/4549682# somewhere, the datetime64 with timezone is getting converted to 'O' dtypeif(isinstance(value,datetime)orisinstance(value,np.datetime64)ornp.issubdtype(value.dtype,np.datetime64)orstr(value.dtype).startswith("datetime64")orvalue.dtype=="O"):returndate2num(value)else:# another numpy dtype like float64returnvalueexceptAttributeError:# possibly int or other float/int dtypereturnvalue
# From https://www.geeksforgeeks.org/maximum-bipartite-matching/
# A DFS based recursive function that returns true if a matching for vertex# u is possible
[docs]defbpm(self,u,matchR,seen):# Try every job one by oneforvinrange(self.jobs):# If applicant u is interested# in job v and v is not seenifself.graph[u][v]andnotseen[v]:# Mark v as visitedseen[v]=True# If job 'v' is not assigned to an applicant OR previously# assigned applicant for job v (which is matchR[v]) has an# alternate job available. Since v is marked as visited in the# above line, matchR[v] in the following recursive call will not# get job 'v' againifmatchR[v]==-1orself.bpm(matchR[v],matchR,seen):matchR[v]=ureturnTruereturnFalse
# Returns maximum number of matching
[docs]defmaxBPM(self):# An array to keep track of the applicants assigned to jobs. The value# of matchR[i] is the applicant number assigned to job i, the value -1# indicates nobody is assigned.matchR=[-1]*self.jobs# Count of jobs assigned to applicantsresult=0foriinrange(self.ppl):# Mark all jobs as not seen for next applicant.seen=[False]*self.jobs# Find if the applicant 'u' can get a jobifself.bpm(i,matchR,seen):result+=1returnresult,matchR
[docs]defmaximum_bipartite_matching(graph:np.ndarray)->np.ndarray:"""Finds the maximum bipartite matching of a graph Parameters ---------- graph : np.ndarray The graph, represented as a boolean matrix Returns ------- order : np.ndarray The order in which to traverse the graph to visit a maximum of nodes """g=GFG(graph)_,order=g.maxBPM()returnnp.asarray(order)
[docs]defalways_iterable(obj,base_type=(str,bytes)):"""If *obj* is iterable, return an iterator over its items:: >>> obj = (1, 2, 3) >>> list(always_iterable(obj)) [1, 2, 3] If *obj* is not iterable, return a one-item iterable containing *obj*:: >>> obj = 1 >>> list(always_iterable(obj)) [1] If *obj* is ``None``, return an empty iterable: >>> obj = None >>> list(always_iterable(None)) [] By default, binary and text strings are not considered iterable:: >>> obj = 'foo' >>> list(always_iterable(obj)) ['foo'] If *base_type* is set, objects for which ``isinstance(obj, base_type)`` returns ``True`` won't be considered iterable. >>> obj = {'a': 1} >>> list(always_iterable(obj)) # Iterate over the dict's keys ['a'] >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit [{'a': 1}] Set *base_type* to ``None`` to avoid any special handling and treat objects Python considers iterable as iterable: >>> obj = 'foo' >>> list(always_iterable(obj, base_type=None)) ['f', 'o', 'o'] """ifobjisNone:returniter(())if(base_typeisnotNone)andisinstance(obj,base_type):returniter((obj,))try:returniter(obj)exceptTypeError:returniter((obj,))