Team 1

Project 1

The measuring cup problem

問題簡介

今天有 \(n\) 個量杯。你想要用這幾個量杯,

經過下面給定的三種操作,來讓最後某個量杯中有 \(t\) 的水量。

  1. 把量杯裝滿水

  2. 量杯中的水倒到另外一個量杯中

  3. 把量杯中的水倒光

今天,我們將使用 廣度優先搜尋演算法

(Breadth-First Search, BFS)

來解決這個問題

就是窮舉法

另外,要讓程式紀錄一種組合的資訊

建立一個陣列來存初始組合狀態

以及由這個初始狀態窮舉出的組合狀態。

# iterate data
data = list()
# the data of a combination
class Combination:
    def __init__(self, init_father_index: int, init_step: int, init_list: list):
        self.father_index = init_father_index
        self.step = init_step
        self.cups_volume = init_list

\(self.father\_index\)

用於步驟回朔,註明:「這個組合是哪個組合的下一步」

其中哪個組合(父節點)在 \(data\) 中的位置

\(self.step\)

註明:這個組合的最少步驟數

\(self.cups\_volume\)

註明:這個組合中,每個量杯各自的水量

data = [comb0]

# initialize first combination
data.append(Combination(0, 0, [0] * n))

最初的資料

(父節點為自己、步驟為0、各量杯水量都為零)

方法一:倒滿量杯

# try 1: fill cup to maximum volume
for cup_index in range(n):
  # if the cup is already full, then skip this step
  if data[cur_p].cups_volume[cup_index] == cup_max[cup_index]:
    continue

  new_comb = data[cur_p].cups_volume.copy()

  # fill the cup of index 'cup_index' to its maximum volume
  new_comb[cup_index] = cup_max[cup_index]

  find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
  if find:
      break

方法二:量杯互倒

# try 2: pour water from cup a to b
for a_index in range(n):
  for b_index in range(n):
    # situation 1: if a-cup is b-cup, then skip this step
    # situation 2: if a-cup is empty, then skip this step
    # situation 3: if b-cup is full, then skip this step
      if a_index == b_index \
      or data[cur_p].cups_volume[a_index] == 0 \
      or data[cur_p].cups_volume[b_index] == cup_max[b_index]:
        continue

      new_comb = data[cur_p].cups_volume.copy()

      # criterion: delta value <= a-cup
      # situation 1: if a-cup currently has more water than b_max - b-cup,
      # then the delta value is b_max - b-cup
      # situation 2: if a-cup currently has less water than b_max - b-cup,
      # then the delta value is just a-cup itself
      delta_volume = min(cup_max[b_index] - new_comb[b_index], new_comb[a_index])
      new_comb[a_index] -= delta_volume
      new_comb[b_index] += delta_volume

      find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
      if find:
        break
   if find:
     break

方法三:清空量杯

# try 3: pour the water out of the cup
for cup_index in range(n):
  # if the cup is empty, then skip this step
  if data[cur_p].cups_volume[cup_index] == 0:
    continue

  new_comb = data[cur_p].cups_volume.copy()

  # pour the water out of the cup of index 'cup_index'
  new_comb[cup_index] = 0

  find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
  if find:
    break

假設 \(comb1\) 是 \(comb0\) 的

下一步驟其中的一種可能組合

先檢查想要的水量有沒有出現在

這個組合的各量杯的水量中。

如果在,那就直接將答案設為 \(comb1\),

將其加進 \(data\) 中,並跳過之後的所有窮舉。

comb1

\(\downarrow\)

data

\(\downarrow\)

data = [comb0, comb1]

# the needed volume is already in the new combination
if t in detect_comb:
  ans = Combination(father_ind, needed_step, new_comb)
  return True

但,如果它不是答案,

但也並非重複組合(不在 \(data\) 中),

那就把它加進 \(data\) 的後面 ;

不然就將它捨去。

comb1

\(\downarrow\)

comb1

\(\downarrow\)

data

\(\downarrow\)

data = [comb0, comb1]

# the combination is already in the data
if detect_comb in [cups_data.cups_volume for cups_data in data]:
  return False

# if two former test failed, that means this is a new combination
data.append(Combination(father_ind, needed_step, new_comb))
return False

要找到甚麼時候呢?

  • 直接找到答案

  • 找完所有的窮舉組合

while cur_p < len(data):

\(cur\_p\) : 目前所在位置

讓我們複習一下整個流程!

  1. 輸入初始數值
  2. 開始對三種倒水方法窮舉
    1. 如果找到答案,就設為正確答案,跳出迴圈
    2. 如果重複了,就捨去
    3. 如果是新的,就把它放到queue後面,等待在稍後窮舉它的下一步
  3. 如果找到答案了,就印出來,並且循著父節點回朔步驟
  4. 如果找完所有可能的組合卻沒有答案,就跳出程式

扣得

print('--> Programed and run by phantom0174 <--')

# cup count
n = int(input('請輸入杯子的個數><\n'))

# needed volume
t = int(input('請輸入你想要的水量><\n'))

# cup max volume
cup_max = list(input('請輸入各杯子的最大容量>< (要以空格隔開)\n').split(' '))
cup_max = list(map(int, cup_max))

# current pop index
cur_p = int(0)


# the data of a combination
class Combination:
    def __init__(self, init_father_index: int, init_step: int, init_list: list):
        self.father_index = init_father_index
        self.step = init_step
        self.cups_volume = init_list


# answer object
ans = Combination(0, 0, list())


# iterate data
data = list()

# initialize first combination
data.append(Combination(0, 0, [0] * n))


# the function for status checking
# if we find the answer, return True; otherwise, return False
def check_and_manipulate(father_ind, needed_step, detect_comb: list) -> bool:
    global data, ans

    # the needed volume is already in the new combination
    if t in detect_comb:
        ans = Combination(father_ind, needed_step, new_comb)
        return True

    # the combination is already in the data
    if detect_comb in [cups_data.cups_volume for cups_data in data]:
        return False

    # if two former test failed, that means this is a new combination
    data.append(Combination(father_ind, needed_step, new_comb))
    return False


# main program


# initialize find status, which is equivalent to 'flag'
find = bool(False)
while cur_p < len(data):
    print(f'Searching... ({cur_p}/{len(data) - 1})')

    # try 1: fill cup to maximum volume
    for cup_index in range(n):
        # if the cup is already full, then skip this step
        if data[cur_p].cups_volume[cup_index] == cup_max[cup_index]:
            continue

        new_comb = data[cur_p].cups_volume.copy()

        # fill the cup of index 'cup_index' to its maximum volume
        new_comb[cup_index] = cup_max[cup_index]

        find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
        if find:
            break
    if find:
        break

    # try 2: pour water from cup a to b
    for a_index in range(n):
        for b_index in range(n):
            # situation 1: if a-cup is b-cup, then skip this step
            # situation 2: if a-cup is empty, then skip this step
            # situation 3: if b-cup is full, then skip this step
            if a_index == b_index \
                    or data[cur_p].cups_volume[a_index] == 0 \
                    or data[cur_p].cups_volume[b_index] == cup_max[b_index]:
                continue

            new_comb = data[cur_p].cups_volume.copy()

            # criterion: delta value <= a-cup
            # situation 1: if a-cup currently has more water than b_max - b-cup,
            # then the delta value is b_max - b-cup
            # situation 2: if a-cup currently has less water than b_max - b-cup,
            # then the delta value is just a-cup itself
            delta_volume = min(cup_max[b_index] - new_comb[b_index], new_comb[a_index])
            new_comb[a_index] -= delta_volume
            new_comb[b_index] += delta_volume

            find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
            if find:
                break
        if find:
            break
    if find:
        break

    # try 3: pour the water out of the cup
    for cup_index in range(n):
        # if the cup is empty, then skip this step
        if data[cur_p].cups_volume[cup_index] == 0:
            continue

        new_comb = data[cur_p].cups_volume.copy()

        # pour the water out of the cup of index 'cup_index'
        new_comb[cup_index] = 0

        find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
        if find:
            break
    if find:
        break

    cur_p += 1

if not find:
    print('不要為難我了qwq, 這是不可能的任務')
else:
    print(f'恭喜!\\^~^ 你只要 {ans.step} 步就可以得到 {t} 的水量!')
    step_list = list()
    step_list.append(ans.cups_volume)

    # initialize current father_index to search
    father_index = ans.father_index

    # get the cups_volume data from father_index, and update the next father_index
    while father_index != 0:
        step_list.append(data[father_index].cups_volume)
        father_index = data[father_index].father_index

    # because we're searching from back, we'll need to reverse the step logs
    step_list.reverse()
    print('以下是步驟!')
    for step in step_list:
        print(step)

謝謝聆聽

第一組 資訊專題報告

By phantom0174

第一組 資訊專題報告

  • 469