2023-06-07 10:07:37 -06:00
|
|
|
import copy
|
2023-06-07 05:49:06 -06:00
|
|
|
from typing import List
|
|
|
|
|
2023-06-07 10:07:37 -06:00
|
|
|
def first_fit_decreasing(input_items: List[List], batch_size: int, filler_items: List=[]) -> List:
|
2023-06-07 05:49:06 -06:00
|
|
|
"""
|
|
|
|
Given as input a list of lists, batch the items so that as much as possible the members of each of the original
|
2023-06-07 10:07:37 -06:00
|
|
|
lists end up in the same batch. Pad out too-short batches by taking items from the filler_items list, if available.
|
2023-06-07 05:49:06 -06:00
|
|
|
|
2023-06-07 10:07:37 -06:00
|
|
|
@return flattened list of all items in input_items and filler_items, arranged such that, as much as possible, items
|
|
|
|
that are in the same input list end up in the same batch.
|
2023-06-07 05:49:06 -06:00
|
|
|
"""
|
|
|
|
|
2023-06-07 10:07:37 -06:00
|
|
|
def sort_by_length(items: List[List]) -> List[List]:
|
|
|
|
return sorted(items, key=lambda x: len(x))
|
2023-06-07 05:49:06 -06:00
|
|
|
|
2023-06-08 12:34:51 -06:00
|
|
|
remaining = input_items
|
2023-06-07 10:07:37 -06:00
|
|
|
output = []
|
2023-06-07 05:49:06 -06:00
|
|
|
while remaining:
|
|
|
|
remaining = sort_by_length(remaining)
|
2023-06-07 10:07:37 -06:00
|
|
|
longest = remaining.pop()
|
|
|
|
if len(longest) == 0:
|
|
|
|
continue
|
|
|
|
if len(longest) >= batch_size:
|
|
|
|
output.append(longest[0:batch_size])
|
|
|
|
del longest[0:batch_size]
|
|
|
|
if len(longest)>0:
|
|
|
|
remaining.append(longest)
|
|
|
|
else:
|
|
|
|
# need to build this chunk by combining multiple
|
2023-06-08 12:34:51 -06:00
|
|
|
combined = longest
|
2023-06-07 10:07:37 -06:00
|
|
|
while True:
|
|
|
|
fill_length = batch_size - len(combined)
|
|
|
|
if fill_length == 0:
|
|
|
|
break
|
|
|
|
|
|
|
|
if len(remaining) == 0 and len(filler_items) == 0:
|
|
|
|
break
|
|
|
|
|
|
|
|
from_filler_bucket = filler_items[0:fill_length]
|
|
|
|
if len(from_filler_bucket) > 0:
|
|
|
|
del filler_items[0:fill_length]
|
|
|
|
combined.extend(from_filler_bucket)
|
|
|
|
continue
|
|
|
|
|
|
|
|
filler = next((r for r in remaining if len(r) <= fill_length), None)
|
|
|
|
if filler is not None:
|
|
|
|
remaining.remove(filler)
|
|
|
|
combined.extend(filler)
|
|
|
|
else:
|
|
|
|
# steal from the next longest
|
|
|
|
next_longest = remaining.pop()
|
|
|
|
combined.extend(next_longest[0:fill_length])
|
|
|
|
del next_longest[0:fill_length]
|
|
|
|
if len(next_longest) > 0:
|
|
|
|
remaining.append(next_longest)
|
|
|
|
output.append(combined)
|
|
|
|
|
|
|
|
output.append(filler_items)
|
|
|
|
return [i for o in output for i in o]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|