Photoluminescence Math, Machines, and Music

Comparing discount plans

26 July 2021 Contentious comments Life hack Optimization

Raven wants to buy all products with respective price \(p\)\(\;\in\;\)\(\mathbb{P}\), and respective discount thresholds \(s \vphantom{ s}_{n}\) so that when the sum exceeds \(s \vphantom{ s}_{n}\), she gets discount \(d \vphantom{ d}_{n}\) , \(n\)\(\;=\;\)\(1\)\(,\,\)\(\,\dotsc\,\)\(,\,\)\(N\). She considers to buy one optional item with price \(q\)\(\;\in\;\)\(\mathbb{Q}\). She has also a limit of budget \(B\). How does she make the best choice?

Yuchun asked me the question, and the confusion is how we elucidate our everyday instinct. If Raven simply minimizes the total cost \(T\), she most likely wouldn’t buy any optional item. Perhaps she should introduce a penalty function \(\varphi\), which trades the total eligible discount with \(q\) . For instance, she may set \(\varphi\)\(\;\equiv\;\)\(\surd\!\)\(q\), and minimize \(T\)\(\,+\,\)\(\surd\!\)\(q\).

Alternatively, Raven may minimize the price not discounted, and when there is a tie, she minimizes the total price. Certainly, this isn’t perfect either, because it may be absurd to see it a better bargain to buy many option items to get more discount. (But people do that all the time.) In other words, we have the assumptions:

\(\mathbb{P} \vphantom{ \mathbb{P}}_{0}\)\(,\,\)\(\mathbb{P} \vphantom{ \mathbb{P}}_{1}\)\(,\,\)\(\,\dotsc\,\)\(,\,\)\(\mathbb{P} \vphantom{ \mathbb{P}}_{N}\)\(\,\mathsf{are}\,\)\(\,\mathsf{mutually}\,\)\(\,\mathsf{exclusive}\,\)\(\,\mathsf{and}\,\)\(\,\mathsf{collectively}\,\)\(\,\mathsf{exhaustive}\,\)\(\,\mathsf{of}\,\)\(\mathbb{P}\)\(;\,\)

\(q\)\(\;\in\;\)\(\mathbb{Q}\)\(;\,\)

\(P \vphantom{ P}_{n}\)\(\;\equiv\;\)\( \displaystyle\sum\limits_{\bar{p} \;\in\; \mathbb{P} \vphantom{ \mathbb{P}}_{n}}\)\(\bar{p}\)\(,\,\)\(\;\mathtt{for}\;\)\(n\)\(\;=\;\)\(1\)\(,\,\)\(\,\dotsc\,\)\(,\,\)\(N\)\(;\,\)

\(T\)\(\;=\;\)\( \displaystyle\sum\limits^{N}_{\bar{n} \;=\; 1}\)\(P \vphantom{ P}_{\bar{n}}\)\(\,-\,\)\( \displaystyle\sum\limits_{\bar{d} \;\in\; \mathbb{D}}\)\(\bar{d}\)\(\,+\,\)\(q\)\(;\,\)

\(s \vphantom{ s}_{n}\)\(\;\leq\;\)\(P \vphantom{ P}_{n}\)\(,\,\)\(\;\mathtt{for}\;\)\(d \vphantom{ d}_{n}\)\(\;\in\;\)\(\mathbb{D}\)\(;\,\)

\(\mathbb{P} \vphantom{ \mathbb{P}}_{0}\)\(,\,\)\(\mathbb{P} \vphantom{ \mathbb{P}}_{1}\)\(,\,\)\(\,\dotsc\,\)\(,\,\)\(\mathbb{P} \vphantom{ \mathbb{P}}_{N}\)\(,\,\)\(q\)\(\;=\;\)\(\underset{\bar{\mathbb{P}} \vphantom{ \bar{\mathbb{P}}}_{0} ,\, \,\dotsc\, ,\, \bar{\mathbb{P}} \vphantom{ \bar{\mathbb{P}}}_{N} ,\, \bar{q}}{ \mathrm{argmin}}\)\(P \vphantom{ P}_{0}\)\(\left[\vphantom{\bar{\mathbb{P}} \vphantom{ \bar{\mathbb{P}}}_{0} ,\, \,\dotsc\, ,\, \bar{\mathbb{P}} \vphantom{ \bar{\mathbb{P}}}_{N}}\right.\)\(\bar{\mathbb{P}} \vphantom{ \bar{\mathbb{P}}}_{0}\)\(,\,\)\(\,\dotsc\,\)\(,\,\)\(\bar{\mathbb{P}} \vphantom{ \bar{\mathbb{P}}}_{N}\)\(\left.\vphantom{\bar{\mathbb{P}} \vphantom{ \bar{\mathbb{P}}}_{0} ,\, \,\dotsc\, ,\, \bar{\mathbb{P}} \vphantom{ \bar{\mathbb{P}}}_{N}}\right]\)\(;\,\)

\(q\)\(\;=\;\)\(\underset{\bar{q}}{ \mathrm{argmin}}\)\(T\)\(\left[\vphantom{\bar{q}}\right.\)\(\bar{q}\)\(\left.\vphantom{\bar{q}}\right]\)\(;\,\)

Taking this view, we implement a brute force approach below.

#! /usr/bin/env python3

import copy

 

def compare_discount(**data):

  budget = data["budget"]

  many_discount = data["many_discount"]

  many_option = data["many_option"]

  many_core = data["many_core"]

  many_price = many_core + many_option

  separation = len(many_discount) + 1

  partition = divide_optional(separation, len(many_core), len(many_option))

  if not partition:

    print("Error: no combination found")

    quit()

  print("number of partition:", len(partition))

 

  answer = None

  total_held = sum(many_core + many_option)

  original_held = sum(many_core + many_option)

  for method in partition:

    total = 0

    total += get_total(many_price, method[0])

    for head in range(1, separation):

      threshold = many_discount[head - 1][0]

      discount = many_discount[head - 1][1]

      total_part = get_total(many_price, method[head])

      if not (threshold < total_part): discount = 0

      total += total_part - discount

    if (budget < total): continue

 

    original = get_total(many_price, method[0])

    if (original_held > original):

      original_held = original

      answer = method

    elif (original_held == original):

      if (total_held > total):

        total_held = total

        answer = method

  if not answer:

    print("Error: no solution found")

    quit()

 

  print("group not discounted:")

  for item in answer[0]:

    print(many_price[item], ',', sep = '')

  for head in range(len(many_discount)):

    group = answer[head + 1]

    if not group: continue

    discount = many_discount[head][1]

    print("group discounted ", discount, ":", sep = '')

    for item in group: print(many_price[item], ", ", sep = '', end = '')

    print('')

  print("total price:", total_held)

 

def divide_optional(separation, number_core, number_option):

  partial = divide_core(separation, number_core)

  if (number_option == 0): return partial

  partition = []

  for option in range(number_option):

    for method in partial:

      for index in range(separation):

        #print(number_core + option)

        hold = copy.deepcopy(method)

        hold[index].add(number_core + option)

        partition.append(hold)

  return partition

 

def divide_core(separation, number_core):

  if (number_core == 0): return divide_empty(separation)

  partial = divide_core(separation, number_core - 1)

  partition = []

  for method in partial:

    for index in range(separation):

      hold = copy.deepcopy(method)

      hold[index].add(number_core - 1)

      partition.append(hold)

  return partition

 

def divide_empty(separation):

  method = []

  for _ in range(separation): method.append(set({}))

  partition = [method]

  return partition

 

def get_total(many_price, group):

  amount = 0

  for index in group: amount += many_price[index]

  return amount

 

# # # # # # # # # # # # # # # #

 

many_discount = [(1000, 100), (2000, 300), (4000, 800)]

many_core = (300, 500, 1000, 1200, 1800)

many_option = (600, 800, 1000)

budget = 5000

plan = compare_discount(

  budget = budget,

  many_discount = many_discount,

  many_option = many_option,

  many_core = many_core,

)

 

# number of partition: 12288

# group not discounted:

# group discounted 100:

# 300, 1000,

# group discounted 800:

# 500, 1200, 1800, 600,

# total price: 4500

  ❦   ❦   ❦

❧ July 26, 2021