PHP siteswap functions

Some of the functions that I use to validate siteswaps on the Edge.

Validate a siteswap

Use to parse any string, will return true if it is a valid siteswap, or false if not.

This function requires the Expand() function.


<?php

function Validate($SS)
{
// Normalise characterset
  $FullCode=strtoupper($SS);

// 2 & 2t are the same thing for validation
  $FullCode=str_replace('2T','2',$FullCode);

// Check syntax

  if(preg_match('/^[0-9A-Z]+$/',$SS))
    $Type='Vanilla';
  elseif(preg_match('/^([0-9A-Z]*(\[[1-9A-Z]{2,}\])+[0-9A-Z]*)+$/',$SS))
    $Type='Multiplex';
  elseif(preg_match('/^(\([02468ACEGIKMOQSUWY]X?,[02468ACEGIKMOQSUWY]X?\))+\*?$/',$SS))
    $Type='Synchronous';
  elseif(preg_match('/^(\(([02468ACEGIKMOQSUWYX]X?|\[[2468ACEGIKMOQSUWYX]{2,}\]),([02468ACEGIKMOQSUWY]X?|\[[2468ACEGIKMOQSUWYX]{2,}\])\))+\*?$/',$SS))
    $Type='Synchronous multiplex';
  else
    return false;


// Candidate throws
  $Hex='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';


// If string is all one character, is valid siteswap
  if(strlen($FullCode)<2 || preg_match('/^'.$FullCode[1].'+$/',$FullCode))
    {
      return true;
    }

// If pattern contains parentheses it must be synchronous
  $Synch=preg_match('/\(|\)/',$FullCode);


// If Synch SS ends with * mirror pairs of throws
  if($Synch && substr($FullCode,-1)==='*')
    $FullCode=Expand($FullCode);

// Start validation

  $Values=[];
  $Beat=[];
  $Destination=[];

  $a=0;
  $b=strlen($FullCode);
  $ThisChar;
  $SynchOffset=0;
  $ThisBeat=0;
  $InMultiplex=false;

// Work through all characters in string, for each throw character populate 2 arrays:
// Values = The throw weight
// Beat = The beat the throw is made from
// also count number of beats in pattern

  while($a<$b)
    {
      $ThisChar=$FullCode[$a];
        
      if(ctype_alnum($ThisChar))
        {
// Is a throw value
          $Beat[]=$ThisBeat;
          if($Synch && $FullCode[$a+1]==='X')
            {
              $Values[]=strpos($Hex,$ThisChar)+$SynchOffset;
              ++$a;
            }
          else
            $Values[]=strpos($Hex,$ThisChar);

          if($InMultiplex===false)
            ++$ThisBeat;
        }
      else
        {
// Is multiplex or synchronous information
          if($ThisChar==='[')
            $InMultiplex=true;

          if($ThisChar===']')
            {
              $InMultiplex=false;
              ++$ThisBeat;
            }

          if($ThisChar==='(' || $ThisChar===')')
            $SynchOffset=1;

          if($ThisChar===',')
            $SynchOffset=-1;
        }
        
      ++$a;
    }


// Work through values, populate destination array with the beat that each throw lands on
    
  $Period=$ThisBeat;
  
  $b=count($Values);
  for($a=0;$a<$b;++$a)
    {
      if($Values[$a]===0)
        $Destination[$a]=$Beat[$a];
      else
        $Destination[$a]=($Beat[$a]+$Values[$a])%$Period;
    }

// Initialise all beats in pattern as having nothing coming in or going out

  $In=[];
  $Out=[];

  $b=$Period;
  for($a=0;$a<$b;++$a)
    {
      $In[$a]=0;
      $Out[$a]=0;
    }

// Count up how many props are thrown (Out) & caught (In) on each beat

  $b=count($Destination);
  for($a=0;$a<$b;++$a)
    {
      if($Values[$a]!==0)
        {
          ++$In[$Destination[$a]];
          ++$Out[$Beat[$a]];
        }
    }

// If number of props coming in exactly matches number of props going out on every beat, pattern is valid
  return Implode(',',$In)===Implode(',',$Out);
}

?>

Expand a synchronous * siteswap

If $SS is of the form (a,b)(c,d)* convert to (a,b)(c,d)(b,a)(d,c).


<?php

function Expand($SS)
{
  if(preg_match('/^(\(([02468ACEGI]X?|\[[02468ACEGI]{2,}\]),([02468ACEGI]X?|\[[02468ACEGI]{2,}\])\))+\*/i',$SS))
    {

// Remove '*' from original code
      $SS=substr($SS,0,-1);

// Create list of individual throws
      $TempStr=substr($SS,1,-1);
      $TempStr=preg_replace('/[\(\)]+/',',',$TempStr);
      $Mirror=explode(',',$TempStr);

// Append original code with mirrored pairs of throws
      for($a=0;$a<Count($Mirror);$a=$a+2)
        {
          $SS.='(';
          $SS.=$Mirror[$a+1];
          $SS.=',';
          $SS.=$Mirror[$a];
          $SS.=')';
        }
    }
  return $SS;
}

?>

Reduce siteswap to smallest repeating sequence

eg. shorten abcabcabc to abc. Also check to see if a synchronous siteswap can also be shortened further using * notation.

This function requires the Expand() function.


<?php

function Shorten($SS)
{

// Expand shortened synch siteswap if necessary
  $SS=Expand($SS);

  $c=strlen($SS);
  $b=$c/2;

// Check for repetition starting from 1 character (smallest possible sequence)
// to half the length of the siteswap (largest possible sequence)

  for($a=1;$a<$b;++$a)
    {

// Check if siteswap can be exactly broken up into chunks of length $a
      if($c%$a===0)
        {

// Create array of strings all $a characters in length
          $Segments=str_split($SS,$a);

// Check if all segments are equal
          if(count(array_unique($Segments))===1)
            {

// Smallest repeating sequence found, update siteswap & exit loop
              $SS=$Segments[0];
              break;
            }
        }
    }

// Check if shortened siteswap is synchronous with an even number of pairs
// could possibly be shortened further using '*' notation
  $Check1=substr($SS,0,strlen($SS)/2);
  if(substr($Check1,-1)===')')
    {

// Create shortened version, then expand to compare against result
      $Check1.='*';
      $Check2=Expand($Check1);

// If expanded check string matches result, then it is ok to shorten with '*'
      if($SS===$Check2)
        $SS=$Check1;
    }

  return $SS;
}

?>

Check if two siteswaps are a rotation of each other

This function returns true if two siteswaps are a rotation of each other, eg. abcd=dabc=cdab=bcda. Returns false if they are not, eg. abcd != acbd.

Both $SS1 and $SS2 need to have been parsed through Shorten() to ensure correct result.

This function requires the SortMultiplex() and Expand() functions.


<?php

function CheckRotation($SS1,$SS2)
{
// Expand shortened synch siteswaps if necessary
  $SS1=Expand($SS1);
  $SS2=Expand($SS2);

// Ensure multiplexes are in the same order
  $SS1=SortMultiplex($SS1);
  $SS2=SortMultiplex($SS2);

// If length of SS are equal & SS1 exists within SS2 repeated twice, patterns are rotations of each other
  return (strlen($SS1)===strlen($SS2) && strpos($SS1,$SS2.$SS2)!==false);
}

?>

Sort all throws within a multiplex

Judging from Edge record data the consensus is that throws within a multiplex should be written in descending order.

This function will sort all values within [square brackets] in reverse order. eg. abc[def]hij[klm] will become abc[fed]hij[mlk].


<?php

function SortMultiplex($SS)
{
  $SS=preg_replace_callback(
      '/\[([^\]]+)\]/',
      function ($matches){

// Get contents of the multiplex
        $str=$matches[1];
        $Throws=[];
        $a=0;
        $b=strlen($str);

// Populate $Throws array with each individual throw
        while($a<$b)
          {

// Check for modified throws eg. 6x or 2t
            if($str[$a+1]==='x' || $str[$a+1]==='X' || $str[$a+1]==='t' || $str[$a+1]==='T')
              {
                $Throws[]=$str[$a].$str[$a+1];
                ++$a;
              }
            else
              $Throws[]=$str[$a];

            ++$a;
          }

// Sort in reverse order
        rsort($Throws);
        return '['.implode('',$Throws).']';
      },
    $SS
  );

  return $SS;
}

?>