99from sympy .core import Basic
1010
1111# this is the logical location of these functions
12- from sympy .core .compatibility import (
13- as_int , default_sort_key , is_sequence , iterable , ordered
14- )
12+ from sympy .core .compatibility import (as_int , is_sequence , iterable , ordered )
13+ from sympy .core .compatibility import default_sort_key # noqa: F401
14+
15+ import sympy
1516
1617from sympy .utilities .enumerative import (
1718 multiset_partitions_taocp , list_visitor , MultisetPartitionTraverser )
@@ -1274,7 +1275,7 @@ def rotate_right(x, y):
12741275 return x [y :] + x [:y ]
12751276
12761277
1277- def least_rotation (x ):
1278+ def least_rotation (x , key = None ):
12781279 '''
12791280 Returns the number of steps of left rotation required to
12801281 obtain lexicographically minimal string/list/tuple, etc.
@@ -1295,18 +1296,19 @@ def least_rotation(x):
12951296 .. [1] https://en.wikipedia.org/wiki/Lexicographically_minimal_string_rotation
12961297
12971298 '''
1299+ if key is None : key = sympy .Id
12981300 S = x + x # Concatenate string to it self to avoid modular arithmetic
12991301 f = [- 1 ] * len (S ) # Failure function
13001302 k = 0 # Least rotation of string found so far
13011303 for j in range (1 ,len (S )):
13021304 sj = S [j ]
13031305 i = f [j - k - 1 ]
13041306 while i != - 1 and sj != S [k + i + 1 ]:
1305- if sj < S [k + i + 1 ]:
1307+ if key ( sj ) < key ( S [k + i + 1 ]) :
13061308 k = j - i - 1
13071309 i = f [i ]
13081310 if sj != S [k + i + 1 ]:
1309- if sj < S [k ]:
1311+ if key ( sj ) < key ( S [k ]) :
13101312 k = j
13111313 f [j - k ] = - 1
13121314 else :
@@ -2398,20 +2400,18 @@ def generate_oriented_forest(n):
23982400 break
23992401
24002402
2401- def minlex (seq , directed = True , is_set = False , small = None ):
2403+ def minlex (seq , directed = True , key = None ):
24022404 """
2403- Return a tuple representing the rotation of the sequence in which
2404- the lexically smallest elements appear first, e.g. `cba ->acb`.
2405+ Return the rotation of the sequence in which the lexically smallest
2406+ elements appear first, e.g. `cba ->acb`.
2407+
2408+ The sequence returned is a tuple, unless the input sequence is a string
2409+ in which case a string is returned.
24052410
24062411 If ``directed`` is False then the smaller of the sequence and the
24072412 reversed sequence is returned, e.g. `cba -> abc`.
24082413
2409- For more efficient processing, ``is_set`` can be set to True if there
2410- are no duplicates in the sequence.
2411-
2412- If the smallest element is known at the time of calling, it can be
2413- passed as ``small`` and the calculation of the smallest element will
2414- be omitted.
2414+ If ``key`` is not None then it is used to extract a comparison key from each element in iterable.
24152415
24162416 Examples
24172417 ========
@@ -2429,51 +2429,22 @@ def minlex(seq, directed=True, is_set=False, small=None):
24292429 >>> minlex('11010011000', directed=False)
24302430 '00011001011'
24312431
2432+ >>> minlex(('bb', 'aaa', 'c', 'a'))
2433+ ('a', 'bb', 'aaa', 'c')
2434+ >>> minlex(('bb', 'aaa', 'c', 'a'), key=len)
2435+ ('c', 'a', 'bb', 'aaa')
2436+
24322437 """
2433- is_str = isinstance (seq , str )
2434- seq = list (seq )
2435- if small is None :
2436- small = min (seq , key = default_sort_key )
2437- if is_set :
2438- i = seq .index (small )
2439- if not directed :
2440- n = len (seq )
2441- p = (i + 1 ) % n
2442- m = (i - 1 ) % n
2443- if default_sort_key (seq [p ]) > default_sort_key (seq [m ]):
2444- seq = list (reversed (seq ))
2445- i = n - i - 1
2446- if i :
2447- seq = rotate_left (seq , i )
2448- best = seq
2449- else :
2450- count = seq .count (small )
2451- if count == 1 and directed :
2452- best = rotate_left (seq , seq .index (small ))
2453- else :
2454- # if not directed, and not a set, we can't just
2455- # pass this off to minlex with is_set True since
2456- # peeking at the neighbor may not be sufficient to
2457- # make the decision so we continue...
2458- best = seq
2459- for i in range (count ):
2460- seq = rotate_left (seq , seq .index (small , count != 1 ))
2461- if seq < best :
2462- best = seq
2463- # it's cheaper to rotate now rather than search
2464- # again for these in reversed order so we test
2465- # the reverse now
2466- if not directed :
2467- seq = rotate_left (seq , 1 )
2468- seq = list (reversed (seq ))
2469- if seq < best :
2470- best = seq
2471- seq = list (reversed (seq ))
2472- seq = rotate_right (seq , 1 )
2473- # common return
2474- if is_str :
2475- return '' .join (best )
2476- return tuple (best )
2438+
2439+ if key is None : key = sympy .Id
2440+ best = rotate_left (seq , least_rotation (seq , key = key ))
2441+ if not directed :
2442+ rseq = seq [::- 1 ]
2443+ rbest = rotate_left (rseq , least_rotation (rseq , key = key ))
2444+ best = min (best , rbest , key = key )
2445+
2446+ # Convert to tuple, unless we started with a string.
2447+ return tuple (best ) if not isinstance (seq , str ) else best
24772448
24782449
24792450def runs (seq , op = gt ):
0 commit comments