Find Array Runs

Swift, 246 bytes

func f(_ a:[Int]){
var c=a.count,m=a+a,z=0,r:String=""
for i in 1..<c{m[i]-=a[i-1];if m[i]==m[i-1]{m[i-1]=0}};m[0]=1
for i in 0..<c{if m[i]==0 {z=1}else if z==0{r+=" \(a[i])"}else{r+="to\(a[i])"+(m[i]>1 ? "by\(m[i])":"");z=0;m[i+1]=1}}
print(r)
}

Try it online!


Jelly,  42  40 bytes

-2 thanks to Kevin Cruijssen (filter out twos, ḟ2, rather than replacing twos with zeros, 2,0y)

ŒṖIE×LƲ€ḟ2SƊÞṪµ.ịU,Iḟ1ƊQ€Fż“to“by”ṁṖƊ$)K

A full program printing the result.
(As a monadic Link a list containing a mixture of integers and characters would be yielded)

Try it online!
(Too inefficient for the largest test-case to complete within 60s, so I removed [1,2,3,4].)

How?

ŒṖIE×LƲ€ḟ2SƊÞṪµ.ịU,Iḟ1ƊQ€Fż“to“by”ṁṖƊ$)K - Main Link: list of numbers
ŒṖ                                       - all partitions
           ƊÞ                            - sort by last three links as a monad:
      Ʋ€                                 -   last four links as a monad for €ach:
  I                                      -     incremental differences (of the part)
   E                                     -     all equal?
     L                                   -     length (of the part)
    ×                                    -     multiply
        ḟ2                               -   filter discard twos
          S                              -   sum
             Ṫ                           - tail (gets us the desired partition of the input)
              µ                       )  - perform this monadic chain for €ach:
               .                         -   literal 0.5
                ị                        -   index into (the part) - getting [tail,head]
                 U                       -   upend - getting [head,tail]
                      Ɗ                  -   last three links as a monad:
                   I                     -     incremental differences (of the part)
                     1                   -     literal one
                    ḟ                    -     filter discard (remove the ones)
                  ,                      -   pair -> [[head,tail],[deltasWithoutOnes]]
                       Q€                -   de-duplicate €ach -> [[head,tail],[delta]] or [[head,tail],[]] or [[loneValue],[]]
                         F               -   flatten -> [head,tail,delta] or [head,tail] or [loneValue]
                                     $   -   last two links as a monad:
                                    Ɗ    -     last three links as a monad:
                           “to“by”       -       literal list [['t', 'o'], ['b', 'y']]
                                   Ṗ     -       pop (get flattened result without rightmost entry)
                                  ṁ      -       mould ["to","by"] like that (i.e. ["to","by"] or ["to"] or [])
                         ż               -     zip together      
                                       K - join with spaces
                                         - implicit print

K (ngn/k), 102 bytes

{1_,//${H:" ",h:*x;l:+/&\x=h+(!#x)*d:--/2#x;$[l>2;(H;"to";x l-1;(~d=1)#,"by",d;o l_x);l;H,o 1_x;""]}x}

Try it online!