Skip to content

Conversation

jiribenes
Copy link
Contributor

@jiribenes jiribenes commented Apr 5, 2025

Tiny & fast exponentiation using streams:
Screenshot 2025-04-05 at 16 04 07

The approach is simple: to calculate n^k, zip squares of n (n, n^2, n^4, ...) with the bits of k.
Sketch:

n^k = n^0b10011
    = (1 * n^0b10000)
    * (0 * n^0b01000)
    * (0 * n^0b00100)
    * (1 * n^0b00010)
    * (1 * n^0b00001)

Also drags a few other changes along:

  • product and iterate in stream
  • temporary addition of eachLSB and eachMSB -- these would probably be better off using the toolchain from Stdlib: Binary/Byte stream utilities #923, but I don't want to forget about the example

I'm adding it because:

  1. it's a fun, small example,
  2. it uses a bunch of different combinators,
  3. it uses the new multiple param lambda case syntax (Allow lambda case patterns for multiple parameters #914),
  4. and it has some missed opt opportunities, I think.

Comment on lines +46 to +62
def prettyBits(bits: List[Bool]): String =
"0b" ++ bits.map { b => if (b) "1" else "0"}.join("")

def testBits(n: Int) = {
println(n)
println(collectList[Bool] {n.eachLSB}.prettyBits)
println(collectList[Bool] {n.eachMSB}.prettyBits)
}

testBits(0)
testBits(1)
testBits(2)
testBits(3)
testBits(4)
testBits(123456789)

println("")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is just my sanity check for eachLSB and eachMSB. I'm happy to (re)move it after rebasing on top of #923 :)

Comment on lines +46 to +58
/// Computes n^exp for integers. (from #923!)
def pow(n: Int, exp: Int): Int = {
def go(n: Int, exp: Int, acc: Int): Int = {
if (exp == 0) {
acc
} else if (mod(exp, 2) == 0) {
go(n * n, exp / 2, acc)
} else {
go(n * n, exp / 2, acc * n)
}
}
go(n, exp, 1)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taken verbatim from #923, I really ought to rebase :)

By the way, as an exercise in algorithms, notice that the pow here and the fastexp above are completely isomorphic, always doing the exact same steps :)

@jiribenes
Copy link
Contributor Author

jiribenes commented Apr 5, 2025

TODO(Self):
add digits as an example & add related stream functions, maybe with an additional cheeky leftPad example? :^)

def takeWhile[A] { cond: A => Bool } { stream: => Unit / emit[A] }: Unit / emit[A] =
  try stream() with emit[A] {
    case value and cond(value) => do emit(value); resume(())
    case _                     => ()
  }

def last[A] { stream: => Unit / emit[A] }: A / Exception[MissingValue] = {
  var latest = None()
  for[A] {stream} { value =>
    latest = Some(value)
  }
  latest.value
}

def digits(n: Int) = last[Indexed[Int]] {
  with index[Int]
  with takeWhile { k => k <= n }
  iterate(1) { k => k * 10 }
}.index + 1

def main() = {
  with on[MissingValue].report
  println(digits(12345)) // ~> 5
}

Comment on lines +178 to +184
def iterate[A](initial: A) { step: A => A }: Unit / emit[A] = {
var current = initial
while (true) {
do emit(current)
current = step(current)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice... This looks a lot like unfolding a coalgebra :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I just wanted to use the Haskell-style name which (weirdly!) is a bit friendlier than unfold

@jiribenes jiribenes changed the title Fast exponentiation, streamingly 🌊 Example: Fast exponentiation, streamingly 🌊 May 6, 2025
@jiribenes jiribenes merged commit 5289380 into master May 6, 2025
3 checks passed
@jiribenes jiribenes deleted the jiribenes/stream-fastexp branch May 6, 2025 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants