Extract array entries to fit perfectly fit cases - php

I am building a student application where I need to find what files are the best match for the students based on the files that have number of the semesters they need to cover.
Each student is uploading a file where they select a number of semesters that are covered. Let's say file X has semester 1 and 2, file Y has semesters 1, 3, 4 and so on.
The only order here is the ascending order. The number of selected semesters can be random (1 or 1,2,3 or 3,4 or or 2,5,6 or 1,2,5,6,8 or any combination of this kind).
What i need to achieve is to get the least number of files covering the maximum amount of semesters taking into account the smaller semester to be covered.
In my example a special case is required semesters 5 where I could return files 1, 2, 3, 4 because all this contains semesters to be covered but this is not what I am looking for. Instead i need to get file 3 and 4 which covers perfectly the case.
I am providing files where each file is covering a number of semesters like this:
Student files in the following files:
file 1 covers semester: array(3)
file 2 covers semester: array(3,4)
file 3 covers semester: array(1,2)
file 4 covers semester: array(3,4,5,6)
$aUserFiles = [
0 => [
'file' => 'file 1',
'semesters' => [
3
]
],
1 => [
'file' => 'file 2',
'semesters' => [
3,4
]
],
2 => [
'file' => 'file 3',
'semesters' => [
1,2
]
],
3 => [
'file' => 'file 4',
'semesters' => [
3,4,5,6
]
]
];
Semesters that needs to be covered and expected results:
Option 1: number of semesters required: 5.
Expected result: file 3, file 4
$aResult = [
0 => [
'file' => 'file 3',
'semesters' => [
1,2
]
],
1 => [
'file' => 'file 4',
'semesters' => [
3,4,5,6
]
]
];
Option 2: number of semesters required: 2.
Expected result: file 3
$aResult = [
0 => [
'file' => 'file 3',
'semesters' => [
1,2
]
]
];
Option 3: number of semesters required: 4.
Expected result: file 3, file 2
Option 4: number of semesters required: 3.
Expected result: file 3, file 1

You can do something like this:
$requiredSemesters = 3; // Your input
$indicators = [];
$result = [];
foreach ($aUserFiles as $key => $file) {
$first = $file['semesters'][0];
$last = $file['semesters'][count($file['semesters']) - 1];
$filesInRangeCount = count(array_intersect($file['semesters'], range(1, $requiredSemesters)));
if (!isset($indicators[$first]) || $indicators[$first]['range'] < $filesInRangeCount) {
$indicators[$first] = ["key" => $key, "max" => $last, "range" => $filesInRangeCount];
}
}
ksort($indicators);
$result = [];
$max = 0;
foreach ($indicators as $indicator) {
if ($max >= $requiredSemesters) {
break;
}
$result[] = $aUserFiles[$indicator['key']];
$max = $indicator["max"];
}
print_r($result);
Demo: https://3v4l.org/ZfDLo
Explanation:
I make a new array and insert the values grouped by minimum value (first number since your arrays are ordered). In that process I make sure to leave only the file with highest number of overlap with your input (1-input) and (first number - last number). Now I sort the grouped array, and iterate. All I need to do now is to add the files until I reach the maximum number which is the given input.

Related

How to insert with Eloquant a multi level entry?

Let's consider I have Quizzes that can contain Questions which each of them contains some Propositions. Therefore I have three models: Quiz, Question and Proposition. The propositions are linked to the questions through a pivot table that contains: id_question, id_proposition, is_correct.
In a Seeder, I would like to insert a dummy quiz which could be represented in YAML as follow:
title: A Quiz
questions:
- name: Animals
content: What's the difference between a duck?
propositions:
- text: The dog has two legs
is_correct: false
- text: One leg is both the same
is_correct: true
- text: Every duck has at least several teeth
is_correct: false
Traditionally in Laravel you need to split each step such as:
$quiz = new Quiz;
$quiz->name = "A Quiz";
$question = $quiz->questions()->create();
...
Is there a shorter way to hierarchically create a new entry that would look like this:
Quiz::create([
'name' => 'A Quiz'
'questions' => [
Question::Create([
'name' => 'Animals',
'content' => "What's the difference between a duck?"
'propositions' => [
Proposition::Create(['content' => 'The dog has two legs']),
Proposition::Create(['content' => 'One leg is both the same'])
Proposition::Create(['content' => 'Every duck has at least several teeth'])
]
])
]
]
just testing with static values :
$quiz = Quiz::create([
'name' => 'A quiz',
])->questions()->createMany([
['name' => 'animals', 'content' => 'animals content'],
['name' => 'fruits', 'content' => 'fruits content'],
])->map(function($value, $key){
$value->propositions()->createMany([
['content' => 'content 1'],
['content' => 'content 2'],
]);
});
i think you can set the values based on YAML index-values(something like json) with foreach

Yii2 - Kartik Widget-Rating overwrite values

it's possible overwrite the values of each star?
i need define step = 3.75, min = 0 and max = 15, and prevent from selecting half a star.
the values I want are:
star 1 => 0; star 2 => 3.75; star 3 => 7.5; star 4 => 11,25; star 5
=> 15.
form.php
echo $form->field($model, 'rating')->widget(StarRating::classname(), [
'pluginOptions' => [
'stars' => 5,
'step' => 3.75,
'min' => 0,
'max' => 15,
]
]);
but when i make this, the selection of each star not display correctly, half of star is selected.
I thought you only want to display the client side (with manual numbers). And you will not get numbers from DB
the correct way is to overwrite the star-rating file. Which should be referred to the Kartik forum.
You must overwrite that the first star has a zero score (highlight first star = 0)
However, you can use the following code in your model.
public function beforeSave($insert)
{
// if ($insert) { // only for Save (No Update)
if (!empty($this->Your_field)) {
$this->setAttribute('Your_field', $this->Your_field-3.75);
}
// }
return parent::beforeSave($insert);
}
Instead of your_field, enter your field name (field of the rate).
You have made the settings incorrectly.
Refer the plugin documentation and demos for details.
quotation krajee:
The logic for highlighting stars depends on the stars, min, max, and
step configurations. The percentage of each star to be highlighted for
each step, will be evaluated using the following expression:
STAR_HIGHLIGHT_PERCENT = (max - min) * step * 100 / stars
For example:
If min = 0, max = 5, step = 0.5, and stars = 5, then
STAR_HIGHLIGHT_PERCENT will evaluate to 50% of each star for each
step.
If min = 1, max = 5, step = 0.5, and stars = 5, then
STAR_HIGHLIGHT_PERCENT will evaluate to 40% of each star for each
step.
So, for example 2 above, the stars will not be completely highlighted
as desired. It is therefore important you set the configuration of
stars, min, max, and step correctly.
Refer the plugin documentation and demos for details.
for You:
'stars' => 4,
'step' => 3.75,
'min' => 0,
'max' => 15,
or
echo StarRating::widget(['name' => 'rating',
'pluginOptions' => [
'stars' => 5,
'step' => 3.75,
'min' => 0,
'max' => 18.75,
'starCaptions' => new JsExpression("function(val){return val-3.75 + ' hearts';}")
]
]);
1
If your field values are integers 1 to 5
To display half stars and full stars, You must use round numbers.
After putting the numbers in the following formula:
(max - min) * step * 100 / stars
Equal to: 50% or 100%
And to display only a full star, Equal to: 100%
2
However, it is better that the rate and step are equal.
For example, if step is 2, is better than max equal to 10 (5 Stars)
3
All of this varies according to the value of your field:
for example:
values: .5, 1, 1.5 , ... 5
Default (stars=5,max=5,step=0.5)
values: 1, 2, 3 , ... 5
(stars=5,max=5,step=1)
values: 1, 2, 3 , ... 12
(stars=6,max=12,step=1) => Half star = one point
values: 2, 4, 6 , ... 12
(stars=6,max=12,step=1) => one star = 2 point

Merge Two Arrays to Have an Integer Value be an near equal as possible

Application to Distribute Stock between Warehouses
I have two arrays,
One has list of Warehouses along with the Current Quantity: (This can be Dynamic with one or more locations)
[
['location_name' => 'Toronto', 'current_qty' => 3],
['location_name' => 'Mississauga','current_qty' => 7],
['location_name' => 'London', 'current_qty' => 5],
]
The Other array has the Amount of Stock that would be Comming in:
[
'qty' => 5
]
And want to distribute the Qty among the locations so that the current qty of each of the locations would be near equal to each other. So want to return an array with the number that needs to be added to each location. Like: Here , of the 5, 3 went to Toronto and 2 to London. So it can be seen then after the nearest equalization, rest of the distribution can be done randomly.
[
['location_name' => 'Toronto', 'add_qty' => 3],
['location_name' => 'Mississauga','add_qty' => 0],
['location_name' => 'London', 'add_qty' => 2],
]
And Just cannot figure out the logic of this algorithm. Would really appreciate any pointers. Many Thanks.
I would do it like this, not really sure about any performance issues. I don't know how big your data set is.
$locations = [
['location_name' => 'Toronto', 'current_qty' => 3, 'add_qty' => 0],
['location_name' => 'Mississauga', 'current_qty' => 7, 'add_qty' => 0],
['location_name' => 'London', 'current_qty' => 5, 'add_qty' => 0],
];
$supplies = 5;
// This function sorts locations, by comparing the sum of the current quantity and the quantity the location will get.
$locationsByQuantityAscending = function ($locationA, $locationB) {
return ($locationA['current_qty'] + $locationA['add_qty']) - ($locationB['current_qty'] + $locationB['add_qty']);
};
// Sort the locations, getting the ones with the lowest quantity first.
usort($locations, $locationsByQuantityAscending);
// Keep dividing, until we're out of supplies
while ($supplies > 0) {
$locations[0]['add_qty']++; // Add one to the location with the lowest supplies
$supplies--; // Decrease the supplies by one
usort($locations, $locationsByQuantityAscending); // Sort the locations again.
}
print_r($locations);
At the end, this will output:
Array
(
[0] => Array
(
[location_name] => Toronto
[current_qty] => 3
[add_qty] => 3
)
[1] => Array
(
[location_name] => London
[current_qty] => 5
[add_qty] => 2
)
[2] => Array
(
[location_name] => Mississauga
[current_qty] => 7
[add_qty] => 0
)
)
If you really need to be performant, you could also just sort the locations once by their current quantity. Then keep adding to the first location, until its stock will be higher than the second location. Then, until the quantity at the second location is higher than the third location, add one to the first and second location, etc.
I think this will be more performant, since you don't have to sort all your locations X times (X being the number of supplies to divide). I'll leave that implementation to you.
Hint: have a look at recursive functions
If performance is critical, and the input data is usually bigger then shown here (e.g. a lot more locations or a lot more quantity to distribute). You might want to consider using SplMinHeap:
For example:
<?php
$helper = new class extends SplMinHeap
{
public function compare($a, $b): int
{
return ($b['current_qty'] + $b['add_qty']) <=> ($a['current_qty'] + $a['add_qty']);
}
};
$locations = [
['location_name' => 'Toronto', 'current_qty' => 3, 'add_qty' => 0],
['location_name' => 'Mississauga', 'current_qty' => 7, 'add_qty' => 0],
['location_name' => 'London', 'current_qty' => 5, 'add_qty' => 0],
];
foreach ($locations as $entry) {
$helper->insert($entry);
}
$qty = 10000;
while ($qty-- > 0) {
$min = $helper->extract();
$min['add_qty']++;
$helper->insert($min);
}
print_r(iterator_to_array($helper));
https://3v4l.org/nDOY8

Find matching equal orders using arrays and PHP

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.

PHP - backtrack through multi-dimensional array to check for recursion issues

I have a PHP array that outlines a parent-child relationships between objects, based on their ID. The array could potentially be infinitely deep, but the only rule is that "you may not add a child ID to a parent, where the child ID is a parent or grandparent (or great-grandparent etc etc) of said parent", in order to rule out recursive loops.
For example:
<?php
// Good relationship: 6 and 4 are children of 7, with 5 a child of 6 and so on
$good_relationship = [
'id' => 7,
'children' => [
[
'id' => 6,
'children' => [
[
'id' => 5,
'children' => [.. etc etc..]
]
]
],
[
'id' => 4,
'children' => []
]
]
];
// Badly-formed relationship: 6 is a child of 7, but someone added 7 as a child of 6.
$bad_relationship = [
'id' => 7,
'children' => [
[
'id' => 6,
'children' => [
[
'id' => 7,
'children' => [ ... 6, then 7 then 6 - feedback loop = bad ... ]
]
]
],
[
'id' => 4,
'children' => []
]
]
];
?>
I'm trying to write a function that checks for recursion issues when an ID is potentially added as a child to another ID. It would take in an ID ($candidate_id) and tries to add it as a child of another ID ($parent_id), and checks the existing array ($relationship) all the way back up the chain, and returns true if the candidate does not show up as a parent,grandparent,etc of $parent, and false if the candidate addition will cause a recursion issue by being added.
From the above $good_relationship, it would return true is I added ID 3 to ID 5, but false if I added ID 7 to ID 5.
Here's what I have so far, but I know it's way off - it's only checking for immediate grandparent of the candidate ID.
<?php
public function check_candidate($array, $candidate_id, $parent_id, &$grandparent_id = 0)
{
$reply_array = [];
foreach($array as $action)
{
// If $action['id'] is the same as the grandparent,
if($grandparent_id == $action['id'])
{
return false;
}
$grandparent_id = $action['id'];
if(isset($action['children']) && count($action['children']) >= 1)
{
$this->check_candidate($action['children'], $candidate_id, $parent_id, $grandparent_id);
}
}
return true;
}
?>
I've had a look at array_walk_recursive() in this case, but if $good_relationship has more than 1 element to it (which it always will), the callback will not know how 'deep' it is within the function, and it all becomes a bit of a nightmare.
Can anyone help me here?

Categories