Topological sorting in PHP - php

I found this topological sorting function for PHP:
Source: http://www.calcatraz.com/blog/php-topological-sort-function-384/
function topological_sort($nodeids, $edges) {
$L = $S = $nodes = array();
foreach($nodeids as $id) {
$nodes[$id] = array('in'=>array(), 'out'=>array());
foreach($edges as $e) {
if ($id==$e[0]) { $nodes[$id]['out'][]=$e[1]; }
if ($id==$e[1]) { $nodes[$id]['in'][]=$e[0]; }
}
}
foreach ($nodes as $id=>$n) { if (empty($n['in'])) $S[]=$id; }
while (!empty($S)) {
$L[] = $id = array_shift($S);
foreach($nodes[$id]['out'] as $m) {
$nodes[$m]['in'] = array_diff($nodes[$m]['in'], array($id));
if (empty($nodes[$m]['in'])) { $S[] = $m; }
}
$nodes[$id]['out'] = array();
}
foreach($nodes as $n) {
if (!empty($n['in']) or !empty($n['out'])) {
return null; // not sortable as graph is cyclic
}
}
return $L;
}
I looks nice and short. Anyways, for some input - I get duplicate lines in the output - see http://codepad.org/thpzCOyn
Generally the sorting seems to be correct if I remove the duplicates with array_unique()
I checked the function with two examples and the sorting itself looks correct.
Should I just call array_unique() on the result?

I'm the author of the original topological sorting function. Thanks to Alex for bringing the duplicate edge issue to my attention. I've updated the function to correctly remove duplicate edges and nodes. The updated version is here:
http://www.calcatraz.com/blog/php-topological-sort-function-384 (Same as original link)
I added the following to achieve the deduplication:
// remove duplicate nodes
$nodeids = array_unique($nodeids);
// remove duplicate edges
$hashes = array();
foreach($edges as $k=>$e) {
$hash = md5(serialize($e));
if (in_array($hash, $hashes)) { unset($edges[$k]); }
else { $hashes[] = $hash; };
}
I had to serialize the edges to make sure duplicates were removed correctly. I also tidied the rest of the function up a bit and added some comments.

You get duplicate lines because there are duplicate edges. I'm no graph theory thug but I'm pretty sure this is not legal :
0 =>
array (
0 => 'nominal',
1 => 'subtotal',
),
2 =>
array (
0 => 'nominal',
1 => 'subtotal',
),
...
You can either add a test in the part that constructs the nodes, something like this :
if ($id==$e[0] && !in_array($e[1], $nodes[$id]['out']))
{
$nodes[$id]['out'][]=$e[1];
}
if ($id==$e[1] && !in_array($e[0], $nodes[$id]['in'])) // Not needed but cleaner
{
$nodes[$id]['in'][]=$e[0];
}
... or just make sure you don't pass duplicate edges to the function. :P

Related

Foreach last item gets methode

Guys I have an array with objects,
I want the last item in the foreach loop do something else then the rest.
How do I archive that?
if(sizeof($testDup) > 3){
} else {
foreach ($testDup as $d) {
}
}
$test array(3)
432 => test_id -> 21
431 => test_id -> 21
435 => test_id -> 21
This will process the array of objects and do something else with the last element:
$data = '';
$arrayWithObjects = array(
(object)array('test1', 'test2'),
(object)array('test1', 'test2'),
(object)array('test1', 'test2'),
);
foreach ($arrayWithObjects as $object) {
// Can't get next in the array, so is last element
if (!next($arrayWithObjects)) {
// Process last element
$data .= $object->{1};
} else {
// Process all other elements
$data .= $object->{0};
}
}
var_dump($data); // "test1test1test2"
you can compare the current one with the end():
class Test {
public function __construct(private string $name) {}
public function read(): string {
return sprintf('%s: hurray', $this->name);
}
public function readLast():string {
return sprintf('%s: am I last?', $this->name);
}
}
$array = [
new Test('first'),
new Test('second'),
new Test('third'),
new Test('fourth'),
];
foreach( $array as $object ){
if($object === end($array)) {
echo $object->readLast().PHP_EOL;
}else{
echo $object->read().PHP_EOL;
}
}
As an alternative to checking if the current item is the last one (which the other answers show), you could use array_slice() to get the start of the array to loop over and then end() to get the last element of the array.
$data = [/*...*/]
foreach ($item as array_splice($data, 0, -1, true) {
$item->foo();
}
if (($item = end($data) !== false) {
$item->bar();
}
In my opinion, this code is easier to read (and metrics like cyclomatic complexity agree) than the nested if $item === end($data) check. If the same is true on your specific case will depend on what, exactly is in the loop and how much of it is different.
In addition, if your array is large, this approach may offer (slightly) better performance (but if your array is large and a small performance difference is important, don't take my word for this - benchmark both solutions with read data).
It's so easy: When the loop is finished, you still got the last element!!
if (!empty($arr)) {
foreach ($arr as $item) {
; // Do something with $item
}
// Here you still got last $item
echo var_export($item, true);
}

How to recursively build a list of Item Substitutions when multiple substitutes exist

In Microsoft Dynamics Nav 2013, there is a feature for specifying Item Substitutions for an item (product); However, you can specify more than one substitution for a single product and technically a substitution can itself have one or more substitutions.
I am trying to build a recursive solution in PHP that allows me to take a known product code, and recursively search for item substitutions to generate a one dimensional array of items. If this were a one to one relationship (parent,child) this would be a trivial task for me, but the fact that there can be multiple childs on any given iteration is kind of blowing my mind.
My question is if anyone knows how to write a recursive method for the situation I've described above? Below I will layout the way the data is structured to give a better understanding of the problem:
$lineItems = array(
'XXX-0',
'XXX-1',
'XXX-3'
);
$substitutionsLookup = array(
0 => array('No_' => 'XXX-1', 'Substitute No_' => 'XXX-2'),
1 => array('No_' => 'XXX-3', 'Substitute No_' => 'XXX-4'),
2 => array('No_' => 'XXX-3', 'Substitute No_' => 'XXX-5'),
3 => array('No_' => 'XXX-5', 'Substitute No_' => 'XXX-6')
);
// Resulting product code substitutions for XXX-0
$result1 = array();
// Resulting product code substitutions for XXX-1
$result2 = array('XXX-2');
// Resulting product code substitutions for XXX-3
$result3 = array('XXX-4', 'XXX-6');
Edit (added my attempt to solve using recursive method):
protected function getSubstitutions($haystack, $needle, &$results = array())
{
if (count($haystack) == 0)
{
return false;
}
$matches = array();
foreach ($haystack as $index => $check)
{
if ($check['No_'] === $needle)
{
$newHaystack = $haystack;
unset($newHaystack[$index]);
$moreMatches = $this->getSubstitutions($newHaystack, $check['Substitute No_'], $results);
if ($moreMatches === false)
{
$matches[] = $check['Substitute No_'];
}
}
}
if (count($matches))
{
foreach($matches as $match)
{
$results[] = $match;
}
}
return $results;
}
Edit (Final code used, derived from accepted answer):
class ItemSubstitutionService implements ServiceLocatorAwareInterface
{
public function getSubstitutions($itemNo, $noInventoryFilter = true, $recursive = true)
{
$substitutions = array();
$directSubs = $this->itemSubstitutionTable->getSubstitutionsByNo($itemNo);
if ($recursive)
{
foreach($directSubs as $sub)
{
$this->getSubstitutionsRecursive($sub, $substitutions);
}
} else {
$substitutions = $directSubs;
}
foreach($substitutions as $index => $sub)
{
$inventory = $this->itemLedgerEntryTable->getQuantityOnHand($sub->getSubstituteNo());
$sub->setInventory($inventory);
if ($noInventoryFilter)
{
if ($inventory == 0)
{
unset($substitutions[$index]);
}
}
}
return $substitutions;
}
private function getSubstitutionsRecursive(ItemSubstitution $sub, &$subs)
{
$directSubs = $this->itemSubstitutionTable->getSubstitutionsByNo($sub->getSubstituteNo());
if (empty($directSubs))
{
$subs[$sub->getSubstituteNo()] = $sub;
}
foreach($directSubs as $curSub)
{
$this->getSubstitutionsRecursive($curSub, $subs);
}
}
}
This code could serve as a solution for your example.
I just presume that you fetch the list of 'direct' items substitutes from your database, so you could replace GetDirectSubstitutes with the code that fetches the substitutes list for the given item (I used you example array as a data source).
Just be careful - this simple implementation doesn't check for cyclical references. If your initial data contains loops, this code will stuck.
function GetDirectSubstitutes($itemNo)
{
global $substitutionsLookup;
$items = array();
foreach ($substitutionsLookup as $itemPair) {
if ($itemPair['No'] == $itemNo) {
array_push($items, $itemPair['SubstNo']);
}
}
return $items;
}
function GetSubstitutesTree($itemNo, &$substitutes)
{
$directSubst = GetDirectSubstitutes($itemNo);
if (!empty($directSubst)) {
$substitutes = array_merge($substitutes, $directSubst);
foreach ($directSubst as $item) {
GetSubstitutesTree($item, $substitutes);
}
}
}

PHP Function that can return value from an array key a dynamic number of levels deep

Using PHP, I would like to write a function that accomplishes what is shown by this pseudo code:
function return_value($input_string='array:subArray:arrayKey')
{
$segments = explode(':',$input_string);
$array_depth = count(segments) - 1;
//Now the bit I'm not sure about
//I need to dynamically generate X number of square brackets to get the value
//So that I'm left with the below:
return $array[$subArray][$arrayKey];
}
Is the above possible? I'd really appreciate some pointer on how to acheive it.
You can use a recursive function (or its iterative equivalent since it's tail recursion):
function return_value($array, $input_string) {
$segments = explode(':',$input_string);
// Can we go next step?
if (!array_key_exists($segments[0], $array)) {
return false; // cannot exist
}
// Yes, do so.
$nextlevel = $array[$segments[0]];
if (!is_array($nextlevel)) {
if (1 == count($segments)) {
// Found!
return $nextlevel;
}
// We can return $nextlevel, which is an array. Or an error.
return false;
}
array_shift($segments);
$nextsegments = implode(':', $segments);
// We can also use tail recursion here, enclosing the whole kit and kaboodle
// into a loop until $segments is empty.
return return_value($nextlevel, $nextsegments);
}
Passing one object
Let's say we want this to be an API and pass only a single string (please remember that HTTP has some method limitation in this, and you may need to POST the string instead of GET).
The string would need to contain both the array data and the "key" location. It's best if we send first the key and then the array:
function decodeJSONblob($input) {
// Step 1: extract the key address. We do this is a dirty way,
// exploiting the fact that a serialized array starts with
// a:<NUMBEROFITEMS>:{ and there will be no "{" in the key address.
$n = strpos($input, ':{');
$items = explode(':', substr($input, 0, $n));
// The last two items of $items will be "a" and "NUMBEROFITEMS"
$ni = array_pop($items);
if ("a" != ($a = array_pop($items))) {
die("Something strange at offset $n, expecting 'a', found {$a}");
}
$array = unserialize("a:{$ni}:".substr($input, $n+1));
while (!empty($items)) {
$key = array_shift($items);
if (!array_key_exists($key, $array)) {
// there is not this item in the array.
}
if (!is_array($array[$key])) {
// Error.
}
$array = $array[$key];
}
return $array;
}
$arr = array(
0 => array(
'hello' => array(
'joe','jack',
array('jill')
)));
print decodeJSONblob("0:hello:1:" . serialize($arr));
print decodeJSONblob("0:hello:2:0" . serialize($arr));
returns
jack
jill
while asking for 0:hello:2: would get you an array { 0: 'jill' }.
you could use recursion and array_key_exists to walk down to the level of said key.
function get_array_element($key, $array)
{
if(stripos(($key,':') !== FALSE) {
$currentKey = substr($key,0,stripos($key,':'));
$remainingKeys = substr($key,stripos($key,':')+1);
if(array_key_exists($currentKey,$array)) {
return ($remainingKeys,$array[$currentKey]);
}
else {
// handle error
return null;
}
}
elseif(array_key_exists($key,$array)) {
return $array[$key];
}
else {
//handle error
return null;
}
}
Use a recursive function like the following or a loop using references to array keys
<?php
function lookup($array,$lookup){
if(!is_array($lookup)){
$lookup=explode(":",$lookup);
}
$key = array_shift($lookup);
if(!isset($array[$key])){
//throw exception if key is not found so false values can also be looked up
throw new Exception("Key does not exist");
}else{
$val = $array[$key];
if(count($lookup)){
return lookup($val,$lookup);
}
return $val;
}
}
$config = array(
'db'=>array(
'host'=>'localhost',
'user'=>'user',
'pass'=>'pass'
),
'data'=>array(
'test1'=>'test1',
'test2'=>array(
'nested'=>'foo'
)
)
);
echo "Host: ".lookup($config,'db:host')."\n";
echo "User: ".lookup($config,'db:user')."\n";
echo "More levels: ".lookup($config,'data:test2:nested')."\n";
Output:
Host: localhost
User: user
More levels: foo

array iterations too many foreach? any ideas on reducing it

protected static function arrayShuffling($itemsArray)
{
$itemSwitching = array();Switching
$shItem= array();
foreach ($itemArray as $i => $myItem) {
if (!in_array($myItem->_id, $itemSwitching)) {
$itemSwitching[$i]['_id'] = $myItem->_id;
$itemSwitching[$i]['poistion'] = $myItem['details']['move_to_poistion'];
}
foreach ($itemSwitching as $t => $pinPrep) {
if ($event->_id == $pinPrep['_id']) {
$shuffeledItem[$itemSwitching[$t]['poistion']] = $myItem;
unset($itemArray[$i]);
}
}
}
foreach($shItem as $key=>$shffuledItem){
array_splice( $itemArray, $key, 0, array($shffuledItem));
}
}
I have this method that takes an array and then look at $myItem['details']['move_to_poistion'] index to see what position that element should be moved to. After that it takes that specific element out of the array and then uses splice to insert it back to the $myItem['details']['move_to_poistion'] position.
I am worried about too many foreach lops and want to know if some how we can shorten this.
thanks

PHP combinations of array elements

I want to generate all possible combination of array elements to fill a placeholder, the placeholder size could vary.
Let say I have array $a = array(3, 2, 9, 7) and placeholder size is 6. I want to generate something like the following:
3,3,3,3,3,3
2,3,3,3,3,3
2,2,3,3,3,3
...........
...........
7,7,7,7,7,9
7,7,7,7,7,7
However (2,3,3,3,3,3) would be considered the same as (3,2,3,3,3,3) so the later one doesn't count.
Could anyone point me to the right direction? I know there is Math_Combinatorics pear package, but that one is only applicable to placeholder size <= count($a).
Edit
I am thinking that this one is similar to bits string combination though with different number base
I have no PHP source code for you but some sources that might help.
Some C code. Look at 2.1:
http://www.aconnect.de/friends/editions/computer/combinatoricode_g.html
Delphi code: combination without repetition of N elements without use for..to..do
Wiki article here
Well it took quit some time to figure this one out.
So i split the question into multiple parts
1.
I firsrt made an array with all the possible value options.
function create_all_array($placeholder, array $values)
{
if ($placeholder <= 0) {
return [];
}
$stack = [];
$values = array_unique($values);
foreach ($values as $value) {
$stack[] = [
'first' => $value,
'childs' => create_all_array($placeholder - 1, $values)
];
}
return $stack;
}
2.
Then I made a function to stransform this massive amount of data into string (no check for uniques).
function string($values, $prefix = '')
{
$stack = [];
foreach($values as $value) {
$sub_prefix = $prefix . $value['first'];
if (empty($value['childs'])) {
$stack[$sub_prefix] = (int)$sub_prefix;
} else {
$stack = array_merge($stack, string($value['childs'], $sub_prefix));
}
}
return $stack;
}
3.
Then the hard part came. Check for duplicates. This was harder than expected, but found some good anser to it and refactored it for my use.
function has_duplicate($string, $items)
{
$explode = str_split ($string);
foreach($items as $item) {
$item_explode = str_split($item);
sort($explode);
$string = implode('',$explode);
sort($item_explode);
$item = implode($item_explode);
if ($string == $item) {
return true;
}
}
return false;
}
4.
The last step was to combine the intel into a new funciton :P
function unique_string($placeholder, array $values)
{
$stack = string(create_all_array($placeholder, $values));
$check_stack = [];
foreach($stack as $key => $item) {
if (has_duplicate($item, $check_stack)) {
unset($stack[$key]);
}
$check_stack[] = $item;
}
return $stack;
}
Now you can use it simple as followed
unique_string(3 /* amount of dept */, [1,2,3] /* keys */);
Ps the code is based for PHP5.4+, to convert to lower you need to change the [] to array() but I love the new syntax so sorry :P

Categories