Algorithm for exclude combinations with unique value using php - php

The algoritham i'm trying different combinations of values will able to give my exact or approx output sum of values
I have attached image for the detail explanation , I have created column total as sum of each row value and finally I have sum all the total value, the whole total sum value is to be my expected output value.
So I'm trying to take a combination of each row sum and like to get total sum value
My algorithm i have searched in google below
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;
}
}
}
}
echo "<pre>";
$sum = 32 ; //SUM
$array = array(5.14327,5.72355,5.91794,4.8209,8.69933,4.12977,4.12977,2.92791,2.36829,2.21819,1.33759,1.72278,1.72278,0.589,1.06405,0.6387,0.6387,1.68995,2.51669,3.97842,2.38058,2.17175,4.88264,5.84811,6.14215);
$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
print_r($list);
Attached Image here

You need to decide how you determine approximate equality. A percentage? Or an absolute amount? That's what you need in your filter lambda function.
// outside lambda
$error = $sum * 5 / 100;// 5%, or
$error = 0.02;// an absolute
...
// inside lambda
return abs(array_sum($var) - $sum) <= $error;

Related

How can I find not just duplicates in array, but numbers that match a range in PHP?

I need to find not just duplicates in an array, but numbers that are within 1 of each other.
example: $myArr = array(46,78,77,43,86,1,47,14,51,31)
How would I get find those numbers if there no duplicates were found?
Thanks.
This will work - tested:
$myArr = array(46,78,77,43,86,1,47,14,51,31);
$i = 1;
while($i <= 99){
if(in_array($i, $myArr)){
// This means it's in the array
// Let's see if it's +1 of the last #
if(isset($last_num)){
$res = ($i + 1);
if(in_array($res, $myArr)){
echo $res . ' is in the array<br>';
}
}
}
$last_num = $i;
$i++;
}
Could use something like this.
<?
$haystack = array(31,46,78,77,43,86,1,47,14,51,31);
array_walk($haystack, function($key, $value) use ($haystack) {
$needle = array($key - 1, $key, $key + 1);
$r = array_intersect($haystack, $needle);
if(count($r) > 1) { unset($r[$value]); print_r(array_values($r)); }
});
?>
Or if you want them returned in an array:
<?
$haystack = array(31,46,78,77,43,86,1,47,14,51,31);
$result = array_filter(array_map(function($key, $value) use ($haystack) {
$needle = array($key - 1, $key, $key + 1);
$r = array_intersect($haystack, $needle);
if(count($r) > 1) { unset($r[$value]); return array_values($r)['0']; }
}, $haystack));
print_r($result);
?>
A little more verbose, but it may be more efficient since we're only looping through the array when necessary. It will return the keys of the original array where duplicates and values within range are found. (Note: I've expanded the code out a lot more than I normally would just for readability for OP)
/**
* Find the any values within an array that are within
* the specified range from another value.
*
* #param Array Array to search in
* #param uint Unsigned integer used for comparison
* #return Array The keys of all found values
**/
function findWithinRange(Array $array, $range) {
// sort the array values numerically
// so we don't have to loop through
// the entire array for each check
asort($array, SORT_NUMERIC);
// Keep the keys in case it's an associative array
$keys = array_keys($array);
// Get the values without the keys so we can easily loop
$values = array_values($array);
$return = array();
// Loop over each item in the array
foreach( $values as $k => $v ) {
$start = $k;
$min = $v - $range;
$max = $v + $range;
// track backwards in the array until we're less than range
// We could use array_search, but we'd be calling it multiple times
// This only runs through the array once and dies the moment
// it is out of the specified range
while(true) {
$k--;
if( !isset($values[$k]) ) {
break; // don't continue if we run out of keys
}
$curVal = $values[$k];
if( $curVal >= $min ) {
$return[] = $keys[$k];
}
else {
break; // kill the while loop if we're outside of range
}
}
// reset
$k = $start;
// track forward in the array until we're greater than range
while(true) {
$k++;
if( !isset($values[$k]) ) {
break; // don't continue if we run out of keys
}
$curVal = $values[$k];
if( $curVal <= $max ) {
$return[] = $keys[$k];
}
else {
break; // kill the while loop if we're outside of range
}
}
}
// drop duplicate reports
$return = array_unique($return);
// return all found keys
return $return;
}
Example usage:
$myArr = array(46,78,77,43,86,1,47,14,51,31);
$res = findWithinRange($myArr, 1);
var_export($res);
// array(6, 0, 1, 2)

Issue with custom script to sort Arrays ascending and descending order

I have an issue to deal with here (a logical error in my code 99%). I just can't seem to find the way to fix it, but I bet one of you will find the problem in no time!
I have to create a function which sorts array passed to it in asc or desc order, but can't use any array sorting functions !
I've been struggling with loops until now and I finally want to ask help from other devs ( you ).
Currently only code for ascending is worked on, descending will be no problem I assume once I do this one. It kinda of does sort values up to some point, but then stops ( it stops if the next smallest value is at the end of the passed array ). What could I do to prevent this and make it sort the whole array and it's elements?
Here is the code so far.
<?php
function order_array($array,$mode = 'ascending') {
$length = count($array);
if($mode == 'descending') {
return $array;
} else {
$sorted_array = array();
$used_indexes = array();
for($i = 0; $i < $length; $i++) {
$smallest = true;
echo $array[$i] . '<br/>';
for($y = 0; $y < $length; $y++) {
//echo $array[$i] . ' > ' . $array[$y] . '<br/>';
// if at ANY time during checking element vs other ones in his array, he is BIGGER than that element
// set smallest to false
if(!in_array($y,$used_indexes)) {
if($array[$i] > $array[$y]) {
$smallest = false;
break;
}
}
}
if($smallest) {
$sorted_array[] = $array[$i];
$used_indexes[] = $i;
}
}
return $sorted_array;
}
}
$array_to_sort = array(1, 3, 100, 99, 33, 20);
$sorted_array = order_array($array_to_sort);
print_r($sorted_array);
?>
I've solved the issue myself by doing it completely different. Now it sorts correctly all the elements of the passed in array. The logical issue I had was of using for() loop. The for() loop ran only a set ( length of passed array ) number of times, while we need it to loop more than that, because we will need to loop all the way untill we have a new sorted array in ascending order. Here is the code that will work
function order_array($array,$mode = 'ascending') {
if($mode == 'descending') {
// for() wont work here, since it will only loop an array length of times, when we would need it
// to loop more than that.
while(count($array)){
$value = MAX($array);
$key = array_search($value, $array);
if ($key !== false) {
unset($array[$key]);
}
$sorted[] = $value;
}
return $sorted;
} else {
// for() wont work here, since it will only loop an array length of times, when we would need it
// to loop more than that.
while(count($array)){
$value = MIN($array);
$key = array_search($value, $array);
if ($key !== false) {
unset($array[$key]);
}
$sorted[] = $value;
}
return $sorted;
}
}
function order_array($array,$mode = 'ascending') {
$length = count($array);
$sorted_array = array();
$used_indexes = array();
for($i = 0; $i < $length; $i++) {
$smallest = true;
echo $array[$i] . '<br/>';
for($y = 0; $y < $length; $y++) {
//echo $array[$i] . ' > ' . $array[$y] . '<br/>';
// if at ANY time during checking element vs other ones in his array, he is BIGGER than that element
// set smallest to false
if(!in_array($y,$used_indexes)) {
if($array[$i] > $array[$y]) {
$smallest = false;
break;
}
}
}
if($smallest) {
$sorted_array[] = $array[$i];
$used_indexes[] = $i;
}
if($mode == 'descending') {
return array_reverse($sorted_array);
}
return $sorted_array;
}
}

Php consolidate number (zip codes)

I have a php array with zipcodes returned from a db query. Zip Codes are in German format so 5 digit long.
Example:
array ('90475', '90419', '90425', '90415', '90429', '90479', '90485');
I would like to consolidate the values to "ranges" with placeholder, like:
array ('90...', '904..', '9041.', '9042', '9047.');
//90485 is left out because there is only 1 match.
Edit / logic:
This is for an autosuggestion search. Trying to build a tree so users can search for entries that match any zipcode starting with 90 or 904, etc.. For the autocomplete to make sense I only want to provide the "9041." value if there is a minimum of two entries matching (90419 and 90415 in example). The zipcodes are always 5 digit long from 00000 - 99999.
Highly appreciate any help.
Thanks.
Here you are:
$length = 5;
$zip = array('90475', '90419', '90425', '90415', '90429', '90479', '90485');
$result = array();
for ($i = 2; $i <= $length - 1; $i++) {
$pass = array();
foreach ($zip as $val) {
$pass[substr($val, 0, $i)]++;
}
foreach ($pass as $key => $val) {
if ($val > 1) {
$result[] = $key.str_repeat('.', $length - $i);
}
}
}
sort($result);
var_dump($result);
This will return in $result an array:
array ('90...', '904..', '9041.', '9042', '9047.');
Every range, which is used only once will be ignored and not returned in $result array.
$myArray = array ('90475', '90419', '90425', '90415', '90429', '90479', '90485');
$consolidate = array();
foreach($myArray as $zip) {
for ($c = 2; $c < 5; ++$c) {
$key = substr($zip, 0, $c) . str_repeat('.',5 - $c);
$consolidate[$key] = (isset($consolidate[$key])) ? $consolidate[$key] + 1 : 1;
}
}
$consolidate = array_filter(
$consolidate,
function ($value) {
return $value > 1;
}
);
var_dump($consolidate);

PHP: Efficiently Search through Collections

I have collections of numbers (arbitrary order) to store.
psuedocode:
id_a:[3,5,7,11]
id_x:[3,5,10,21]
id_b:[12,24,25,26]
etc.
I need to be able to search through all the collections and return the group_IDs.
For example, if I look up 5, I should get back ['id_a','id_x']. I want to do this efficiently with some sort of mapping, not by looping through all numbers of all collections. I also want to be able to map directly to each key and get back the collection (e.g., 'id_x' returns [3,5,10,21]) ; again I prefer this be done efficiently without looping through the keys.
edit:
I could use the numbers as the keys and efficiently get back 'id_'. Or, I could go the other way and use 'id_' as keys and efficiently get back the array of numbers. However, I want to be able to go efficiently in both directions. I guess I could maintain two arrays, but that seems messy.
Your examples all show the array values in sorted order. If they are always in sorted order, then you can use a binary search to find known values. This code:
function binarySearch($needle, array $haystack) {
$high = count($haystack) - 1;
$low = 0;
$mid = false;
while ($high >= $low) {
$mid = ($high + $low) >> 1;
$t = $needle - $haystack[$mid];
if ($t < 0) {
$high = $mid - 1;
} elseif ($t > 0) {
$low = $mid + 1;
} else {
return $mid;
}
}
return $mid;
}
function searchArrays($needle) {
static $id_a = array(3,5,7,11);
static $id_x = array(3,5,10,21);
static $id_b = array(12,24,25,26);
static $arrayNames = array('id_a', 'id_x', 'id_b');
$rv = array();
foreach ($arrayNames as $arrayName) {
$array = $$arrayName;
$index = binarySearch($needle, $array);
if ($array[$index] == $needle) {
$rv[] = $arrayName;
}
}
return $rv;
}
$needles = range(3,8);
foreach ($needles as $needle) {
$result = searchArrays($needle);
printf("searchArrays(%s)=%s\n", $needle, join(', ', $result));
}
will output the following:
searchArrays(3)=id_a, id_x
searchArrays(4)=
searchArrays(5)=id_a, id_x
searchArrays(6)=
searchArrays(7)=id_a
searchArrays(8)=

What are better ways to insert element in sorted array in PHP

I've recently send my CV to one company that was hiring PHP developers. They send me back a task to solve, to mesure if I'm experienced enough.
The task goes like that:
You have an array with 10k unique elements, sorted descendant. Write function that generates this array and next write three different functions which inserts new element into array, in the way that after insert array still will be sorted descendant. Write some code to measure speed of those functions. You can't use PHP sorting functions.
So I've wrote function to generate array and four functions to insert new element to array.
/********** Generating array (because use of range() was to simple :)): *************/
function generateSortedArray($start = 300000, $elementsNum = 10000, $dev = 30){
$arr = array();
for($i = 1; $i <= $elementsNum; $i++){
$rand = mt_rand(1, $dev);
$start -= $rand;
$arr[] = $start;
}
return $arr;
}
/********************** Four insert functions: **************************/
// for loop, and array copying
function insert1(&$arr, $elem){
if(empty($arr)){
$arr[] = $elem;
return true;
}
$c = count($arr);
$lastIndex = $c - 1;
$tmp = array();
$inserted = false;
for($i = 0; $i < $c; $i++){
if(!$inserted && $arr[$i] <= $elem){
$tmp[] = $elem;
$inserted = true;
}
$tmp[] = $arr[$i];
if($lastIndex == $i && !$inserted) $tmp[] = $elem;
}
$arr = $tmp;
return true;
}
// new element inserted at the end of array
// and moved up until correct place
function insert2(&$arr, $elem){
$c = count($arr);
array_push($arr, $elem);
for($i = $c; $i > 0; $i--){
if($arr[$i - 1] >= $arr[$i]) break;
$tmp = $arr[$i - 1];
$arr[$i - 1] = $arr[$i];
$arr[$i] = $tmp;
}
return true;
}
// binary search for correct place + array_splice() to insert element
function insert3(&$arr, $elem){
$startIndex = 0;
$stopIndex = count($arr) - 1;
$middle = 0;
while($startIndex < $stopIndex){
$middle = ceil(($stopIndex + $startIndex) / 2);
if($elem > $arr[$middle]){
$stopIndex = $middle - 1;
}else if($elem <= $arr[$middle]){
$startIndex = $middle;
}
}
$offset = $elem >= $arr[$startIndex] ? $startIndex : $startIndex + 1;
array_splice($arr, $offset, 0, array($elem));
}
// for loop to find correct place + array_splice() to insert
function insert4(&$arr, $elem){
$c = count($arr);
$inserted = false;
for($i = 0; $i < $c; $i++){
if($elem >= $arr[$i]){
array_splice($arr, $i, 0, array($elem));
$inserted = true;
break;
}
}
if(!$inserted) $arr[] = $elem;
return true;
}
/*********************** Speed tests: *************************/
// check if array is sorted descending
function checkIfArrayCorrect($arr, $expectedCount = null){
$c = count($arr);
if(isset($expectedCount) && $c != $expectedCount) return false;
$correct = true;
for($i = 0; $i < $c - 1; $i++){
if(!isset($arr[$i + 1]) || $arr[$i] < $arr[$i + 1]){
$correct = false;
break;
}
}
return $correct;
}
// claculates microtimetime diff
function timeDiff($startTime){
$diff = microtime(true) - $startTime;
return $diff;
}
// prints formatted execution time info
function showTime($func, $time){
printf("Execution time of %s(): %01.7f s\n", $func, $time);
}
// generated elements num
$elementsNum = 10000;
// generate starting point
$start = 300000;
// generated elements random range 1 - $dev
$dev = 50;
echo "Generating array with descending order, $elementsNum elements, begining from $start\n";
$startTime = microtime(true);
$arr = generateSortedArray($start, $elementsNum, $dev);
showTime('generateSortedArray', timeDiff($startTime));
$step = 2;
echo "Generating second array using range range(), $elementsNum elements, begining from $start, step $step\n";
$startTime = microtime(true);
$arr2 = range($start, $start - $elementsNum * $step, $step);
showTime('range', timeDiff($startTime));
echo "Checking if array is correct\n";
$startTime = microtime(true);
$sorted = checkIfArrayCorrect($arr, $elementsNum);
showTime('checkIfArrayCorrect', timeDiff($startTime));
if(!$sorted) die("Array is not in descending order!\n");
echo "Array OK\n";
$toInsert = array();
// number of elements to insert from every range
$randElementNum = 20;
// some ranges of elements to insert near begining, middle and end of generated array
// start value => end value
$ranges = array(
300000 => 280000,
160000 => 140000,
30000 => 0,
);
foreach($ranges as $from => $to){
$values = array();
echo "Generating $randElementNum random elements from range [$from - $to] to insert\n";
while(count($values) < $randElementNum){
$values[mt_rand($from, $to)] = 1;
}
$toInsert = array_merge($toInsert, array_keys($values));
}
// some elements to insert on begining and end of array
array_push($toInsert, 310000);
array_push($toInsert, -1000);
echo "Generated elements: \n";
for($i = 0; $i < count($toInsert); $i++){
if($i > 0 && $i % 5 == 0) echo "\n";
printf("%8d, ", $toInsert[$i]);
if($i == count($toInsert) - 1) echo "\n";
}
// functions to test
$toTest = array('insert1' => null, 'insert2' => null, 'insert3' => null, 'insert4' => null);
foreach($toTest as $func => &$time){
echo "\n\n================== Testing speed of $func() ======================\n\n";
$tmpArr = $arr;
$startTime = microtime(true);
for($i = 0; $i < count($toInsert); $i++){
$func($tmpArr, $toInsert[$i]);
}
$time = timeDiff($startTime, 'checkIfArraySorted');
showTime($func, $time);
echo "Checking if after using $func() array is still correct: \n";
if(!checkIfArrayCorrect($tmpArr, count($arr) + count($toInsert))){
echo "Array INCORRECT!\n\n";
}else{
echo "Array OK!\n\n";
}
echo "Few elements from begining of array:\n";
print_r(array_slice($tmpArr, 0, 5));
echo "Few elements from end of array:\n";
print_r(array_slice($tmpArr, -5));
//echo "\n================== Finished testing $func() ======================\n\n";
}
echo "\n\n================== Functions time summary ======================\n\n";
print_r($toTest);
Results can be found here: http://ideone.com/1xQ3T
Unfortunately I was rated only 13 points out of 30 for this task (don't know how it was calculated or what exactly was taken in account). I can only assume that's because there are better ways to insert new element into sorted array in PHP. I'm searching this topic for some time now but couldn't find anything good. Maby you know of better approach or some articles about that topic?
Btw on my localhost (PHP 5.3.6-13ubuntu3.6 with Suhosin-Patch, AMD Athlon(tm) II X4 620) insert2() is fastest, but on ideone (PHP 5.2.11) insert3() is fastest.
Any ideas why? I suppose that array_splice() is tuned up somehow :).
//EDIT
Yesterday I thought about it again, and figured out the better way to do inserts. If you only need sorted structure and a way to iterate over it and your primary concern is the speed of insert operation, than the best choise would be using SplMaxHeap class. In SplMaxHeap class inserts are damn fast :) I've modified my script to show how fast inserts are. Code is here: http://ideone.com/vfX98 (ideone has php 5.2 so there won't be SplMaxHeap class)
On my localhost I get results like that:
================== Functions time summary ======================
insert1() => 0.5983521938
insert2() => 0.2605950832
insert3() => 0.3288729191
insert4() => 0.3288729191
SplMaxHeap::insert() => 0.0000801086
It may just be me, but maybe they were looking for readability and maintainability as well?
I mean, you're naming your variables $arr, and $c and $middle, without even bothering to place proper documentation.
Example:
/**
* generateSortedArray() Function to generate a descending sorted array
*
* #param int $start Beginning with this number
* #param int $elementsNum Number of elements in array
* #param int $dev Maximum difference between elements
* #return array Sorted descending array.
*/
function generateSortedArray($start = 300000, $elementsNum = 10000, $dev = 30) {
$arr = array(); #Variable definition
for ($i = 1; $i <= $elementsNum; $i++) {
$rand = mt_rand(1, $dev); #Generate a random number
$start -= $rand; #Substract from initial value
$arr[] = $start; #Push to array
}
return $arr;
}

Categories