[r-t] delta based transposition for blue line generation

Alan Burlison alan.burlison at gmail.com
Fri Jun 10 23:15:16 UTC 2016


On 10/06/2016 21:04, doug boniface wrote:

> An interesting approach but I think I must be missing something as
> well. I assume you are calculating your deltas for each row from the
> place notation and then the blue line co-ordinates from the deltas.
> Surely this leads to the same amount, if not more, code than working
> directly from PN. If not I'd be very interested to know how its done
> (and I mean that seriously not cynically).

First thing to note is that I'm using a functional language (Scala) 
which is excels at applying functions to lists and other similar data 
structures, so converting to that type of format at the start makes 
subsequent operations much simpler.

To search and download a method's XML definition from 
methods.ringing.org, extract the PN and convert it from text into delta 
form is about 80 lines of code altogether. The core is 16 lines which 
parses the text PN and converts to deltas. The core algorithm is to 
generate pairs of 'swaps' (+1, -1) for each 'gap' between numbers in 
each Change represented in the PN, and replace the digits in the PN with 
0, as they denote the bells that don't move place. e.g. for the first 
change of PH Doubles:

"5" -> (+1,-1)+(+1,-1)+(0) = (+1,-1,+1,-1,0)

I expect that's more use than the code ;-)

     def expandPart(pn: String): Seq[Seq[Int]] = {
       for (c <- 
pn.split("""\.""").flatMap(_.split("""(?<=-)|(?=-)"""))) yield {
         c match {
           case "-" => swaps(stage)
           case _ => {
             var p = c.toSeq.map(CCBRToPlace)
             (swaps(p.head) :+ 0) ++
              p.sliding(2).flatMap({
                case Seq(a) => Seq.empty
                case Seq(a, b) => swaps(b - a - 1) :+ 0
              }) ++
              swaps(stage - p.last - 1)
           }
         }
       }
     }

Where 'swaps' is simply:

Range(0, num_swaps).map(s => if (even(s)) 1 else -1)

There's a bit more wrapping code, the result is a list of delta lists 
(Changes), each of which describes a single change, e.for PH Doubles:

Block(Change(1,-1,1,-1,0), Change(0,1,-1,1,-1))

Applying a Change repeatedly to a Row to expand an extent is trivial:

     val changes = plainHunt.forever
     var row = ring.rounds
     do {
       row = row * changes.next
       println(row.toCCBR)
     } while (row != ring.rounds)

The implementation of the '*' operator that applies a Change to a Row 
and returns a new Row is 1 line of code:

     def *(change: Change): Row =
      new Row(change.deltas.zipWithIndex.map(di => places(di._2 + di._1)))

Rows are represented as 0-based bell numbers and if for example if we 
start with reverse rounds and apply the first change of PH5 to it:

Row(0,1,2,3,4) * Change(1,-1,1,-1,0)

That first step maps the Change into a sequence of (delta, index) pairs:

((1,0),(-1,1),(1,2),(-1,3),(0,4))

Then add each of the inner pairs together to give:

(1,0,3,2,4)

Then uses those as indexes into the bell numbers in the old row to form 
the new row:

Position  Old row     Index  New row
    0         4          1       3
    1         3          0       4
    2         2          3       1
    3         1          2       2
    4         0          4       0

As I said in earlier mail the internal representation is really just 
down personal preference - I find the delta notation easier as it is 
expressed in terms of relative moves. The first version I wrote used 
'conventional' notation, there's not a huge amount of difference in 
terms of code complexity - and I have code to convert between the two 
representations anyway.

> I have also had the need to generate blue lines from place notation
> but decided to abandon tracking the whole row at each change and just
> track one bell. At the risk of the terms “Grandmother” and “suck
> eggs” coming to the minds of some readers, my approach was broadly:
> create PN for whole lead as:

> full PN = PN + HL + reverse PN + LE

Yes, for methods of that form that's what I do.

> select a start bell
> for each entry in full PN
> if entry = X
> move up or down 1 place according to if odd or even place
> else if at the place in PN entry
> stay in current pos
> else
> diff = place in PN entry – current position
> (Now determine move direction by whether diff is +ve or -ve, odd or
even and if the relevant place entry is odd or even.)

Sounds kinda like deltas to me ;-)

> There was a bit more detail in the full version to cope with methods
> that had multiple internal places. The results were fed into an array
> and used as co-ordinates by the line drawing functions.
>
> The whole block can be used to to generate the line for any given
> place bell or put in a loop to get the line for the whole method
> given any start bell.

Mine just dumps out ASCII as I know there are existing things that draw 
far nicer pictures than I could be bothered to generate, and the main 
aim was just to help me fully understand place notation, which it did.

-- 
Alan Burlison
--




More information about the ringing-theory mailing list