need a cleaner way to break a tie in php - php

I have a poll which has 4 choices, sometimes people vote for the same things and choices end up in a tie. I'm looking for a way to break this tie.
The poll variables are always different the below is just an example of how they come. I'm currently doing this with messy if statements and randomizing results for each scenario or one === the other.
<?php
$choice1=4;
$choice2=4;
$choice3=2;
$choice4=4;
if ($choice1==$choice2) {
$a = ['$choice1','$choice2'];
$breaker = $a[mt_rand(0, count($a)-1)];
echo $breaker;
}elseif ($choice1==$choice3) {
$a = ['$choice1','$choice3'];
$breaker = $a[mt_rand(0, count($a)-1)];
echo $breaker;
}elseif ($choice1==$choice4) {
$a = ['$choice1','$choice4'];
$breaker = $a[mt_rand(0, count($a)-1)];
echo $breaker;
}elseif ($choice2==$choice1) {
$a = ['$choice2','$choice1'];
$breaker = $a[mt_rand(0, count($a)-1)];
echo $breaker;
//etc...
// This goes on and on also goes up to 3 way ties and 4 way ties.
This method seems extremely inelegant, also the number of votes is going to increase as more and more users register to vote so it is not scalable at this point any suggestions?

This approach uses an array with two set of sorts.
// As many choices as you would like
$choices = [
['id' => 1, 'name' => 'choice1', 'count' => 4],
['id' => 2, 'name' => 'choice2', 'count' => 4],
['id' => 3, 'name' => 'choice3', 'count' => 2],
['id' => 4, 'name' => 'choice4', 'count' => 4],
['id' => 5, 'name' => 'choice5', 'count' => 4],
['id' => 6, 'name' => 'choice6', 'count' => 4],
['id' => 7, 'name' => 'choice7', 'count' => 4],
['id' => 8, 'name' => 'choice8', 'count' => 6],
['id' => 9, 'name' => 'choice9', 'count' => 7],
['id' => 10, 'name' => 'choice10', 'count' => 4],
['id' => 11, 'name' => 'choice11', 'count' => 6],
['id' => 12, 'name' => 'choice12', 'count' => 6],
['id' => 13, 'name' => 'choice13', 'count' => 7],
['id' => 14, 'name' => 'choice14', 'count' => 3],
['id' => 15, 'name' => 'choice15', 'count' => 4],
];
// First, sort by count
usort($choices, function($a, $b) {
if ( $a['count'] < $b['count'] ) return -1; // a < b
elseif ( $a['count'] > $b['count'] ) return 1; // a > b
return 0;
});
// Now, all are sorted by count.
// Walk through and deal with items that have the same count.
$buf = []; // A temp buffer to keep all items with a certain count
$prevCcount = null;
$output = [];
foreach($choices as $choice) {
$count = $choice['count'];
echo sprintf('id %d count %d', $choice['id'], $choice['count']) . "\n";
if ( $prevCount != $count ) {
// Possible new group of items with the same count
// Does the buffer have more than 1. If so, randomize.
if ( count($buf) > 1 ) {
echo "Shuffling " . count($buf) . "items\n";
$shuffled = shuffle($buf);
if ( ! $shuffled ) {
throw new Exception('Failed to shuffle');
}
}
if ( count($buf) > 0 ) {
$output = array_merge($output, $buf);
$buf = [];
}
}
$prevCount = $count; // Keep track of count of previous item
$buf[] = $choice; // add current item to buffer
}
// Deal with the tail
// There will be 1 or more items still in the buffer that must be dealt with
echo "Final buf has " . count($buf) . " items\n";
if ( count($buf) > 1 ) {
echo "Shuffling " . count($buf) . " items\n";
$shuffled = shuffle($buf);
if ( ! $shuffled ) {
throw new Exception('Failed to shuffle');
}
}
$output = array_merge($output, $buf);
print_r($output);
echo "\n";

You could use array_keys and max to get back which of them has the max score then chose one in that array in your liking
$choice1=4;
$choice2=4;
$choice3=2;
$choice4=4;
$arr = ["choice1" => $choice1,"choice2" => $choice2,"choice3" => $choice3,"choice4" => $choice4];
$winners = array_keys($arr, max($arr));
var_dump($winners);
Output:
array(3) {
[0]=>
string(7) "choice1"
[1]=>
string(7) "choice2"
[2]=>
string(7) "choice4"
}
You can then use your existing method to choose one of the three (in this case).
$breaker = $winners[mt_rand(0, count($winners)-1)];
echo $breaker;

Related

How to format html table to show unique months and item id and sum the cost? (PHP)

I have been stuck for days on this 'little' problem.
I have one array which contains specific data:
$data = array(
0 => array('id' => 8, 'month' => 1, 'cost' => 12500),
1 => array('id' => 8, 'month' => 2, 'cost' => 14200),
2 => array('id' => 9, 'month' => 1, 'cost' => 23000),
3 => array('id' => 9, 'month' => 2, 'cost' => 18000),
);
And this is the html table results i need to get:
Id Jan Feb Mar Apr May
8 12,500 14,200 10,200 10,300 11,000
9 23,000 18,000 21,320 10,642 14,636
How i can sort array to display this data in html table on view.ctp ?
I have tried using foreach loops but i really don't know how to put unique months and unique id like it is displayed above. I'm using CakePHP 2.x technology.
I appreciate every help. Thank you
Loop over your array and group into a new array based on the ID. Assign month=cost key-value pairs, using IDs as the grouping keys of your multidimensional array.
$by_id = [];
foreach($data as $x) {
$by_id[$x['id']][$x['month']] = $x['cost'];
// e.g. $by_id[8][2] = 14200;
}
This will turn your sample data into the following array:
array(2) {
[8] · array(2) {
[1] · int(12500)
[2] · int(14200)
}
[9] · array(2) {
[1] · int(23000)
[2] · int(18000)
}
}
That should be much easier to turn into a HTML table. For example like this:
$html = [];
$html[] = '<table>';
$html[] = '<tr><th>ID</th><th>Jan</th><th>Feb</th></tr>';
foreach($by_id as $id => $months) {
$html[] = '<tr>';
$html[] = "<td>{$id}</td>";
foreach($months as $month => $cost) {
$html[] = "<td>{$cost}</td>";
}
$html[] = '</tr>';
}
$html[] = '</table>';
echo implode("\n", $html);
I trust the code is clear enough without elaborate comments. Add months to the table header as necessary. Use e.g. number_format for $cost to format your monies. Be aware that the sample code above doesn't account for possible missing months for a given ID, it assumes symmetric data. Add checks and remedies if necessary. See demo at https://3v4l.org/7CFRf.
You should use a 2D array to display that kind of table.
Then the $data array might be like this:
$data = array(
0 => array('id' => 8, 'costData' => array(
0 => array('month' => 1, 'cost' => 12500),
1 => array('monnt' => 2, 'cost' => 14200)
),
1 => array('id' => 9, 'costData' => array(
0 => array('month' => 1, 'cost' => 23000),
1 => array('month' => 2, 'cost' => 18000)
)
);
To revert the $data array like this, the code below will be needed.
<?php
$newData = array();
for($i = 0; $i < count($data); $i++)
{
$newItem = array('id' => $data[$i]['id'], 'costData' => array(
'month' => $data[$i]['month'],
'cost' => $data[$i]['cost']
));
for($j = $i + 1; $j < count($data); $j++)
{
if($data[$j]['id'] == $data[$i]['id']) {
$newItem['costData'][] = array('month' => $data[$j]['month'], 'cost' => $data[$j]['cost']);
array_splice($data, $j, 1);
$j--;
}
}
$newData[] = $newItem;
}
Using new data Array, you can make your html page like this.
$html = '';
for($i = 0; $i < count($newData); $i++)
{
$html .= '<tr>';
$html .= '<td>'.($newData[$i]['id']).'</td>';
for($j = 0; $j < count($newData[$i]['costData']); $j++) {
$html .= '<td>'.($newData[$i]['costData'][$j]).'</td>';
// you can do some operations if there are missing months to leave blank td tags..
}
// fill the missing months to fill the td tags.
}
You could use a combination of the array_column() and array_sum() functions:
$data = array(
array('id' => 8, 'month' => 1, 'cost' => 12500),
array('id' => 8, 'month' => 2, 'cost' => 14200),
array('id' => 9, 'month' => 1, 'cost' => 23000),
array('id' => 9, 'month' => 2, 'cost' => 18000),
);
$result = array();
foreach ($data as $row) {
$result[$row['id']][$row['month']] = $row['cost'];
}
// Sum the costs for each month
$months = array_column($data, 'month');
$result = array_map(function($v) use ($months) {
return array_sum(array_intersect_key($v, array_flip($months)));
}, $result);
print_r($result);
This will output:
Array
(
[8] => Array
(
[1] => 12500
[2] => 14200
)
[9] => Array
(
[1] => 23000
[2] => 18000
)
)

Loop through arrays with limitations

I have an array of transports:
$transports = [
0 => ['label' => 'Transport 1', 'maxPalettesToLoad' => 5],
1 => ['label' => 'Transport 2', 'maxPalettesToLoad' => 2]
];
and an array of palettes with certain products i want to "load" on the transport above
$palettes = [
0 => ['id_product' => 2, 'id_sale_order_item' => 1, 'amount' => 4],
1 => ['id_product' => 4, 'id_sale_order_item' => 4, 'amount' => 3]
];
What I'm struggling to accomplish is to load all the palettes onto transports where total number of loaded palettes is equal to maxPalettesToLoad parameter of each transport. I'm looking for a solution that will generate me values like:
$toret = [
'Transport 1' => [
0 => ['id_product' => 2, 'id_sale_order_item' => 1],
1 => ['id_product' => 2, 'id_sale_order_item' => 1],
2 => ['id_product' => 2, 'id_sale_order_item' => 1],
3 => ['id_product' => 2, 'id_sale_order_item' => 1],
4 => ['id_product' => 4, 'id_sale_order_item' => 4]
],
'Transport 2' => [
0 => ['id_product' => 4, 'id_sale_order_item' => 4],
1 => ['id_product' => 4, 'id_sale_order_item' => 4]
],
];
EDIT: Here is my attempt. It's kinda different in terms of data structure, because I simplified it to make it shorted
foreach ($transportsData as $transportData) {
$modelTransportOrder = new TransportOrder();
$modelTransportOrder->max_palettes_amount = $maxPalettesAmount;
$status &= $modelTransportOrder->save();
$amountAssigned = $transportData['palletes'];
if ($status) {
$j = 1;
foreach ($palettesData as $paletteData) {
if ($modelSaleOrderItem = SaleOrderItem::findOne([$paletteData['id_sale_order_item']])) {
for ($i = 0; $i < $paletteData['amount']; $i++) {
if ($j <= $amountAssigned) {
$modelTransportOrderItem = new TransportOrderItem();
$modelTransportOrderItem->id_transport_order = $modelTransportOrder->id;
$modelTransportOrderItem->id_product = $modelSaleOrderItem->id_product;
$modelTransportOrderItem->id_sale_order_item = $modelSaleOrderItem->id;
$modelTransportOrderItem->index = $j;
$status &= $modelTransportOrderItem->save();
// Index reset
if ($j == $amountAssigned) {
$j = 0;
break;
}
}
$j++;
}
} else {
$status = false;
}
}
} else {
// Zapis transportu nie powiódł się
}
}
I have managed to solve my problem using unset after each used palette:
foreach ($initedPalettes as $key => $palette) {
if ($i <= $amountAssigned) {
$modelsTransportOrderItem = self::createTransportOrderItem
(
$modelTransportOrder->id,
$palette['idProduct'],
$palette['idSaleOrderItem'],
$i
);
$status &= $modelsTransportOrderItem->save();
unset($initedPalettes[$key]);
$i++;
}
}

Combine array of objects using keys

I want to combine two arrays of objects. Let me give you an example:
Example:
// First array:
$array1 = [
{ name => 'Joe', p_id => 1 },
{ name => 'Bob', p_id => 2 },
{ name => 'Sam', p_id => 4 }
]
// Second array:
$array2 = [
{ id => 1, name => 'X' },
{ id => 2, name => 'Y' },
{ id => 4, name => 'Z' }
]
Expected output:
$output = [
{ name => 'Joe + X', id => 1 },
{ name => 'Bob + Y', id => 2 },
{ name => 'Sam + Z', id => 4 }
]
Goal:
I want the fastest possible way to combine the name property in the second array with the name property in the first array.
Note: The p_id property in the first array is the same as the id property in the second array.
What i try:
I've used nested loops that have a very low speed.
array_map is the solution!
Given:
$first = [
{ name => 'Joe', p_id => 1 },
{ name => 'Bob', p_id => 2 },
{ name => 'Sam', p_id => 4 },
];
$second = [
{ id => 1, name => 'X' },
{ id => 2, name => 'Y' },
{ id => 4, name => 'Z' },
];
The solution is just simply:
$result = array_map(
static function (\stdClass $first, \stdClass $second): array {
return [
'name' => $first->name . ' + ' . $second->name,
'id' => $first->p_id,
];
},
$first, $second
);
PS: I assume the objects are \stdClass, replace it by the correct one.
Here is a solution for when the ids of the elements inside the array are not in order. Notice that I have changed the order of $array1. Just a bit better than the regular nested loops, on the loop of the $array2 it will remove the "found" elements to improve speed of the next loop.
From:
// First array:
$array1 = [
(object) ['name' => 'Joe', 'p_id' => 1],
(object) ['name' => 'Sam', 'p_id' => 4],
(object) ['name' => 'Bob', 'p_id' => 2],
];
// Second array:
$array2 = [
(object) ['id' => 1, 'name' => 'X'],
(object) ['id' => 2, 'name' => 'Y'],
(object) ['id' => 4, 'name' => 'Z'],
];
Solution:
$result = [];
foreach ($array1 as $array1Element) {
for ($i=0;$i<count($array2);$i++) {
if ($array1Element->p_id === $array2[$i]->id) {
$array2[$i]->name = $array1Element->name . ' + ' . $array2[$i]->name;
$result[] = $array2[$i];
unset($array2[$i]);
$array2 = array_values($array2);
break;
}
}
}

reducing an array into another array

Inside a laravel blade template, I am trying to reduce an array like this:
$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];
into something like this:
$reducedList = [['count' => 2, 'kg' => 2], ['count' => 1, 'kg' => 3]];
This is what I have so far:
#php
$variableWeights = isset($sale->variable_weight_json) ? collect(json_decode($sale->variable_weight_json, true)) : null;
$groups = array();
if (isset($variableWeights)) {
$groups = $variableWeights->reduce(function($carry, $item) {
$index = array_search($item['kg'], array_column($carry, 'weight'));
if (isset($index)) {
$existing = $carry[$index];
array_splice($carry, $index, 1, [
'count' => $existing['count'] + 1,
'weight' => $item['kg']
]);
} else {
array_push($carry, [
'count' => 1,
'weight' => $item['kg'],
]);
}
return $carry;
}, array());
}
#endphp
But it is giving me the error Undefined offset: 0
I am new to php. How should the code be corrected or is there a better approach to achieve the desired result?
Why dont you reduce it to something simpler like this
$reducedList = [2 => 2, 3 => 1]
where the weight is the index and the value is the count.
$reducedList = [];
foreach ($longList as $box) {
if (isset($reducedList[$box['kg']]) {
$reducedList[$box['kg']]++;
} else {
$reducedList[$box['kg']] = 1;
}
}
This way you avoid complexity but you still get the same amount of information.
I guess you can achieve it with code like:
$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];
$reducedList = array_values(array_reduce(
$longList,
function($carry, $item) {
if (isset($carry[$item['kg']])) {
++$carry[$item['kg']]['count'];
} else {
$carry[$item['kg']] = ['count' => 1, 'kg' => $item['kg']];
}
return $carry;
},
[]
));
print_r($reducedList);
Here is a working example.
don't use isset() function. it checks only variable existing. use empty() or other condition, it will check variable existing and value. try this.
#php
$variableWeights = isset($sale->variable_weight_json) ? collect(json_decode($sale->variable_weight_json, true)) : null;
$groups = array();
if ($variableWeights->isNotEmpty()) {
$groups = $variableWeights->reduce(function($carry, $item) {
$index = array_search($item['kg'], array_column($carry, 'weight'));
if ($index != false ) {
$existing = $carry[$index]?: false;
if ($existing) {
array_splice($carry, $index, 1, [
'count' => $existing['count'] + 1,
'weight' => $item['kg']
]);
}
} else {
array_push($carry, [
'count' => 1,
'weight' => $item['kg'],
]);
}
return $carry;
}, array());
}
#endphp
You can use the sexy countBy() method (combined with the famous map() one) of the Collection class.
Try this:
$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];
$shortList = collect($longList)
->countBy('kg')
->map(function ($count, $kg) {
return [
'kg' => $kg,
'count' => $count,
];
});
With that, you'll get this:
dd($shortList);
=> Illuminate\Support\Collection {#3380
all: [
2 => [
"kg" => 2,
"count" => 2,
],
3 => [
"kg" => 3,
"count" => 1,
],
],
}
Here you have a working demo.
isset checks if variable is set (i.e it exists and not NULL). As I (and core developers) can hardly imagine in which cases array_search can return NULL, there's a manual which says:
Returns the key for needle if it is found in the array, FALSE otherwise.
So, you need to check if $index is not false:
$index = array_search($item['kg'], array_column($carry, 'weight'));
// Note that I use `!==` because using `!=` will convert
// `0` index to false, which is not correct in your case
if (false !== $index) {

How to compare array a with b and create new arrays c (items in a and b), d (items in a, not in b), and e (items in b, not in a)?

Consider the following arrays. They represent the top-5 performing employees for yesterday and today:
$yesterday = array(
6 => array('name' => 'Tod', 'score' => 9.5),
12 => array('name' => 'Jim', 'score' => 7.3),
18 => array('name' => 'Bob', 'score' => 8.4),
7 => array('name' => 'Jan', 'score' => 6.2),
20 => array('name' => 'Sam', 'score' => 6.0),
);
$today = array(
6 => array('name' => 'Tod', 'score' => 9.1),
9 => array('name' => 'Jef', 'score' => 9.3),
35 => array('name' => 'Axl', 'score' => 7.6),
7 => array('name' => 'Jan', 'score' => 6.5),
41 => array('name' => 'Ted', 'score' => 8.0),
);
I need 3 new arrays compiled from the above: $stay holding employees who were in the top-5 yesterday, and still are today, $gone, holding employees who were in the top-5 yesterday, but are not anymore, and $new, holding newcomers to $todays top-5 list:
// notice that the scores in $stay come from $today, not $yesterday
// also notice that index keys are maintained
$stay = array(
6 => array('name' => 'Tod', 'score' => 9.1),
7 => array('name' => 'Jan', 'score' => 6.5)
);
$gone = array(
12 => array('name' => 'Jim', 'score' => 7.3),
18 => array('name' => 'Bob', 'score' => 8.4),
20 => array('name' => 'Sam', 'score' => 6.0)
);
$new = array(
9 => array('name' => 'Jef', 'score' => 9.3),
35 => array('name' => 'Axl', 'score' => 7.6),
41 => array('name' => 'Ted', 'score' => 8.0)
);
I have no clue on how to build the logic here. I started with a loop, but didn't get far. I believe it should be something like this. Could you help me get this right?
for ($i = 0; $i < count($yesterday); $i++) {
// I'm comparing key numbers, but not key values
// how do I compare key values?
if (in_array($yesterday[$i], $today) {
// add to $stay array
}
else {
// add to $gone array
}
}
for ($i = 0; $i < count($today); $i++) {
if (!in_array($today[$i], $yesterday) {
// add to $new array
}
}
P.S. I don't know if this is helpfull, but $yesterday and $today are always of equal length (in this case, 5 items, but other scenarios are possible in which both arrays hold 7 or 10 items for instance). The combined items of $stay and $new logically are always equal to the number of items in either $yesterday or $today :-)
https://www.php.net/manual/function.array-intersect.php
https://www.php.net/manual/function.array-diff.php
$c = array_intersect($a, $b);
$d = array_diff($a, $b);
$e = array_diff($b, $a);
This can be accomplished using just a few of PHP's many powerful array functions:
$stay = array_intersect_assoc($yesterday, $today);
$gone = array_diff_assoc($yesterday, $today);
$new = array_diff_assoc($today, $yesterday);
I think this should work but isnt the fastest method to do this i think.
$stay = array();
$gone = array();
$new = array();
$found = false;
foreach($yesterday as $yKey)
{
$found = false;
foreach($today as $tKey)
{
if($tKey['name'] == $yKey['name'])
{
$found = true;
$stay[]['name'] = $tKey['name'];
break;
}
else
{
$found = false;
}
}
if($found == false)
{
$gone[]['name'] = $yKey['name'];
}
}
foreach($today as $tKey)
{
$found = false;
foreach($yesterday as $yKey){
if($yKey['name'] == $tKey['name'])
{
$found = true;
break;
}
else{
$found = false;
}
}
if($found == false)
{
$gone[]['name'] = $tKey['name'];
}
}

Categories