'''
Try online python:
  https://www.programiz.com/python-programming/online-compiler/
  https://replit.com/languages/python3
'''

# ----------------------------------------------------------------------------
#                              K-SUBSETS
# Task:
# Generate all k-subsets of a given set of N items.
# Technical note: A set is represented as a list.
# ----------------------------------------------------------------------------

# 1. -------------------------------------------------------------------------
# Idea:
# The first item of the given set is either an element
# of each k-subset or it is not.
# Implementation:
# Divide the set mentally
# into the first item F and the rest of the list  RL.
# 1. Generate all subsets of size k in RL
# 2. Generate all subsets of size k-1 in RL,
#    prepended F to each generated subset
#    and append the subset the to the result of 1.

def k_subsets1( set, k ):

  if k == len( set ) :
    return [ set ]
  if k == 0 :
    return [[]]

  # all subsets without the first item
  result = k_subsets1( set[1:], k )

  # all subsets with the first item:
  resWithout = k_subsets1( set[1:], k-1 )
  for subset in resWithout:
    result.append( [set[0]] + subset )

  return result




# 2. -------------------------------------------------------------------------
# Idea:
# For each item I in the set, generate all subsets
# of size k-1, using only the elements to the right of I
# (with higher index in the list)
# and prepend I to each of the generated subsets.

def k_subsets2( set, k ):
  if k == 0:
    return [[]]
  if k == len(set) :
    return [set]

  result = []
  for i in range( len(set) ):
    smallerSubsets = k_subsets2( set[i+1:], k-1 )
    for subset in smallerSubsets:
      result.append( [set[i]] + subset )

  return result

# 3. -------------------------------------------------------------------------
# Idea:
# Collect the items of the subset in a single result list.
# Add one item to the result on each recursion level.
# The technical advantage of the idea is that no
# intermediate lists (results) are generated.

def k_subsets3a( set, k, i_start, result, depth ):

  if depth == k:
    print( result )
    return

  i_lastStart = len(set) - (k-depth)
  for i in range( i_start, i_lastStart+1 ):
    result[depth] = set[i]
    k_subsets3a( set, k, i+1, result, depth+1 )

# Fancy modification of 3a approach:
# Process lists from the end to the beginning,
# with decreasing remaining depth to simplify the code.
# No more strange index calculations! Cool, isn't it? :-)

def k_subsets3b( set, i_end, result, remainingDepth ):
  if remainingDepth < 0:   # note the zero!
    print( result )
    return

  for i in range( i_end, remainingDepth-1, -1 ):  # go backwards
    result[remainingDepth] = set[i]
    k_subsets3b( set, i-1, result, remainingDepth-1 )

# 4. -------------------------------------------------------------------------
# Idea:
# Identify a k-subset by its  characteristic (indicator) vector.
# That is, a 0/1 vector of length N containing exactly k 1s.
# Generate all characteristic vectors, in descending
# lexicographical order, to represent all k-subsets,
# from {1, 2, ..., k} to { N-k+1, N-k+2, ..., N }, that is
# from ( 1, 1, ..., 1, 0, ..., 0, 0) to ( 0, 0, ..., 0, 1, ..., 1, 1).



# Abstain from recursion, iteration works nicely too in this case.


def nextChi( chi, k, N ):

    # skip all 1s at the end:
    j1 = N-1
    while chi[j1] == 1: j1 -= 1

     # no more subsets?
    if j1 == N-k-1: return False

    # next subset
    j0 = j1-1
    while chi[j0] == 0: j0 -= 1
    chi[j0], chi[j0+1] = 0, 1  # move 1 to the right
    # move remaining 1s from the end to just behind current 1
    numOfEndOnes = N-j1-1  #
    for j in range( j0+2, j0+2 + numOfEndOnes ): chi[j] = 1
    for j in range( j0+2 + numOfEndOnes, N ): chi[j] = 0

    return True

def k_subsets4(  set, k ):
    N = len(set)
    if k == N:
       print( *set ); return

    chi = [1]*k  + [0]*(N-k)
    rank = 0
    while True:
          print( *chi, end = '   ' )
          print( [set[k] for k in range(N) if chi[k] == 1 ], ' ',  rank )
          if nextChi( chi, k, N ) == False: break
          rank += 1



# ----------------------------------------------------------------------------
#   M A I N    -   e x p e r i m e n t s
# ----------------------------------------------------------------------------


# set = [ 11, 22, 33, 44, 55, 66, 77, 88 ]
set = [ 1,2,3,4,5,6]

k = 3
k_subsets4( set, k )

'''
characteristic   subset     rank
   vector

   1 1 1 0 0 0   [1, 2, 3]    0
   1 1 0 1 0 0   [1, 2, 4]    1
   1 1 0 0 1 0   [1, 2, 5]    2
   1 1 0 0 0 1   [1, 2, 6]    3
   1 0 1 1 0 0   [1, 3, 4]    4
   1 0 1 0 1 0   [1, 3, 5]    5
   1 0 1 0 0 1   [1, 3, 6]    6
   1 0 0 1 1 0   [1, 4, 5]    7
   1 0 0 1 0 1   [1, 4, 6]    8
   1 0 0 0 1 1   [1, 5, 6]    9
   0 1 1 1 0 0   [2, 3, 4]   10
   0 1 1 0 1 0   [2, 3, 5]   11
   0 1 1 0 0 1   [2, 3, 6]   12
   0 1 0 1 1 0   [2, 4, 5]   13
   0 1 0 1 0 1   [2, 4, 6]   14
   0 1 0 0 1 1   [2, 5, 6]   15
   0 0 1 1 1 0   [3, 4, 5]   16
   0 0 1 1 0 1   [3, 4, 6]   17
   0 0 1 0 1 1   [3, 5, 6]   18
   0 0 0 1 1 1   [4, 5, 6]   19


# alternate calls k_subsets1 and k_subsets2
subsets = k_subsets2( set, k )
for subs in subsets:
  print( subs )
print( "Number of subsets:", len(subsets) )
'''

#k_subsets3a( set, k, 0, [0]*k, 0 )
#k_subsets3b( set, len(set)-1, [0]*k, k-1 )
