Advent of Code 2016 Day16: Dragon Checksum

Primary tabs

This is a series of programming challenges for beginners and experienced Caché programmers.

For an introduction : go to article https://community.intersystems.com/post/advent-code-2016-day1-no-time-ta...

The challenge of day 16 is about generating random data using a modified version of a dragon curve (you can find more info on fractal curves like Dragon here : https://en.wikipedia.org/wiki/Dragon_curve).

First you need to generate data in a loop as follows :

Create an initial state with your puzzle input

Call the data you have at this point "a".
Make a copy of "a"; call this copy "b".
Reverse the order of the characters in "b".
In "b", replace all instances of 0 with 1 and all 1s with 0.
The resulting data is "a", then a single 0, then "b"

Repeat until your generated data is equal or longer than 272 digits.

 

After this proces, you need to calculate a checksum, this is the string that is the answer to the puzzle.
The checkum is made by replacing all pairs of 1 and 0's by a single digit : 1 if the pair is the same digit, 0 if the pair are two different digits. (start with the first 272 digits)
Repeat this calculation until your checksum has an odd length.

The complete instructions are to be found at http://adventofcode.com/2016/day/16, it will also contain your puzzle input.

The code we wrote for this part of the challenge was not too difficult, here is Bert's code :

Start() PUBLIC {
input = 11100010111110100
minLength=272
  while $LENGTH(input)<minLength {
input=$$dragonCurve(input)
  }
!,$$calculateChecksum($EXTRACT(input,1,minLength))
}

dragonCurve(Input) {
result=Input_"0"
  for i=$LENGTH(Input):-1:1 {
result = result_'$EXTRACT(Input,i)
  }
result
}

calculateChecksum(Input) {
checksum=""
  for i=1:2:$LENGTH(Input)-1 {
    if $EXTRACT(Input,i)=$EXTRACT(Input,i+1) {
checksum=checksum_"1"
else {
checksum=checksum_"0"
    }
  }
  if '($LENGTH(checksum)#2) {
checksum=$$calculateChecksum(checksum)
  }
checksum

}

 

The second part of the challenge made a rewrite necessary : the length of the dragon changed to 35.651.584.
As you might know, the limit of strings in Cache is 32.767 if long strings are not enabled, and 3.641.144 if long strings are enabled.

 
So instead of using one string, I used an array of strings. To make the reverse operations easy, each loop in creating the dragon doubles the indices in the array. Bert used some optimizations to make his code faster : he makes the array smaller before calculating the checksum, and calculate the first 16K using the code of part1.

 

Here is my code (which took a lot longer to calculate ) :

Class AOC2016.Day16
{

ClassMethod Part2()
{
  #Dim state as %String = "01110110101001000"
  #Dim disk as %Integer = 35651584 ;272
  #Dim dragon as Array of %String
  #Dim iDragon as %Integer

  set dragon($i(dragon)) = state
  For {
    Do ..Dragon(.dragon)
    If ..Length(.dragon)>disk Quit
  }
  Do ..Cut(.dragon,disk)
  For {
    Do ..Checksum(.dragon)
    If ..Length(.dragon)#2'=0 Quit
  }
  Set iDragon="" For {
    set iDragon=$O(dragon(iDragon)) If iDragon="" Quit
    Write dragon(iDragon)
  }
  Quit
}

ClassMethod Dragon(ByRef dragon)
{
  #Dim middle as %Integer
  #Dim iDragon as %Integer

  set middle = $order(dragon(""),-1)
  set dragon($i(dragon))="0"  //first append 0
  //than append reverse of previous dragon (and negate digits)
  For iDragon=middle:-1:1 {
    Set dragon($i(dragon)) = $TR($reverse(dragon(iDragon)),"10","01")
  }
  Quit
}

//Cut length of array to maxlen
ClassMethod Cut(ByRef dragon, maxlen)
{
  #Dim iDragon, length as %Integer = 0
  For iDragon=1:1:dragon {
    Set length=length+$Length(dragon(iDragon))
    If length > maxlen set dragon(iDragon)=$E(dragon(iDragon),1,*-(length-maxlen)) Quit
  }
  For iDragon=iDragon+1:1:dragon Kill dragon(iDragon)
  set dragon=$order(dragon(""),-1)
  Quit
}

ClassMethod Length(dragon)
{
  #Dim length as %Integer = 0
  #Dim iDragon as %Integer = ""
  For {
    Set iDragon = $Order(dragon(iDragon)) if iDragon = "" Quit
    Set length=length+$Length(dragon(iDragon))
  }
  Quit length
}

ClassMethod Checksum(str)
{
  #Dim rest as %String = ""
  #Dim iStr as %Integer = ""
  #Dim oldString as %String
  #Dim pair as %Integer

  For {
    Set iStr=$o(str(iStr)) if iStr="" Quit
    Set oldString = str(iStr)
    Set str(iStr)=""
      For pair=1:2:$length(oldString) {
        If (pair+1)>$length(oldString) set str(iStr+1)=$e(oldString,*)_$Get(str(iStr+1)) Continue
        If $E(oldString,pair)=$E(oldString,pair+1) {
          set str(iStr)=str(iStr)_"1"
else {
          set str(iStr)=str(iStr)_"0"
        }
      }
      If str(iStr)="" Kill str(iStr)
    }
    Quit
  }

}

 

And the code Bert wrote that was faster :

Start2() PUBLIC {
strings=1
input=11100010111110100
minLength=35651584

  //first create big string with dragonCurve1 then loop with dragon curve 2
  while $LENGTH(input)<16000 {
input=$$dragonCurve(input)
  }
strings(1)=input

length=$$totalLength(.strings)
  while length<minLength {
!,"current length: "_length
dragonCurve2(.strings)
length=$$totalLength(.strings)
  }
  !,"starting making it shorter"
makeShorter(.strings,minLength)
!,$$totalLength(.strings)
!,"starting checksum: "
!
calculateChecksum2(.strings)
}

dragonCurve(Input) {
result=Input_"0"
  for i=$LENGTH(Input):-1:1 {
result = result_'$EXTRACT(Input,i)
  }
result
}

makeShorter(Strings,maxLength) {
totLength=$$totalLength(.Strings)

  while (totLength>maxLength) {
    struct=$ORDER(Strings(""),-1,value)
    if $LENGTH(value)>(totLength-maxLength) {
value=$EXTRACT(value,1,*-(totLength-maxLength))
Strings(struct)=value
else {
Strings(struct)
    }
totLength=$$totalLength(.Strings)
  }
}

totalLength(Strings) {
result=0
struct=$ORDER(Strings(""),1,string) 
  while struct'="" {
result=result+$LENGTH(string)
struct=$ORDER(Strings(struct),1,string)
  }
result
}

dragonCurve2(Strings) {
Strings($INCREMENT(Strings))="0"
struct=$ORDER(Strings($ORDER(Strings(""),-1)),-1,value)
  while struct'="" {
Strings($INCREMENT(Strings))=$TR($REVERSE(value),"10","01")
struct=$ORDER(Strings(struct),-1,value)
  }
}

calculateChecksum2(Strings) {
pair=""
result=""
struct=$ORDER(Strings(""),1,string)
  while struct'="" {
i=0:1:$LENGTH(string) {
letter=$EXTRACT(string,i)
pair=pair_letter
      if $LENGTH(pair)=2 {
        if $EXTRACT(pair)=letter {
result=result_1
else {
result=result_0
        }
        if $LENGTH(result)>30000 {
newStrings($INCREMENT(newStrings))=result
result=""
        }
pair=""
      }
    }
struct=$ORDER(Strings(struct),1,string)
  }
newStrings($INCREMENT(newStrings))=result

  if '($$totalLength(.newStrings)#2) {
calculateChecksum2(.newStrings)
else {
    zw newStrings
  }
}

 

Look here for all our solutions so far : https://bitbucket.org/bertsarens/advent2016 and https://github.com/DannyWijnschenk/AdventOfCode2016

Here is the list of all Advent of Code 2016 articles  :

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25