I have an array with recipes from 3 categories, breakfast, lunch and dinner. Each of these categories, have 10 unique recipes.
$recipes = [
'breakfast' => [
0 => [
'title' => 'eggless waffles',
'calorie' => 210,
],
1 => [
'title' => 'blueberry oatmeal',
'calorie' => 161,
],
...
],
'lunch' => [9],
'dinner' => [9]
];
I'd like to sort and create a combination of 3 recipes for each day
$days = array_fill(0, 6, [1 => [], 2 => [], 3 => []]);
Each recipe has a calorie amount, and each final day should have a combination (consists of 1 breakfast, 1 lunch and 1 dinner) with recipes that was ordered by whichever 3 recipe combo hit closest to 500
For example, if day 1 combined recipes (breakfast, lunch and dinner) calorie totaled 660, and day 2 was 400. It's possible that switching breakfast from day 2, to day 1 might make both of them hit closest to 500, however it's possible that switching day 3 breakfast to day 1, and day 2 to day 3 might make all 3 hit closer to 500 as well.
So day 1, 2, 3, 4, 5, 6, and 7 should have 3 recipes (breakfast, lunch and dinner)
$final = [
0 => [
'breakfast' => [...],
'lunch' => [...],
'dinner' => [...],
],
1 => [
'breakfast' => [...],
'lunch' => [...],
'dinner' => [...],
],
2 => [
'breakfast' => [...],
'lunch' => [...],
'dinner' => [...],
],
...
];
It's been days since I've reached an impasse, and I cannot figure out how to go about sorting these arrays into a combination of 3 for each day. (I know I'm not providing a lot of code to go off of)
Edit 1:
This is what I've got so far:
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:
I'm trying to figure out what algorithm best fits to sort this issue out. I think using a bipartite matching (maximum) algorithm might be what I need.
Edit 3:
Thank you all for taking the time to help, I haven't forgotten about the answers. I had to put this aside for the time being, however soon enough I'll get to it, and the accepted answer will get my remaining 300 bounty.
So I tested a genetic algorithm and it works. I used Jenetics, a Java library (it's not PHP, sorry, but PHP is not suited to heavy computations anyway).
I took 1400 calories as the daily target.
The function to be minimized is the mean squared error.
Here's the code:
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());
}
}
A genetic algorithm is non-deterministic so it gives a different result each time. The best solution I could get is:
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
It's almost perfect (all days are at 1400 calories except Sunday which has 1390).
You have:
10 breakfasts
10 lunches
10 dinners
The combinations of these are 10x10x10 = 1000 recipes.
Part 1
Calculate these recipes and their total calories each.
From each recipes `s total calories, calculate the absolute difference from the daily calories goal:
AbsoluteDifference = Abs(calories - 500)
and which breakfast, lunch and dinner it consists of.
So now e.g. you have a list:
| 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
Part 2
This is the difficult part, by calculating all combinations of 7 recipes is gonna take too long.
The best combination is the one that has total of absDiff of its recipes the minimum:
MIN(AbsDiff(recipe1) + AbsDiff(recipe2) + AbsDiff(recipe7))
Algorithm
The idea is to calculate only a few combinations of 7 different recipes.
Initial Step
Make a guess of "how much the minimum could be about", e.g. say you think it is likely less than 350 calories.
Using this guess, you can try to calculate all the combinations of 7 recipes that have TotalCaloriesDiff < 350.
Based on:
In a collection of n positive numbers that sum up to or less than S, at least one of them will be less than S divided by n (S/n).
In this case S=350 and n=7, then at least one recipe should have AbsDiff < 350/7 = 50.
So, you could try to calculate combinations of 7 recipes with lower total differences to the guess.
Steps
Get the recipes with AbsDiff(recipe1) < 350 / 7
For each recipe1 found above, get the next recipes that have AbsDiff(recipe2) < (350 - AbsDiff(recipe1)) / 6 and they do not share any breakfast, lunch or dinner with recipe1.
continue till you get combinations of 7 recipes.
select the combination with lowest TotalCaloriesDiff
If you do not find any results, based on your guess, you raise the guess, e.g. 350 + 50 = 400.
Here is my answer to a similar problem.
I think first you should make a combination of dishes, where calorie should be near to 500, to make them unice, like:
$arr = [
0 => [
'breakfast' => 160
'lunch' => 160
'dinner' => 180
],
...
You should rebuild array like $breakfast = ['1' => 130, '2' => 150, '3' => 160, '4' => 170, '5' => 120, '6' => 100, '7' => 130] and etc.
Maybe try to compare arrays like
$final = ['1' => 152, '2' => 235, '3' => 521, '4' => 343, ... ];
And then you can grab each value from $arr ->
$final = ['1' => ['breakfast' => '1', 'lunch' => '5', 'dinner' => '2'], ...];
I think you can modify this logic as you want. Best of luck
Example: The shop has 3 different payment amount of promotion, if buy 10 get 1 credit and 20 for 2 credit...These promotions I set on config like this:
reward
0
10 amount
1 reward
1
20 amount
2 reward
2
30 amount
3 reward
So, how can I loop or foreach these can determine my purchased amount?
note: I am new to programming so please guide me to my homework.
I expect the output should:
if amount=20
then
array[0] true
array[1] true
array[3] wrong(require amount=30)
then get the final credit i get
so, all you need is to loop through your config and find if your curr amount is less then config amount:
$config = [
[
'amount' => 10,
'reward' => 1,
],
[
'amount' => 20,
'reward' => 2,
],
[
'amount' => 30,
'reward' => 3,
],
];
to loop through you can use foreach
when the condition is true there is no reason to loop more - so just break
try yourself before opening the demo
I am cracking my brain and can't find a good solution for my problem. I am trying to design a system that I can use for batch picking in our order system.
The point is that from a set of orders I want to pick 6 orders that are most equal to each other. In our warehouse most orders are them so we can safe a lot of time by picking some orders at the same time.
Assume I have the following array:
<?php
$data = [
156 => [
1,
2,
7,
9,
],
332 => [
3,
10,
6
],
456 => [
1,
],
765 => [
7,
2,
10,
],
234 => [
1,
9,
3,
6,
],
191 => [
7,
],
189 => [
7,
6,
3,
],
430 => [
10,
9,
1,
],
482 => [
1,
2,
7,
],
765 => [
1,
5,
9,
]
];
?>
The array key is the order id, and the values are the product ID's it contains. If I want to pick the top 3 orders which look at much like each other, where do I start?
Any help would be much appreciated!
1. Step
Sort productId inside order (ASC)
2. Step
In loop check difference (array_diff) in each order to each other.
Create array with defference. For example:
$diff = [
'156' => [ //order id
'234' => 4, // with order 234 has 4 differences
'332' => 7, // with order 332 has 7 differences
// and so on...
],
]
3. Step
Order $diff by ASC and receive order with less differences.
Improvement
Also you could add total size of products in order for compare with difference. For example, If you have an order with 100 products and 10 diffs - it's better than order with 10 products and 9 diffs.
Here is what i would do if I had the problem :
$topOrders = [];
foreach($data as $value):
foreach($value as $order):
if(isset($$order)):
$$order++;
else:
$$order = 1;
endif;
$topOrders[$order] = $$order;
endforeach;
endforeach;
print_r($topOrders);
In $topOrders, you have an array that contains as key the ID, and as value you got the number of orders. All you have to do is to sort your array to get your top 3.
I have two arrays which I can get after form submit:
$product_id = $request->get('product_id');
// [1, 3, 4]
$quantity = $request->get('quantity');
// [5, 1, 2]
Now I want to submit this arrays into database where I want to pick the purchase_price from product database. I'm not sure how to assign product_id to product_quantity (index 0 to 0, 1 to 1, 2 to 2) and store into database.
Sample data to store into carts:
[1 5 120 ],
[3 1 230 ],
[4 2 340 ],
foreach ($product_id as $product)
{
}
DB::table('carts')->insert(
['product_id' => '',
'quantity' => 0,
'purchase_price' =>
]
);
Just for clarification:
product_id and quantity come from dynamic input box means number of product_id and quantity are same but it could be n times as user wanted. So I store it as arrays.
Now from this array I wanted to store it in database where I want to store with product_id with quantity.
Lets give you some suggetions:
If you have below array - if not then make it array like below:
$dataset = [
0 => [
'product_id' => 1,
'quantity' => 5,
'purchase_price' => 120,
],
1 => [
'product_id' => 3,
'quantity' => 1,
'purchase_price' => 230,
],
2 => [
'product_id' => 4,
'quantity' => 2,
'purchase_price' => 340,
]
];
Now you have to write INSERT query for this:
$result = Cart::insert($dataSet);
if ($result)
return true;
else
return false;
You will get an idea how to do it after seeing above code...good luck
Please check out this sample.
you can parse 2d array and convert it to json to store in the database then decode back:
$product_id = [1,2,3];
// [1, 3, 4]
$quantity = [5,1,2];
// [5, 1, 2]
$output=[120,230,340];
$out=[];
for ($i=0; $i < count($product_id); $i++) {
$out[$i]=[$product_id[$i],$quantity[$i],$output[$i]];
}
dd(json_encode($out));
output:
"[[1,5,120],[2,1,230],[3,2,340]]"
You can use
foreach ($product_id as $key=>$product)
{
//select purchase price from table by using the $product value
$purchase_price = *your select code here*
DB::table('carts')->insert([
'product_id' => $product,
'quantity' => $quantity[$key],
'purchase_price' => $purchase_price
]);
}
Let me know if not works
I am writing products stock script. I have a MySQL table "products_stock":
id zid pid amount
1 123 321 1
2 124 321 5
4 124 566 7
3 125 321 10
So, total amount of product id = 321 in stock is 16. Somebody makes order with pid=321 and qty = 7. I need some php-function, which will minus 7 from column amount starting from the first zid, and update records in table products_stock so that it lookes after like this:
id zid pid amount
1 123 321 0
2 124 321 0
4 124 566 7
3 125 321 9
I am stucked from this point.
Thank you for answers!
I don't use codeigniter but going through the documentation on how to perform a select operation and batch update. The main issue is getting your logic right... You select all the product entry for that particular item and you iterate through and subtract the amount of each from the ordered product.
<?php
//your order
$orders = [
['id' => 321, 'qty' => 7],
['id' => 501, 'qty' => 20],
];
$data = [];
foreach($orders as $order) {
$items = $this->db->where('pid', $order['id']);
$order_qty = $order['qty'];
foreach($items as $item){
$item_qty = $item->amount - $order_qty;
if ($item_qty <= 0) {
// set amount to 0 because it is less than order quantity
$data[] = ['id' => $item->id, 'amount' => 0];
// Subtract amount from order quantity to get the remaining order
$order_qty = $order_qty - $item->amount;
} else {
//If amount is more than order quantity set to calculated amount
$data[] = ['id' => $item->id, 'amount' => $item_qty];
// update order to 0
$order_qty = 0;
}
//exit if order quantity is 0
if ($order_qty === 0 ){
break;
}
}
}
$this->db->update_batch('product_stock', $data, pid');
You can do so by selecting all the relevent rows from the products_stock table and subtract/update the values one by one in loop until your required quantity gets over.
Example code
// These are the inputs you will be getting.
$pid = 321;
$qty = 7;
// Fetch all the relevent rows using the below query statement.
$select_sql = "select * from products_stock where pid = {$pid} and amount > 0";
// This will return the below array.
$data = [
[
'id' => 1,
'zid' => 123,
'pid' => 321,
'amount' => 1,
],
[
'id' => 1,
'zid' => 124,
'pid' => 321,
'amount' => 5,
],
[
'id' => 1,
'zid' => 125,
'pid' => 321,
'amount' => 10,
],
];
$update_data = [];
// You can then loop through your rows to perform your logic
foreach ($data as $row) {
// Exit loop if qty is 0
if ($qty == 0) {
break;
}
$id = $row['id'];
$amount = $row['amount'];
// Subtract the required amount from qty.
$qty -= $amount;
$amount = 0;
// If extra qty was consumed, add back to the amount.
if ($qty < 0) {
$amount =+ ($qty * -1);
$qty = 0;
}
// Update you data here or the best practice would be to avoid sql in a loop and do a bulk update.
// You can do this based on your requirement.
// $update_sql = "update products_stock set amount = {$amount} where id = {$id}";
// Prepare date for bulk update
$update_data []= [
'id' => $id,
'amount' => $amount,
];
echo "Qty {$qty} | Amt {$amount} \n";
echo "--------\n";
}
// Bulk update all your rows
if (count($update_data) > 0) {
$this->db->update_batch('products_stock', $update_data, 'id');
}
echo "\n";
echo "Balance Qty ". $qty . "\n";
Output
Qty 6 | Amt 0
--------
Qty 1 | Amt 0
--------
Qty 0 | Amt 9
--------
Balance Qty 0
Refer https://eval.in/920847 for output.
You can try running the same code with different qty.
This is the rough logic for your use case which will work as expected. Still there may be better ways to do this in an optimal way.
Glad if it helps you.