Как создать алгоритм, который продолжит выполнение алгоритма до тех пор, пока не добьется успеха в Java

У меня есть пакет (https://github.com/skjolber/3d-bin-container-packing/), который будет упаковывать элементы в контейнер для меня. Однако, если предметов слишком много, например, 1000 рубашек, и только 500 помещаются в самый большой контейнер, который у нас есть, он не будет пытаться упаковать оставшиеся 500, результат просто возвращает нуль и не пытается объединить контейнеры. доступный.

Решаю проблему с упаковкой. Обратите внимание, что это не обязательно должно быть точное решение, поскольку мы всего лишь собираем калькулятор стоимости перевозки. Я наткнулся на пакет, который решает большую часть проблемы. В основном у меня есть 5 контейнеров, которые можно использовать. API получает запрос с продуктами, я собираю размеры и упаковываю их. Моя первоначальная мысль состояла в том, чтобы проверить, был ли пакет успешным, и если да, то добавить его в объект ListpackagingResults, чтобы у меня был список. Но то, как устроен этот пакет, подозреваю, что без потери целостности продуктов он работать не будет.

Вот конструктор службы

public PackingService(List<Product> products) 
{
    containers = SetContainers();
    this.packer = new LargestAreaFitFirstPackager(containers);
    this.products = products;
    PrepareProductsToPack(products);
}

фактический метод упаковки

public List<Container> PackItems()
{
    packedResultContainers = new ArrayList<Container>();
    Container results =  packer.pack(productsToPack);
    return this.packedResultContainers;
}

и, наконец, вот я беру свой список сущностей Product и подготавливаю их к алгоритму упаковки.

    productsToPack = new ArrayList<BoxItem>();
    for(Product product : products)
    {
        for(int i = 1; i < product.getQuantity(); i++)
        {
            productsToPack.add(new BoxItem(new Box(product.getSku(),
                                                    product.getProductDimensions().getRoundedWidth(),
                                                    product.getProductDimensions().getRoundedDepth(),
                                                    product.getProductDimensions().getRoundedHeight(),
                                                    product.getProductDimensions().getRoundedWeight())));
        }
    }

Причина, по которой я запутался, заключается в том, что если первоначальный запрос не упаковывается, мне нужно будет разбить свой список на, 2,3,4 в зависимости от того, сколько элементов есть, и у меня не было бы возможности узнать, сколько списки, которые мне нужно иметь.

Может ли кто-нибудь дать некоторое представление о том, как я могу настроить свой алгоритм, чтобы справиться с этим?

Я также должен заявить, что причина, по которой я делаю это таким образом, заключается в том, что, как только у меня есть список результатов, я делаю запрос API UPS, чтобы собрать способы доставки и их тарифы.

Я обновляю свой ответ для будущих пользователей, вот как я решил проблему с упаковкой мусорного ведра.

public ArrayList<Container> PackItems()
{
    this.packingResults = new ArrayList<Container>();
    productSetsToPack = chopped(productsToPack, getInitBoxCount(productsToPack));
    int hashMapId = 0;
    for(int i = productSetsToPack.size()-1; i >= 0; i--)
    {
        ArrayList<BoxItem> itemsToPack = productSetsToPack.get(i);
        productSetsMap.put(hashMapId, itemsToPack);
        pack(itemsToPack, hashMapId, 5); //pack largest sized boxs
        ++hashMapId;
    }; 

    for (Map.Entry<Integer, Container> entry : packedContainerMap.entrySet()) {
        int containerKey = entry.getKey();
        Container packedContainer = entry.getValue();
        int smallestBoxSize = getSmallestBox(productSetsMap.get(containerKey), Integer.parseInt(packedContainer.getName()));
        packingResults.add(pack(productSetsMap.get(containerKey), smallestBoxSize));
        // ...
    }
    return packingResults;
}

person boo    schedule 16.01.2019    source источник
comment
Я автор связанного проекта github, и я ожидал, что он будет работать как есть. После этого поста было выпущено несколько релизов, не возражаете ли вы «попробовать еще раз», и если нет результата с несколькими блоками, опубликовать (неудачный) модульный тест в системе отслеживания проблем проекта?   -  person ThomasRS    schedule 30.11.2019


Ответы (4)


бу, То, что вы хотите, очень возможно. Это будет довольно сложно, особенно если у вас есть 5 разных размеров упаковки. Я бы предложил использовать рекурсию.

создайте метод, который находит наименьшее количество самых больших упаковок, в которые может поместиться ваш заказ. скажем, он помещается в 4 большие коробки. затем возьмите каждую большую коробку и попытайтесь упаковать ее в коробки поменьше. прогоны будут выглядеть примерно так: ящик 5 — самый большой ящик, а ящик 1 — самый маленький ящик.

box5 - box5 - box5- box5
box5 - box5 - box5- box4
box5 - box5 - box5- box3
box5 - box5 - box5- box2
box5 - box5 - box5- box1

этот пробег поместился бы в 3 больших коробки и 1 маленькую коробку.

Код для поиска самого большого ящика, вероятно, будет циклом while, подобным этому

int boxCount = getInitBoxCount(products);

public int getInitBoxCount(ArrayList<BoxItems> items)
{
    if(productsFit(products,sizeLarge) == null)
    {
        ArrayList<BoxItems> temp1 = new ArrayList<BoxItems>();
        ArrayList<BoxItems> temp2 = new ArrayList<BoxItems>();

        for(int x = 0; x < items.length ; x++)
        {
           if(x > items.length/2)
           {
               temp1.add(items.get(x)) 
           }
           else
           {
               temp2.add(items.get(x))
           }
        }
        return getInitBoxCount(temp1) + getInitBoxCount(temp2);
    }
    return 1;
}

Это будет постоянно делить ваш список BoxItems пополам, пока он не будет соответствовать количеству ящиков.

так что теперь мы определили способ доставки, который будет работать, но также, вероятно, будет иметь много дополнительного места.

Следующие 2 шага заключались бы в создании рекурсивного способа обрезать размер блока, например

// 5 is the largest

public int getSmallestBox(int boxsize,ArrayList<BoxItems> items)
{
    if(productsFit(boxsize -1)!= null)
    {
        return getSmallestBox(boxsize -1, items)
    }
    else
        return boxsize;
}

И это будет самая маленькая коробка, в которую мы можем поместить предметы. Последнее, что вам нужно проверить, это объединение полей. вы можете обнаружить, что у вас есть 4 маленьких ящика, и вы можете объединить их в 2 средних ящика. после того, как вы сможете комбинировать и сжимать блоки, вы сможете отслеживать результаты после каждого запуска. поэтому общий поток программы будет выглядеть примерно так в псевдокоде.

Container[getInitBoxCount()] shipments //create an array of containers with the size of the inital box count 

for(Container c : shipments)
{
    c = new LargeContainer();
}


Container[] check; //create new objects into the check array. dont set one array = to anoter

do
{
    check = shipments //create new objects into the check array. dont set one array = to anoter
    for(Container c: shipments)
        getSmallestBox(5, c);
    combineBoxes(shipments);
}
while(check == shipments)
person caleb baker    schedule 16.01.2019
comment
Большое спасибо, что нашли время объяснить рекурсию. Это имеет смысл, однако я немного смущен парой концепций, которые вы указали в своем ответе. check = shipments какова цель этого и разве ваш комментарий не говорит этого не делать? Я изменил Container[] на ArrayList<Container>, так как у него есть несколько полезных статических методов, которые я могу вызывать. и вместо создания нового LargeContainer я просто добавляю его в список массивов. - person boo; 17.01.2019
comment
@boo во-первых. Мне жаль, что я не понимаю API, который вы используете, и это сделало мой код немного двусмысленным. что я имел в виду под чеком = отгрузки, так это создать клон отгрузок и установить его равным чеку. Я считаю, что у ArrayList может быть метод clone(), который выполнит это. Так же придется побороться с чеком == отгрузки то что вы пытаетесь сравнить есть если у ArrayLists одинаковый размер и количество контейнеров - person caleb baker; 18.01.2019
comment
Это может быть другой вопрос, но в getInitBoxCount() я возвращаю право int, но на данный момент он не отслеживает, какие продукты находятся в контейнере. Я думал добавить arraylist<arraylist<boxitem>>, но если я это сделаю, то каждый раз я буду дублировать элементы, которые были упакованы, если это не удастся. Нужно ли мне создавать новый объект и удалять элементы из списка массивов параметров? - person boo; 18.01.2019
comment
@caleb_baker См. обновленный вопрос о результатах вашей помощи. Еще раз спасибо. - person boo; 21.01.2019

Посмотрите, может ли повторная попытка Spring быть полезной. Чтобы в методе были повторы и другая логика, где вы сомневаетесь, что не получите свой первый список и так далее.

person LNP    schedule 16.01.2019
comment
Разве это не будет просто повторять один и тот же код снова и снова? Если упаковка не удалась (она не выдает исключение, а вместо этого просто возвращает ноль), тогда мне нужно будет сказать, что я разделил мой список продуктов на второй список продуктов, затем упаковал оба, если оба терпят неудачу, затем попробуйте 3, и это похоже на то, что это может теоретически никогда не закончится, поэтому мне нужно будет как-то динамически создавать новый объект для каждой неудачной попытки. Это вообще возможно? - person boo; 16.01.2019
comment
Вы могли вызвать другой метод при сбое с помощью @Recover - person LNP; 16.01.2019

Вы можете сгенерировать все возможные подмножества вашей коллекции элементов, а затем перебирать их в отсортированном порядке (начиная с наибольшего и уменьшающегося размера), пока не добьетесь успеха.

Это, конечно, не очень хорошо масштабируется. Id также генерирует подмножества, используя индексы списка вместо ссылок (добавьте все элементы в список, создайте набор чисел от 0 до list.size(), затем сгенерируйте все подмножества этого набора.)

person M. Reyes    schedule 16.01.2019

В текущей версии это работает:

@Test
void testIssue158() {
    List<Container> containers = new ArrayList<>();
    containers.add(new Container("X",10, 10, 5, 1000));

    List<BoxItem> products = new ArrayList<>();
    for(int i = 0; i < 1000; i++) {
        products.add(new BoxItem(new Box(Integer.toString(i), 1, 1, 1, 1)));
    }

    LargestAreaFitFirstPackager packager = new LargestAreaFitFirstPackager(containers, false, true, true);
    List<Container> fits = packager.packList(products, 50, Long.MAX_VALUE);
    assertNotNull(fits);
    assertEquals(fits.size(), 2);
}

Надеюсь, это означает отсутствие специального обходного пути для совместимости с несколькими контейнерами!

person ThomasRS    schedule 30.11.2019