Как разделить комбинации на три категории? - PullRequest
0 голосов
/ 24 марта 2020

У меня есть массив с рецептами из 3 категорий: завтрак, обед и ужин. У каждой из этих категорий есть 10 уникальных рецептов.

$recipes = [
    'breakfast' => [
        0 => [
            'title' => 'eggless waffles',
            'calorie' => 210,
        ],
        1 => [
            'title' => 'blueberry oatmeal',
            'calorie' => 161,
        ],
        ...
     ],
    'lunch' => [9],
    'dinner' => [9]
];

Я бы хотел отсортировать и создать комбинацию из 3 рецептов на каждый день

$days = array_fill(0, 6, [1 => [], 2 => [], 3 => []]);

Каждый рецепт имеет калорийность ie сумма, и каждый последний день должен иметь комбинацию (состоит из 1 завтрака, 1 обеда и 1 ужина) с рецептами, которые были заказаны в зависимости от того, какой комбо из 3 рецептов попадал ближе всего к 500

Например, если день 1 комбинированные рецепты (завтрак, обед и ужин) калорийность ie составила 660, а день 2 был 400. Вполне возможно, что переключение завтрака со дня 2 на день 1 может привести к тому, что оба из них попадут ближе к 500, однако возможно, что день переключения 3 завтрака к 1-му дню и 2-го к 3-му дню также могут приблизить все 3 к 500.

Таким образом, в 1, 2, 3, 4, 5, 6 и 7 дни должно быть 3 рецепта ( завтрак, обед и ужин)

$final = [
    0 => [
        'breakfast' => [...],
        'lunch' => [...],
        'dinner' => [...],
    ],
     1 => [
        'breakfast' => [...],
        'lunch' => [...],
        'dinner' => [...],
    ],
    2 => [
        'breakfast' => [...],
        'lunch' => [...],
        'dinner' => [...],
    ],
    ...
];

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

Edit 1: Это то, что я получил до сих пор:

class Combinations {

    private $days;

    public function __construct(){
        $this->days = array_fill(1, 7, [1 => [], 2 => [], 3 => []]);
    }

    public function create(){
        $median = 600;

        foreach($this->days as $day => $categories){
            while($this->dayIsIncomplete($day)){
                $recipes = [];
                foreach($categories as $category => $value){
                    $recipes[$category] = $this->getRandomRecipe($category);
                }

                // add random meals to first day
                if($day === 1){
                    $this->days[$day] = $recipes;
                    continue;
                }

                foreach($recipes as $category => $recipe){
                    foreach($this->days as $dayKey => $mealsArray){
                        $originalMacros = $this->totalMacros($mealsArray);

                        // remove $recipe category from mealsArray, and merge it ($recipe)
                        $filteredMacros = $this->totalMacros(array_merge([$recipe], array_filter($mealsArray, function($key) use($category){
                            return $key !== $category;
                        }, ARRAY_FILTER_USE_KEY)));

                        // if original is not closer to median
                        if(($originalMacros - $median) * ($originalMacros - $median) < ($filteredMacros - $median) * ($filteredMacros - $median)){
                            // flip current recipes
                            // switch D2B ($recipe) with D1B
                        }
                    }
                }
            }
        }
    }

    public function getRandomRecipe(int $category){
        $recipes = []

        if($category === 1){
            $recipes[] = ['id' => 1, 'calorie' => 310];
            $recipes[] = ['id' => 2, 'calorie' => 360];
            $recipes[] = ['id' => 3, 'calorie' => 450];
            $recipes[] = ['id' => 4, 'calorie' => 330];
            $recipes[] = ['id' => 5, 'calorie' => 220];
            $recipes[] = ['id' => 6, 'calorie' => 390];
            $recipes[] = ['id' => 7, 'calorie' => 400];
            $recipes[] = ['id' => 8, 'calorie' => 320];
            $recipes[] = ['id' => 9, 'calorie' => 460];
        }

        if($category === 2){
            $recipes[] = ['id' => 10, 'calorie' => 420];
            $recipes[] = ['id' => 11, 'calorie' => 360];
            $recipes[] = ['id' => 12, 'calorie' => 450];
            $recipes[] = ['id' => 13, 'calorie' => 310];
            $recipes[] = ['id' => 14, 'calorie' => 320];
            $recipes[] = ['id' => 15, 'calorie' => 490];
            $recipes[] = ['id' => 16, 'calorie' => 440];
            $recipes[] = ['id' => 17, 'calorie' => 520];
            $recipes[] = ['id' => 18, 'calorie' => 560];
        }

        if($category === 3){
            $recipes[] = ['id' => 19, 'calorie' => 510];
            $recipes[] = ['id' => 20, 'calorie' => 660];
            $recipes[] = ['id' => 21, 'calorie' => 750];
            $recipes[] = ['id' => 22, 'calorie' => 610];
            $recipes[] = ['id' => 23, 'calorie' => 580];
            $recipes[] = ['id' => 24, 'calorie' => 690];
            $recipes[] = ['id' => 25, 'calorie' => 710];
            $recipes[] = ['id' => 26, 'calorie' => 620];
            $recipes[] = ['id' => 27, 'calorie' => 730];
        }

        return $recipes[array_rand($recipes)];
    }

    public function dayIsIncomplete($day){
       return !empty($this->days[$day][1]) && !empty($this->days[$day][2]) && !empty($this->days[$day][3]);
    }

    public function totalMacros($array){
        $total = 0;
        foreach ($array as $key => $value) {
            $total += $value['calorie'];
        }
        return $total / 2;
    }
}

Edit 2:

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

Редактировать 3:

Спасибо всем, что нашли время помочь, я не забыл об ответах. Мне пришлось отложить это на некоторое время, однако достаточно скоро я доберусь до него, и принятый ответ получит мои оставшиеся 300 щедрых.

Ответы [ 3 ]

2 голосов
/ 28 марта 2020

Итак, я проверил генетический c алгоритм, и он работает. Я использовал Jenetics , библиотеку Java (это не PHP, извините, но PHP не подходит для тяжелых вычислений в любом случае).

Я принимал 1400 калорий в день target.

Минимизируемая функция - это среднеквадратическая ошибка .

. Вот код:

import java.util.ArrayList;
import io.jenetics.*;
import io.jenetics.engine.*;
import io.jenetics.util.*;

public class Recipes
{
    private static final int TARGET = 1400;
    private static final int DAYS = 7;

    private static class Recipe
    {
        public int id;
        public int calories;

        public Recipe(int id, int calories)
        {
            this.id = id;
            this.calories = calories;
        }
    }

    private static ISeq<Recipe> getSeq(int[] ids, int[] calories)
    {
        ArrayList<Recipe> list = new ArrayList<>();
        for(int i=0;i<ids.length;i++)
            list.add(new Recipe(ids[i], calories[i]));
        return ISeq.of(list);
    }

    private static double meanSquareError(Genotype<EnumGene<Recipe>> gt)
    {
        int err = 0;
        for(int d=0;d<DAYS;d++)
        {
            int calories = 0;
            for(int m=0;m<3;m++)
                calories += gt.get(m).get(d).allele().calories;
            err += (calories-TARGET)*(calories-TARGET);
        }
        return err / (double)DAYS;
    }

    public static void main(String[] args)
    {
        ISeq<Recipe> recipes1 = getSeq(new int[]{ 1,  2,  3,  4,  5,  6,  7,  8,  9}, new int[]{310, 360, 450, 330, 220, 390, 400, 320, 460});
        ISeq<Recipe> recipes2 = getSeq(new int[]{10, 11, 12, 13, 14, 15, 16, 17, 18}, new int[]{420, 360, 450, 310, 320, 490, 440, 520, 560});
        ISeq<Recipe> recipes3 = getSeq(new int[]{19, 20, 21, 22, 23, 24, 25, 26, 27}, new int[]{510, 660, 750, 610, 580, 690, 710, 620, 730});

        Factory<Genotype<EnumGene<Recipe>>> gtf = Genotype.of(
            PermutationChromosome.of(recipes1, DAYS),
            PermutationChromosome.of(recipes2, DAYS),
            PermutationChromosome.of(recipes3, DAYS)
        );

        Engine<EnumGene<Recipe>, Double> engine = Engine
            .builder(Recipes::meanSquareError, gtf)
            .optimize(Optimize.MINIMUM)
            .populationSize(50)
            .alterers(new SwapMutator<>(0.2), new PartiallyMatchedCrossover<>(0.2), new Mutator<>(0.01))
            .build();

        Phenotype<EnumGene<Recipe>, Double> result = engine.stream()
            .limit(20000)
            .collect(EvolutionResult.toBestPhenotype());

        for(int m=0;m<3;m++)
        {
            for(int d=0;d<DAYS;d++)
            {
                Recipe r = result.genotype().get(m).get(d).allele();
                System.out.print(String.format("%2d (%d)  ", r.id, r.calories));
            }
            System.out.println();
        }
        System.out.println("MSE = " + result.fitness());
    }
}

Geneti c алгоритм не определен c, поэтому каждый раз дает другой результат. Лучшее решение, которое я мог бы получить, это:

 3 (450)   4 (330)   5 (220)   2 (360)   7 (400)   1 (310)   8 (320)
16 (440)  15 (490)  17 (520)  10 (420)  13 (310)  11 (360)  14 (320)
19 (510)  23 (580)  20 (660)  26 (620)  24 (690)  27 (730)  21 (750)

MSE = 14.285714

Это почти идеально (все дни на 1400 калорий, кроме воскресенья с 1390).

1 голос
/ 28 марта 2020

У вас есть:

  • 10 завтраков
  • 10 обедов
  • 10 обедов

Их комбинации 10x10x10 = 1000 рецептов.

Часть 1

Рассчитайте эти рецепты и их общее количество калорий.

Из общего количества калорий каждого рецепта рассчитайте абсолютное отличие от ежедневной цели калорий :

AbsoluteDifference = Abs(calories - 500)

и того, из какого завтрака, обеда и ужина он состоит.

Итак, теперь у вас есть список:

| Recipes   | AbsDiff | Breakfast | Lunch | Dinner |
| recipe 1  |  140    |  1        |   7   |  4 
| recipe 2  |  135    |  4        |   8   |  3
| recipe 3  |  210    |  7        |   9   |  10
 ... 
| recipe 1000 | 170   |  5        |   1   |  9

Часть 2

Это сложная часть, так как вычисление всех комбинаций из 7 рецептов займет слишком много времени.

Лучшая комбинация - та, которая имеет всего absDiff его рецептов минимум :

MIN(AbsDiff(recipe1) + AbsDiff(recipe2) + AbsDiff(recipe7))

Алгоритм

Идея состоит в том, чтобы рассчитать только несколько комбинаций из 7 различных рецептов.

Начальный шаг

  • Сделайте предположение о том, «сколько может быть минимум», например, скажем, вы думаете, что это похоже на 350 калорий.

Используя эту догадку , вы можете попытаться вычислить все комбинации из 7 рецептов, которые имеют TotalCaloriesDiff <350. </p>

На основе:

В наборе из n положительных чисел, сумма которых не превышает S, хотя бы одно из них будет меньше S, деленного на n (S / n) .

В этом случае S = 350 и n = 7, тогда хотя бы один рецепт должен иметь AbsDiff <350/7 = 50. </p>

Итак, вы можете попытаться вычислить комбинации из 7 рецептов с меньшим общим отличием к предположению.

Шаги

  • Получите рецепты с AbsDiff (recipe1) <350/7 </li>
  • Для каждого рецепта1, найденного выше , получите следующие рецепты , которые имеют AbsDiff (recipe2) <(350 - AbsDiff (recipe1)) / 6, и они <strong>не делят завтрак, обед или ужин с recipe1.
  • продолжайте, пока не получите комбинации из 7 рецептов.
  • выберите комбинацию с самым низким TotalCaloriesDiff

Если вы не нашли никаких результатов, основанных на вашем предположении, вы повышаете предположение, например, 350 + 50 = 400.

Вот мой ответ на подобную проблему .

0 голосов
/ 24 марта 2020

Я думаю, что сначала вы должны сделать комбинацию блюд, где калорийность ie должна быть близка к 500, чтобы они были объединены, например:

$arr = [
    0 => [
        'breakfast' => 160
        'lunch' => 160
        'dinner' => 180
    ],
...

Вы должны перестроить массив как $breakfast = ['1' => 130, '2' => 150, '3' => 160, '4' => 170, '5' => 120, '6' => 100, '7' => 130] и и др c. Может быть, попробуйте сравнить массивы, такие как

$final = ['1' => 152, '2' => 235, '3' => 521, '4' => 343, ... ];

И затем вы можете получить каждое значение из $ arr ->

$final = ['1' => ['breakfast' => '1', 'lunch' => '5', 'dinner' => '2'], ...];

Я думаю, что вы можете изменить эту логику c, как хотите. Желаем удачи

...