
import logging
import math
from functools import partial
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union

import torch

from .tensor_utils import tensor_tree_map, tree_map




def        __UpperCamelCase					(    lowerCAmelCase__							:    Union[dict, list, tuple, torch.Tensor]  ):
	__a			:  Optional[Any]		    =  []
	if isinstance(a__  ,       a__  ):
		for v in tree.values():
			shapes.extend(_fetch_dims(a__  )  )
	elif isinstance(a__  ,       (list, tuple)  ):
		for t in tree:
			shapes.extend(_fetch_dims(a__  )  )
	elif isinstance(a__  ,       torch.Tensor  ):
		shapes.append(tree.shape  )
	else:
		raise ValueError('''Not supported'''  )

	return shapes




@torch.jit.ignore
def        __UpperCamelCase					(    lowerCAmelCase__							:    int  ,       lowerCAmelCase__							:    Tuple[int, ...]  ):
	__a			:  Optional[int]		    =  []
	for d in reversed(a__  ):
		idx.append(flat_idx % d  )
		__a			:  str		    =  flat_idx // d

	return tuple(reversed(a__  )  )




@torch.jit.ignore
def        __UpperCamelCase					(    lowerCAmelCase__							:    Sequence[int]  ,       lowerCAmelCase__							:    Sequence[int]  ,       lowerCAmelCase__							:    Sequence[int]  ,       lowerCAmelCase__							:    Optional[Sequence[bool]] = None  ,       lowerCAmelCase__							:    Optional[Sequence[bool]] = None  ,       ):





	def reduce_edge_list(lowerCAmelCase__							:    List[bool]  ) -> None:
		__a			:  int		    =  True
		for i in range(len(a__  )  ):
			__a			:  Any		    =  -1 * (i + 1)
			l[reversed_idx] &= tally
			__a			:  str		    =  l[reversed_idx]

	if start_edges is None:
		__a			:  Dict		    =  [s == 0 for s in start]
		reduce_edge_list(a__  )
	if end_edges is None:
		__a			:  Tuple		    =  [e == (d - 1) for e, d in zip(a__  ,       a__  )]
		reduce_edge_list(a__  )

	# Base cases. Either start/end are empty and we're done, or the final,
	# one-dimensional tensor can be simply sliced
	if len(a__  ) == 0:
		return [()]
	elif len(a__  ) == 1:
		return [(slice(start[0]  ,       end[0] + 1  ),)]

	__a			:  Dict		    =  []
	__a			:  List[str]		    =  []

	# Dimensions common to start and end can be selected directly
	for s, e in zip(a__  ,       a__  ):
		if s == e:
			path_list.append(slice(a__  ,       s + 1  )  )
		else:
			break

	__a			:  Union[str, Any]		    =  tuple(a__  )
	__a			:  Union[str, Any]		    =  len(a__  )

	# start == end, and we're done
	if divergence_idx == len(a__  ):
		return [path]

	def upper() -> Tuple[Tuple[slice, ...], ...]:
		assert start_edges is not None
		assert end_edges is not None

		__a			:  Optional[int]		    =  start[divergence_idx]
		return tuple(
		    path + (slice(a__  ,       sdi + 1  ),) + s
		    for s in _get_minimal_slice_set(
		        start[divergence_idx + 1 :]  ,       [d - 1 for d in dims[divergence_idx + 1 :]]  ,       dims[divergence_idx + 1 :]  ,       start_edges=start_edges[divergence_idx + 1 :]  ,       end_edges=[True for _ in end_edges[divergence_idx + 1 :]]  ,       )  )

	def lower() -> Tuple[Tuple[slice, ...], ...]:
		assert start_edges is not None
		assert end_edges is not None

		__a			:  int		    =  end[divergence_idx]
		return tuple(
		    path + (slice(a__  ,       edi + 1  ),) + s
		    for s in _get_minimal_slice_set(
		        [0 for _ in start[divergence_idx + 1 :]]  ,       end[divergence_idx + 1 :]  ,       dims[divergence_idx + 1 :]  ,       start_edges=[True for _ in start_edges[divergence_idx + 1 :]]  ,       end_edges=end_edges[divergence_idx + 1 :]  ,       )  )

	# If both start and end are at the edges of the subtree rooted at
	# divergence_idx, we can just select the whole subtree at once
	if start_edges[divergence_idx] and end_edges[divergence_idx]:
		slices.append(path + (slice(start[divergence_idx]  ,       end[divergence_idx] + 1  ),)  )
	# If just start is at the edge, we can grab almost all of the subtree,
	# treating only the ragged bottom edge as an edge case
	elif start_edges[divergence_idx]:
		slices.append(path + (slice(start[divergence_idx]  ,       end[divergence_idx]  ),)  )
		slices.extend(lower()  )
	# Analogous to the previous case, but the top is ragged this time
	elif end_edges[divergence_idx]:
		slices.extend(upper()  )
		slices.append(path + (slice(start[divergence_idx] + 1  ,       end[divergence_idx] + 1  ),)  )
	# If both sides of the range are ragged, we need to handle both sides
	# separately. If there's contiguous meat in between them, we can index it
	# in one big chunk
	else:
		slices.extend(upper()  )
		__a			:  Optional[Any]		    =  end[divergence_idx] - start[divergence_idx]
		if middle_ground > 1:
			slices.append(path + (slice(start[divergence_idx] + 1  ,       end[divergence_idx]  ),)  )
		slices.extend(lower()  )

	return slices




@torch.jit.ignore
def        __UpperCamelCase					(    lowerCAmelCase__							:    torch.Tensor  ,       lowerCAmelCase__							:    int  ,       lowerCAmelCase__							:    int  ,       lowerCAmelCase__							:    int  ):
	__a			:  Tuple		    =  t.shape[:no_batch_dims]
	__a			:  List[str]		    =  list(_flat_idx_to_idx(a__  ,       a__  )  )
	# _get_minimal_slice_set is inclusive
	__a			:  Tuple		    =  list(_flat_idx_to_idx(flat_end - 1  ,       a__  )  )

	# Get an ordered list of slices to perform
	__a			:  Any		    =  _get_minimal_slice_set(
	    a__  ,       a__  ,       a__  ,       )

	__a			:  Union[str, Any]		    =  [t[s] for s in slices]

	return torch.cat([s.view((-1,) + t.shape[no_batch_dims:]  ) for s in sliced_tensors]  )




def        __UpperCamelCase					(    lowerCAmelCase__							:    Callable  ,       lowerCAmelCase__							:    Dict[str, Any]  ,       lowerCAmelCase__							:    int  ,       lowerCAmelCase__							:    int  ,       lowerCAmelCase__							:    bool = False  ,       lowerCAmelCase__							:    Any = None  ,       lowerCAmelCase__							:    bool = False  ,       ):





	if not (len(a__  ) > 0):
		raise ValueError('''Must provide at least one input'''  )

	__a			:  Union[str, Any]		    =  [shape[:no_batch_dims] for shape in _fetch_dims(a__  )]
	__a			:  int		    =  tuple([max(a__  ) for s in zip(*a__  )]  )

	def _prep_inputs(lowerCAmelCase__							:    torch.Tensor  ) -> torch.Tensor:
		if not low_mem:
			if not sum(t.shape[:no_batch_dims]  ) == no_batch_dims:
				__a			:  Optional[int]		    =  t.expand(orig_batch_dims + t.shape[no_batch_dims:]  )
			__a			:  Optional[Any]		    =  t.reshape(-1  ,       *t.shape[no_batch_dims:]  )
		else:
			__a			:  int		    =  t.expand(orig_batch_dims + t.shape[no_batch_dims:]  )
		return t

	__a			:  List[str]		    =  tensor_tree_map(_prep_inputs  ,       a__  )
	__a			:  Union[str, Any]		    =  None
	if _out is not None:
		__a			:  Optional[Any]		    =  tensor_tree_map(lambda lowerCAmelCase__  : t.view([-1] + list(t.shape[no_batch_dims:]  )  )  ,       _out  )

	__a			:  List[str]		    =  1
	for d in orig_batch_dims:
		flat_batch_dim *= d

	__a			:  Optional[Any]		    =  flat_batch_dim // chunk_size + (flat_batch_dim % chunk_size != 0)

	def _select_chunk(lowerCAmelCase__							:    torch.Tensor  ) -> torch.Tensor:
		return t[i : i + chunk_size] if t.shape[0] != 1 else t

	__a			:  int		    =  0
	__a			:  str		    =  prepped_outputs
	for _ in range(a__  ):
		# Chunk the input
		if not low_mem:
			__a			:  Any		    =  _select_chunk
		else:
			__a			:  Dict		    =  partial(
			    _chunk_slice  ,       flat_start=a__  ,       flat_end=min(a__  ,       i + chunk_size  )  ,       no_batch_dims=len(a__  )  ,       )

		__a			:  Any		    =  tensor_tree_map(a__  ,       a__  )

		# Run the layer on the chunk
		__a			:  Optional[Any]		    =  layer(**a__  )

		# Allocate space for the output
		if out is None:
			__a			:  Union[str, Any]		    =  tensor_tree_map(lambda lowerCAmelCase__  : t.new_zeros((flat_batch_dim,) + t.shape[1:]  )  ,       a__  )

		# Put the chunk in its pre-allocated space
		if isinstance(a__  ,       a__  ):

			def assign(lowerCAmelCase__							:    dict  ,       lowerCAmelCase__							:    dict  ) -> None:
				for k, v in da.items():
					if isinstance(a__  ,       a__  ):
						assign(a__  ,       da[k]  )
					else:
						if _add_into_out:
							v[i : i + chunk_size] += da[k]
						else:
							__a			:  Dict		    =  da[k]

			assign(a__  ,       a__  )
		elif isinstance(a__  ,       a__  ):
			for xa, xa in zip(a__  ,       a__  ):
				if _add_into_out:
					xa[i : i + chunk_size] += xa
				else:
					__a			:  Optional[Any]		    =  xa
		elif isinstance(a__  ,       torch.Tensor  ):
			if _add_into_out:
				out[i : i + chunk_size] += output_chunk
			else:
				__a			:  Union[str, Any]		    =  output_chunk
		else:
			raise ValueError('''Not supported'''  )

		i += chunk_size

	__a			:  Tuple		    =  tensor_tree_map(lambda lowerCAmelCase__  : t.view(orig_batch_dims + t.shape[1:]  )  ,       a__  )

	return out



class 		UpperCamelCase__  :
			def __init__(self			: Any ,    snake_case_			: Dict = 5_1_2 ,    ):
				__a			:  Tuple		    =  max_chunk_size
				__a			:  List[str]		    =  None
				__a			:  Any		    =  None
			def 				lowerCAmelCase      (self			: List[str] ,    snake_case_			: Any ,    snake_case_			: int ,    snake_case_			: List[Any]       ):
				logging.info('''Tuning chunk size...'''       )

				if min_chunk_size >= self.max_chunk_size:
					return min_chunk_size

				__a			:  int		    =  [2**l for l in range(int(math.log(self.max_chunk_size ,    2       )       ) + 1       )]
				__a			:  List[str]		    =  [c for c in candidates if c > min_chunk_size]
				__a			:  Dict		    =  [min_chunk_size] + candidates
				candidates[-1] += 4

				def test_chunk_size(snake_case_			: List[str]       ) -> bool:
					try:
						with torch.no_grad():
							fn(*__lowerCAmelCase ,    chunk_size=__lowerCAmelCase       )
						return True
					except RuntimeError:
						return False

				__a			:  Optional[Any]		    =  0
				__a			:  List[str]		    =  len(__lowerCAmelCase       ) - 1
				while i > min_viable_chunk_size_index:
					__a			:  Optional[Any]		    =  test_chunk_size(candidates[i]       )
					if not viable:
						__a			:  Tuple		    =  (min_viable_chunk_size_index + i) // 2
					else:
						__a			:  List[Any]		    =  i
						__a			:  Tuple		    =  (i + len(__lowerCAmelCase       ) - 1) // 2

				return candidates[min_viable_chunk_size_index]
			def 				lowerCAmelCase      (self			: Tuple ,    snake_case_			: Union[str, Any] ,    snake_case_			: str       ):
				__a			:  Tuple		    =  True
				for aa, aa in zip(__lowerCAmelCase ,    __lowerCAmelCase       ):
					assert type(__lowerCAmelCase       ) == type(__lowerCAmelCase       )
					if isinstance(__lowerCAmelCase ,    (list, tuple)       ):
						consistent &= self._compare_arg_caches(__lowerCAmelCase ,    __lowerCAmelCase       )
					elif isinstance(__lowerCAmelCase ,    __lowerCAmelCase       ):
						__a			:  Optional[int]		    =  [v for _, v in sorted(aa.items() ,    key=lambda snake_case_       : x[0]       )]
						__a			:  Union[str, Any]		    =  [v for _, v in sorted(aa.items() ,    key=lambda snake_case_       : x[0]       )]
						consistent &= self._compare_arg_caches(__lowerCAmelCase ,    __lowerCAmelCase       )
					else:
						consistent &= aa == aa

				return consistent







			def 				lowerCAmelCase      (self			: List[str] ,    snake_case_			: Tuple ,    snake_case_			: Optional[int] ,    snake_case_			: Optional[Any] ,    ):
				__a			:  int		    =  True
				__a			:  Dict		    =  tree_map(lambda snake_case_       : a.shape if isinstance(__lowerCAmelCase ,    torch.Tensor       ) else a ,    __lowerCAmelCase ,    __lowerCAmelCase       )
				if self.cached_arg_data is not None:
					# If args have changed shape/value, we need to re-tune
					assert len(self.cached_arg_data       ) == len(__lowerCAmelCase       )
					__a			:  Tuple		    =  self._compare_arg_caches(self.cached_arg_data ,    __lowerCAmelCase       )
				else:
					# Otherwise, we can reuse the precomputed value
					__a			:  Optional[Any]		    =  False

				if not consistent:
					__a			:  int		    =  self._determine_favorable_chunk_size(
					    __lowerCAmelCase ,    __lowerCAmelCase ,    __lowerCAmelCase ,    )
					__a			:  str		    =  arg_data

				assert self.cached_chunk_size is not None

				return self.cached_chunk_size


def        __UpperCamelCase					(    lowerCAmelCase__							:    list  ):

 def merge(lowerCAmelCase__							:    list  ,       lowerCAmelCase__							:    list  ) -> list:

  def _merge():
   while left and right:
    yield (left if left[0] <= right[0] else right).pop(0  )
   yield from left
   yield from right

  return list(_merge()  )

 if len(lowerCAmelCase__  ) <= 1:
  return collection
 __a			:  str		    =  len(lowerCAmelCase__  ) // 2
 return merge(merge_sort(collection[:mid]  )  ,       merge_sort(collection[mid:]  )  )


if __name__ == "__main__":
      import doctest

      doctest.testmod()
      lowercase__          =input('Enter numbers separated by a comma:\n').strip()
      lowercase__          =[int(item) for item in user_input.split(',')]
      print(*merge_sort(unsorted), sep=',')
