text: from scipy.stats import uniform, norm
  import math, random, sys
  import numpy as np
  import itertools
  
  
  """
  " Adapted version from Marten Voorberg.
  """
  def generate_uniform_data(amount, lower_bound, upper_bound):
      read = uniform.rvs(size=amount)
      return np.vectorize(lambda x: lower_bound + math.floor(x * (upper_bound - lower_bound)))(read)
  
  
  def generate_normal_data(amount, mean, sd, lower_bound, upper_bound):
      read = norm(mean, sd).rvs(size=amount)
      # read.sort()
      return np.vectorize(lambda x: math.floor(max(min(x, upper_bound), lower_bound)))(read)
  
  
  def generate_uniform_data_list(amount, max_len):
      return generate_uniform_data(amount, 0, max_len-1)
  
  
  def generate_normal_data_list(amount, max_len, mean, sd):
      return generate_normal_data(amount, mean, sd, 0, max_len-1)
  
  
  SETUP = 'SETUP'
  EXPERIMENT = 'EXPERIMENT'
  INSERT = 0
  QUERY = 1
  DELETE = 2
  GET = 3
  SET = 4
  U_min = -2 ** 31
  U_max = 2 ** 31 - 1
  
  
  
  def write_to_file(filename, setup_data, experiment_operations, experiment_data):
      if len(experiment_data) != len(experiment_operations):
          raise ValueError('The number of numbers in the experiment should be the same as the number of operations')
  
      if any(op not in [INSERT, QUERY, DELETE, GET, SET] for op in experiment_operations):
          raise ValueError('Only INSERT, QUERY, DELETE, GET and SET operations are allowed.')
      if any(op in [GET, SET] for op in experiment_operations):
          print("Please note that you are including operations for GET or SET instructions")
  
      print("Writing to ", filename)
      with open(filename, 'w+') as file:
          file.write(f'{SETUP} {len(setup_data)}\n')
          file.writelines(map(lambda s: str(s) + '\n', setup_data))
          file.write(f'{EXPERIMENT} {len(experiment_data)}\n')
  
          for (operation, number) in zip(experiment_operations, experiment_data):
              file.write(f'{operation} {number}\n')
  
  
  
  if __name__ == '__main__':
      FILES_PER_EXPONENT = 10
      MIN_EXPONENT = 10
      MAX_EXPONENT = 20
      MEAN = 2 ** 28
      SD = 2 ** 25
  
      DYN_INSTR = int(2 ** 20 / 3)
      TWO_INSTR = int(2 ** 20 / 2)
      STATIC_INSTR = int(2 ** 20 / 1)
  
      INSTR_AMOUNT = STATIC_INSTR # change depending on instruction amount
  
      for exp in range(MIN_EXPONENT, MAX_EXPONENT + 1):
          print(f'Exponent: {exp : <5}')
          for file_id in range(0, FILES_PER_EXPONENT):
              print(f'  Iteration: {file_id : <2}')
              setup_amount = 2 ** exp
              
              MEAN_LIST = 2 ** (exp - 1)
              SD_LIST = 2 ** (exp - 3)
  
              # SETUP
              setup_numbers = generate_normal_data(setup_amount, MEAN, SD, U_min, U_max)
              # setup_numbers = generate_uniform_data(setup_amount, U_min, U_max) 
              # setup_numbers = generate_normal_data_list(setup_amount, setup_amount, MEAN_LIST, SD_LIST) 
              # setup_numbers = generate_uniform_data_list(setup_amount, setup_amount) 
  
  
              # QUERY
              is_member_instructions = zip(generate_normal_data(INSTR_AMOUNT, MEAN, SD, U_min, U_max),
              # is_member_instructions = zip(generate_uniform_data(INSTR_AMOUNT, U_min, U_max),
              # is_member_instructions = zip(generate_normal_data_list(INSTR_AMOUNT, setup_amount, MEAN_LIST, SD_LIST),
              # is_member_instructions = zip(generate_uniform_data_list(INSTR_AMOUNT, setup_amount),
                                           itertools.repeat(QUERY))
  
  
  
              # DELETE set
              delete_set_numbers = random.sample(list(setup_numbers), min(setup_amount, INSTR_AMOUNT))
              if len(delete_set_numbers) < INSTR_AMOUNT:
                  delete_set_numbers.extend(
                      generate_normal_data(INSTR_AMOUNT - len(delete_set_numbers), MEAN, SD, U_min, U_max))
                      # generate_uniform_data(INSTR_AMOUNT - len(delete_numbers), U_min, U_max))
  
              delete_set_instructions = zip(delete_set_numbers, itertools.repeat(DELETE))
  
  
  
              # DELETE list
              # delete_list_instructions = zip(generate_normal_data_list(INSTR_AMOUNT, setup_amount, MEAN_LIST, SD_LIST),
              # delete_list_instructions = zip(generate_uniform_data_list(INSTR_AMOUNT, setup_amount),
                                        # itertools.repeat(DELETE))
  
  
  
              # ADD
              # insert_instructions = zip(generate_normal_data(INSTR_AMOUNT, MEAN, SD, U_min, U_max),
              # insert_instructions = zip(generate_uniform_data(INSTR_AMOUNT, U_min, U_max),
              # insert_instructions = zip(generate_normal_data_list(INSTR_AMOUNT, setup_amount, MEAN_LIST, SD_LIST),
              # insert_instructions = zip(generate_uniform_data_list(INSTR_AMOUNT, setup_amount),
                                        # itertools.repeat(INSERT))
  
  
              # GET
              # get_instructions = zip(generate_normal_data_list(INSTR_AMOUNT, setup_amount, MEAN_LIST, SD_LIST),
              # get_instructions = zip(generate_uniform_data_list(INSTR_AMOUNT, setup_amount),
                                        # itertools.repeat(GET))
  
  
              # SET
              # set_instructions = zip(generate_normal_data_list(INSTR_AMOUNT, setup_amount, MEAN_LIST, SD_LIST),
              # set_instructions = zip(generate_uniform_data_list(INSTR_AMOUNT, setup_amount),
                                        # itertools.repeat(SET))
  
  
  
  
              # comment any of these to not include them
              # and update the instruction count variable
              post_instr = []
  
              post_instr.extend(is_member_instructions)
              # post_instr.extend(insert_instructions)
              # post_instr.extend(delete_set_instructions)
  
  
              # ====== LIST
              # post_instr.extend(set_instructions)
              # post_instr.extend(get_instructions)
  
              # post_instr = [None]*STATIC_INSTR
              # post_instr[::2] = list(insert_instructions)
              # post_instr[1::2] = list(delete_list_instructions)
  
  
              random.shuffle(post_instr) # TODO: comment out in add-remove function
              instructions = []
              instructions.extend(post_instr)
              instruction_numbers = [num for num, _ in instructions]
              instructions_ops = [op for _, op in instructions]
              assert len(instructions) == STATIC_INSTR
  
              # SETS
              write_to_file(f'set-normal/query-n-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
              # write_to_file(f'set-normal/no-remove-n-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
              # write_to_file(f'set-normal/n-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
  
              # write_to_file(f'set-uniform/query-u-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
              # write_to_file(f'set-uniform/no-remove-u-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
              # write_to_file(f'set-uniform/u-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
  
  
              # LISTS
              # write_to_file(f'list-normal/get-set-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
              # write_to_file(f'list-uniform/get-set-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
              # write_to_file(f'list-normal/add-remove-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
              # write_to_file(f'list-uniform/add-remove-2^{exp}-{file_id}.data', setup_numbers, instructions_ops,
                            instruction_numbers) # ALWAYS KEEP LINE
  