Static Algorithm for giving response after user input - php

I have 2 arrays: F of length 4 and S of length 25
User can select any no of these elements from the above two arrays For eg selecting any 2 from F and 10 from S.Based on what user selected i have give a response to the user from my another array R of length 100.
I tried making if else but was exhausted after creating
100s of them.
So is there any better approach for this ?
My goal is to give user one of the index from array R.
PS. There is no AI(Artificial Intelligence) involved.
EDIT
Currently what i have done is :
if(in_array(1,$F) && in_array(12,$S)){
return $R[3];
}else if(in_array(1,$F) && in_array(17,$s)){
return $R[91];
}else if(in_array(2,$F) && in_array(1,$F) && in_array(21,$S) && in_array(25,$S)){
return $R[23];
}else if(in_array(3,$F) && in_array(21,$S) && in_array(7,$S)){
return $R[17];
}..........

Something like this? Creating map like #zerkms mentioned.
function getRKey(array $f, array $s)
{
// values map by order
$map_r_array = [
3 => ['f' => [1], 's' => [12]],
91 => ['f' => [1], 's' => [17]],
23 => ['f' => [1, 2], 's' => [21, 25]],
17 => ['f' => [3], 's' => [7, 21]],
];
foreach ($map_r_array as $key => $check)
{
// check one by one.
if (!array_diff($check['f'], $f) && !array_diff($check['s'], $s))
{
return $key;
}
}
return null;
}
$f = [1, 2, 3];
$s = [7, 21];
var_dump(getRKey($f, $s)); // int(17)

Related

get the least Frequent item from array of objects

I'm trying to output [device_id] of the least frequent [device_ip_isp] from this array.
Also, If the array only has two SharedDevice Object available, and having different [device_ip_isp], it should output the 2nd SharedDevice Object's [device_id]
array (
0 =>
SharedDevice::__set_state(array(
'device_no' => 1,
'device_id' => '82',
'device_ip_isp' => 'Verizon Fios',
)),
1 =>
SharedDevice::__set_state(array(
'device_no' => 2,
'device_id' => '201',
'device_ip_isp' => 'Spectrum',
)),
2 =>
SharedDevice::__set_state(array(
'device_no' => 3,
'device_id' => '312',
'device_ip_isp' => 'Verizon Fios',
)),
3 =>
SharedDevice::__set_state(array(
'device_no' => 4,
'device_id' => '9715',
'device_ip_isp' => 'Verizon Fios',
)),
4 =>
SharedDevice::__set_state(array(
'device_no' => 5,
'device_id' => '11190',
'device_ip_isp' => 'Verizon Fios',
)),
)
The output should be 201 because "Spectrum" is the least frequent.
I tried the following and had issues:
I'm not sure how I can sort the object variables before comparing to find the least frequent.
/*
$user->getUser_devices() will output the array shown above.
*/
leastFrequent($user->getUser_devices(), 5);
function leastFrequent($arr, $n){
// find the min frequency
// using linear traversal
$min_count = $n + 1;
$res = -1;
$curr_count = 1;
for ($i = 1; $i < $n; $i++) {
if ($arr[$i]['device_ip_isp'] == $arr[$i - 1]['device_ip_isp']) {
$curr_count++;
} else {
if ($curr_count < $min_count) {
$min_count = $curr_count;
$res = $arr[$i - 1]['device_id'];
}
$curr_count = 1;
}
}
// If last element is
// least frequent
if ($curr_count < $min_count) {
$min_count = $curr_count;
$res = $arr[$n - 1]['device_id'];
}
return $arr[$n]$res['device_id'];
}
Ok, below is the explanation of the snippet inside out.
array_column to get all the device_ip_isp values in a single array.
array_count_values to get the frequency of each value.
array_reverse to reverse the count frequency array since you need the latest share device ID incase of count collision for a minimum count value.
min to get the lowest value among all the frequency counts.
array_search to get the key of the first frequency min element.
In the end, we reverse the input array to immediate return the device IP the moment we find the key from the above step matching the current device_ip_isp.
Snippet:
<?php
function getLeastFrequentElement($arr){
$freqs = array_reverse(array_count_values(array_column($arr, 'device_ip_isp')));
$deviceIPISP = array_search(min($freqs), $freqs);
foreach(array_reverse($arr) as $sharedDevice){
if($sharedDevice->device_ip_isp === $deviceIPISP){
return $sharedDevice->device_id;
}
}
throw new Exception("No shared object found!");
}
echo getLeastFrequentElement($arr);
Online Demo

Loop Array in PHP

how to loop an array if the data is 1 or more than 1?
I tried it with
foreach($array['bGeneral'] as $item) {
echo $item['bItem'];
}
but for arrays that have 1 data an error
Basically you need to check if the first element of $array['bGeneral'] is an array or a data value, and if so, process the data differently. You could try something like this:
if (isset($array['bGeneral']['bItem'])) {
// only one set of values
$item = $array['bGeneral'];
// process item
}
else {
// array of items
foreach ($array['bGeneral'] as $item) {
// process item
}
}
To avoid duplication of code, you will probably want to put the item processing code in a function.
Alternatively you could create a multi-dimensional array when you only have one value and then continue processing as you do with multiple values:
if (isset($array['bGeneral']['bItem'])) {
$array['bGeneral'] = array($array['bGeneral']);
}
foreach ($array['bGeneral'] as $item) {
// process item
}
Don't forget recursion - sometimes it is a best choise :
function scan_data($data, $path = null) {
if (!is_array($data))
echo "{$path} : {$data}\n";
else
foreach ($data as $k => $v)
scan_data($v, $path . '/' . $k);
}
$data = [
['a' => 1, 'b' => 2],
['a' => ['c' => 3, 'd' => 4], 'b' => 5],
['a' => 1, 'b' => ['e' => ['f' => 1, 'g' => 2], 'h' => 6] ]
];
scan_data($data);
Output:
/0/a : 1
/0/b : 2
/1/a/c : 3
/1/a/d : 4
/1/b : 5
/2/a : 1
/2/b/e/f : 1
/2/b/e/g : 2
/2/b/h : 6

PHP find all combinations to a sum in inner array

I'm writing a PHP script for available rooms in a hotel. I want every combination for a group (i.e. 4 person). This is my array.
$room_array = array(
array(
"title" => "1 person room",
"room_for" => 1,
"price" => 79
),
array(
"title" => "2 person room with other",
"room_for" => 1,
"price" => 69
),
array(
"title" => "2 person room alone",
"room_for" => 1,
"price" => 89
),
array(
"title" => "2 person",
"room_for" => 2,
"price" => 69
),
array(
"title" => "3 person",
"room_for" => 3,
"price" => 69
)
);
Possible outcome:
4x 1 person room
4x 2 person room with other
3x 1 person room + 1x 2 person room with other
2x 2 person room
1x 3 person room + 1x 1 person room
etc. etc.
This calls for a recursive function. But every example I looked at doesn't work with counting in the inner array. The closest i found was this question:
Finding potential combinations of numbers for a sum (given a number set to select from)
But i didn't get de solution to work..
UPDATE:
Hi, thanks for all the answers. Really helped me in finding the best practice. In the meantime, the assignment has changed a little so I can't answer my own original question. My problem is solved. Thanks again for the help!
My answer below will get you partway there.
Resources
I borrowed some code logic from this answer.
To quote the answer (in case of future removal), please view below.
You can try
echo "<pre>";
$sum = 12 ; //SUM
$array = array(6,1,3,11,2,5,12);
$list = array();
# Extract All Unique Conbinations
extractList($array, $list);
#Filter By SUM = $sum $list =
array_filter($list,function($var) use ($sum) { return(array_sum($var) == $sum);});
#Return Output
var_dump($list);
Output
array
0 => array
1 => string '1' (length=1)
2 => string '2' (length=1)
3 => string '3' (length=1)
4 => string '6' (length=1)
1 => array
1 => string '1' (length=1)
2 => string '5' (length=1)
3 => string '6' (length=1)
2 => array
1 => string '1' (length=1)
2 => string '11' (length=2)
3 => array
1 => string '12' (length=2)
Functions Used
function extractList($array, &$list, $temp = array()) {
if(count($temp) > 0 && ! in_array($temp, $list))
$list[] = $temp;
for($i = 0; $i < sizeof($array); $i ++) {
$copy = $array;
$elem = array_splice($copy, $i, 1);
if (sizeof($copy) > 0) {
$add = array_merge($temp, array($elem[0]));
sort($add);
extractList($copy, $list, $add);
} else {
$add = array_merge($temp, array($elem[0]));
sort($add);
if (! in_array($temp, $list)) {
$list[] = $add;
}
}
}
}
My answer
The code below uses the code referenced above. I changed the return functionality of the array_filter function to map it to your needs.
The only thing left for you to do is change the function so that it can catch multiple of the same type of room. At the moment, the code below will only output 1 of each type of room (as per the code referenced above). An easy way to get around this would be to multiply the array values you send to the function by the number of guests you are searching for rooms, but up to the amount of rooms available. So: if you are looking to book for 4 guests and you have no single rooms remaining and only 1 double room, your best match result would have to be a 2 person room and a 3 person room. I've added some brief functionality to add this (it's commented out), although I have not tested it. It will likely take a while to process that as well so if you're looking for a quicker method, you're gonna have to use a better algorithm as already mentioned in previous comments/answers or solve P vs NP
The code below also gives you the option to toggle a value of $exact. This value, if set to true, will return only matches exactly equal to the number of guests, and if set to false will return all matches that equal to at least the number of guests.
<?php
class Booking {
private $minGuests = 1;
protected $guests = 1;
protected $rooms = [];
public function getRoomCombinations(bool $exact = true) {
$guests = $this->guests;
$list = [];
$rooms = $this->rooms;
/*for($i = 0; $i < $guests-1; $i++) {
$rooms = array_merge($rooms, $this->rooms);
}
asort($rooms);*/
$this->extractList($rooms, $list);
$result = array_filter($list, function($var) use ($guests, $exact) {
if($exact)
return(array_sum(array_map(function($item) { return $item['room_for'];}, $var)) == $guests);
else
return(array_sum(array_map(function($item) { return $item['room_for'];}, $var)) >= $guests && count($var) <= $guests);
});
array_multisort(array_map('count', $result), SORT_ASC, $result);
return $result;
}
private function extractList(array $array, array &$list, array $temp = []) {
if (count($temp) > 0 && !in_array($temp, $list))
$list[] = $temp;
for($i = 0; $i < sizeof($array); $i++) {
$copy = $array;
$elem = array_splice($copy, $i, 1);
if (sizeof($copy) > 0) {
$add = array_merge($temp, array($elem[0]));
sort($add);
$this->extractList($copy, $list, $add);
} else {
$add = array_merge($temp, array($elem[0]));
sort($add);
if (!in_array($temp, $list)) {
$list[] = $add;
}
}
}
}
public function setGuests(int $guests) {
$this->guests = ($guests >= $this->minGuests ? $guests : $this->minGuests);
return $this;
}
public function setHotelRooms(array $rooms) {
$this->rooms = $rooms;
return $this;
}
}
$booking = (new Booking())
->setGuests(4)
->setHotelRooms([
[
"title" => "1 person room",
"room_for" => 1,
"price" => 79
],
[
"title" => "2 person room with other",
"room_for" => 1,
"price" => 69
],
[
"title" => "2 person room alone",
"room_for" => 1,
"price" => 89
],
[
"title" => "2 person",
"room_for" => 2,
"price" => 69
],
[
"title" => "3 person",
"room_for" => 3,
"price" => 69
]
]);
echo '<pre>' . var_export($booking->getRoomCombinations(true), true) . '</pre>';
?>
If you need all the combinations then you can use an backtracking iterative algorithm (depth path).
In summary:
Type of tree: binary tree because all the levels can contain a solution when the number of persons contabilized = objetive
Binary tree
Algorithm functions
You need to increment the cont every time that a level is generated with the number of persons of the level and decrement when you change your track (exploring brothers or back)
solution: array[0..levels-1] values {0 (node not selected) ,1 (node selected)}
solution[0] = 1 -> You choose that "1 person room" belongs to the solution
solutions: list/array of objects and every object contains array of titles of rooms
function Backtracking ()
level:= 1
solution:= s_initial
end:= false
repeat
generate(level, solution)
IF solution(level, solution) then
save_solution
else if test(level, solution) then
level:= level+ 1
else
while NOT MoreBrothers(level, solution)
go_back(level, s)
until level==0
2.1. Generate: generate next node
2.2. Solution: test if it's a solution
2.3. Critery: if we must continue by this track or bound
2.4. MoreBrothers: if there are nodes without check at this level
2.5. Backtrack: all the nodes at this level were explored
2.6. Save solution: add to the solutions array your object that contains strings
$room_array = array(
array(
"title" => "1 person room",
"room_for" => 1,
"price" => 79
),
array(
"title" => "2 person room with other",
"room_for" => 1,
"price" => 69
),
array(
"title" => "2 person room alone",
"room_for" => 1,
"price" => 89
),
array(
"title" => "2 person",
"room_for" => 2,
"price" => 69
),
array(
"title" => "3 person",
"room_for" => 3,
"price" => 69
)
);
// Gets rooms based on a given number of guests
function get_possible_rooms($num_guests) {
global $room_array;
$possible_rooms = [];
foreach ($room_array as $room) {
if ($num_guests <= $room['room_for']) {
$possible_rooms[] = $room['title'];
}
}
return $possible_rooms;
}
// Gets the available room capacities
function get_room_capacities() {
global $room_array;
$capacities = [];
foreach ($room_array as $room) {
$capacities[] = $room['room_for'];
}
return array_unique($capacities);
}
// Gets the different combinations of groups of guests based on the room capacities
function get_guest_assignments($remaining_guests, $parent_id = '', $num_guests, &$result) {
$room_capacities = get_room_capacities();
for ($i = 1; $i <= $remaining_guests; ++$i) {
if (in_array($i, $room_capacities)) {
$parent_guests = (isset($result[$parent_id])) ? $result[$parent_id] : 0;
$result[$parent_id . $i] = $parent_guests + $i;
for ($j = 1; $j <= $remaining_guests - $i; ++$j) {
// Recursively get the results for children
get_guest_assignments($j, $parent_id . $i, $num_guests, $result);
}
}
}
if ($remaining_guests === 1 && $parent_id !== '') {
// If it reaches the end and it does not fulfill the required number of guests,
// mark it for removal later
if ($result[$parent_id] < $num_guests) {
$result[$parent_id] = null;
}
}
// This is the last recursion
if ($result[$parent_id . '1'] === $num_guests) {
// Remove duplicates.
// To do this, we need to re-sort the keys (e.g. 21 becomes 12) and call array_unique()
// I admit this is a bit sloppy implementation.
$combinations = [];
foreach ($result as $key => $value) {
if ($value !== null) {
$nums = str_split($key);
sort($nums);
$combinations[] = implode('', $nums);
}
}
$result = array_unique($combinations);
}
}
// Gets the rooms for each group of guest
function get_room_assignments($guest_str) {
$rooms = [];
for ($i = 0; $i < strlen($guest_str); ++$i) {
$num_guests = intval(substr($guest_str, $i, 1));
$rooms[] = get_possible_rooms($num_guests);
}
return $rooms;
}
//----------
// RUN
//----------
$guests = 4;
$result = [];
get_guest_assignments($guests, null, $guests, $result);
foreach ($result as $guest_combi) {
$assignments = get_room_assignments($guest_combi);
// Printing output
echo 'Guest Combination ' . $guest_combi . "\n";
echo json_encode($assignments, JSON_PRETTY_PRINT);
echo "\n\n";
}
The output will look something like this:
...
Guest Combination 13
[
[
"1 person room",
"2 person room with other",
"2 person room alone",
"2 person",
"3 person"
],
[
"3 person"
]
]
...
"Guest combination 13" means the 4 guests will be split into groups of 1 and 3 persons.
Output is an array of possible rooms for each group. So in the example, the group of 1 can book 1 person room, 2 person room with other, ... 3 person room. And the group of 3 can book 3 person room.
—
Other notes:
I know we hate global but doing this just for brevity. Feel free to modify.
There's a shorter way to code this, but this implementation makes it easier to debug since guest combinations are used as keys.

Most efficient way to compare arrays in PHP by order?

Take these two arrays in PHP:
$array1 = [
2 => 'Search',
1 => 'Front-End / GUI'
];
$array2 = [
1 => 'Front-End / GUI',
2 => 'Search'
];
Most of the array comparison functions do not care about order. Doing an array_diff will result in an empty array.
What's the most efficient / shortest / cleanest way to compare two arrays with regard to order and:
show whether or not they are equal (true / false)?
show the difference (such as for PHPUnit)?
Running $this->assertEquals( $array1, $array2 ); in PHPUnit ideally should yield something like:
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
## ##
Array (
- 2 => 'Search'
- 1 => 'Front-End / GUI'
+ 1 => 'Front-End / GUI'
+ 2 => 'Search'
)
Update - Solution
This generates a sort-of diff only if all elements are same, just in different order.
PHPUnit Tests:
public function test...() {
$actual = someCall();
$expected = [...];
// tests for same elements
$this->assertEquals( $expected, $actual );
// tests for same order
$diff = $this->array_diff_order( $expected, $actual );
$this->assertTrue( $expected === $actual, "Failed asserting that two arrays are equal order.\n--- Expected\n+++ Actual\n## ##\n Array(\n$diff )" );
}
private function array_diff_order( $array1, $array2 ) {
$out = '';
while ((list($key1, $val1) = each($array1)) && (list($key2, $val2) = each($array2)) ) {
if($key1 != $key2 || $val1 != $val2) $out .= "- $key1 => '$val1' \n+ $key2 => '$val2'\n";
}
return $out;
}
You can just use the === operator
$array = array(1 => "test", 2=> "testing");
$array2 = array(1 => "test", 2=> "testing");
var_dump($array === $array2);
$array2 = array(2 => "test", 1=> "testing");
var_dump($array === $array2);
returns
boolean true
boolean false
then use array_diff_assoc() to find the differences
while ((list($key1, $val1) = each($array)) && (list($key2, $val2) = each($array2)) ) {
if($key1 != $key2 || $val1 != $val2) echo "- $key1 - $val1 \n + $key2 - $val2";
}
Should give some output for order
Using your array this gives me
2 - Search + 1 - Front-End / GUI
1 - Front-End / GUI + 2 - Search
you can change the output to how ever you need it
If you are looking for a solution to generate a diff like output, i think this is a place where iterator shine:
Just having two iterators for each array and stepping trough on them simultaneously in one loop and compare key + value pairs can do almost everything you need:
$array1 = [
2 => 'Search',
1 => 'Front-End / GUI'
];
$array2 = [
1 => 'Front-End / GUI',
2 => 'Search'
];
$it0 = new ArrayIterator($array1);
$it1 = new ArrayIterator($array2);
while ($it0->valid() || $it1->valid()) {
if ($it0->valid() && $it1->valid()) {
if ($it0->key() != $it1->key() || $it0->current() != $it1->current()) {
print "- ".$it0->key().' => '.$it0->current()."\n";
print "+ ".$it1->key().' => '.$it1->current()."\n";
}
$it0->next();
$it1->next();
} elseif ($it0->valid()) {
print "- ".$it0->key().' => '.$it0->current()."\n";
$it0->next();
} elseif ($it1->valid()) {
print "+ ".$it1->key().' => '.$it1->current()."\n";
$it1->next();
}
}
Will output something like:
- 2 => Search
+ 1 => Front-End / GUI
- 1 => Front-End / GUI
+ 2 => Search
This idea of course should be expanded to handle nested arrays with RecursiveArrayIterator and probably format the output better too.
You want array_diff_assoc(), which compares values AND keys. array_diff() considers only values.
followup: Works fine here:
php > $arr1 = array(2 => 'Search', 1 => 'Front');
php > $arr2 = array(1 => 'Search', 2 => 'Front');
php > var_dump(array_diff_assoc($arr1, $arr2));
array(2) {
[2]=>
string(6) "Search"
[1]=>
string(5) "Front"
}

How to find missing values in a sequence with PHP?

Suppose you have an array "value => timestamp". The values are increasing with the time but they can be reset at any moment.
For example :
$array = array(
1 => 6000,
2 => 7000,
3 => 8000,
7 => 9000,
8 => 10000,
9 => 11000,
55 => 1000,
56 => 2000,
57 => 3000,
59 => 4000,
60 => 5000,
);
I would like to retrieve all the missing values from this array.
This example would return :
array(4,5,6,58)
I don't want all the values between 9 and 55 because 9 is newer than the other higher values.
In real condition the script will deal with thousands of values so it need to be efficient.
Thanks for your help!
UPDATE :
The initial array can be ordered by timestamps if it is easier for the algorithm.
UPDATE 2 :
In my example the values are UNIX timestamps so they would look more like this : 1285242603 but for readability reason I simplified it.
Here’s another solution:
$prev = null;
$missing = array();
foreach ($array as $curr => $value) {
if (!is_null($prev)) {
if ($curr > $prev+1 && $value > $array[$prev]) {
$missing = array_merge($missing, range($prev+1, $curr-1));
}
}
$prev = $curr;
}
You can do the following:
Keep comparing adjacent array keys. If
they are consecutive you do nothing.
If they are not consecutive then you
check their values, if the value has
dropped from a higher value to a
lower value, it means there was a
reset so you do nothing.
If the value has not dropped then it
is a case of missing key(s). All the
numbers between the two keys(extremes
not included) are part of the result.
Translated in code:
$array = array( 1 => 6000, 2 => 7000, 3 => 8000, 7 => 9000, 8 => 10000,
9 => 11000,55 => 1000, 56 => 2000, 57 => 3000, 59 => 4000,
60 => 5000,);
$keys = array_keys($array);
for($i=0;$i<count($array)-1;$i++) {
if($array[$keys[$i]] < $array[$keys[$i+1]] && ($keys[$i+1]-$keys[$i] != 1) ) {
print(implode(' ',range($keys[$i]+1,$keys[$i+1]-1)));
print "\n";
}
}
Working link
This gives the desired result array(4,5,6,58):
$previous_value = NULL;
$temp_store = array();
$missing = array();
$keys = array_keys($array);
for($i = min($keys); $i <= max($keys); $i++)
{
if(!array_key_exists($i, $array))
{
$temp_store[] = $i;
}
else
{
if($previous_value < $array[$i])
{
$missing = array_merge($missing, $temp_store);
}
$temp_store = array();
$previous_value = $array[$i];
}
}
var_dump($missing);
Or just use Gumbo's very smart solution ;-)

Categories