Arc Forumnew | comments | leaders | submit | lacker's commentslogin
1 point by lacker 5987 days ago | link | parent | on: Arc Programming Assignment

A bit nit picky, but memoization alone won't make the solution optimal, since you are dealing with very large numbers. n! has O(n log n) digits, so in the limit it will take O(n log n) time just to calculate n! from n * (n-1)!.

-----

1 point by jmatt 5987 days ago | link

I agree memoization isn't optimal.

But I don't know about your big-O estimate. Since the triangle is constructive and you are memoizing calculating n! from n * (n - 1)! will take O(1). It'll be the cached (n - 1)! value times n.

-----

1 point by lacker 5982 days ago | link

Doing a single multiplication is not O(1) when the numbers have more than a constant number of digits. I'm not precisely sure how long multiplying large numbers takes, but it's at least linear in the number of digits involved. And (n-1)! has O(n log n) digits.

-----

1 point by eds 5987 days ago | link

I don't follow how n! has O(n log n) digits. Mind explaining?

-----

2 points by lacker 5982 days ago | link

No prob. Sorry if I go on at too much length here ;-)

So a useful formula for counting digits is, a positive integer x has floor(log10(x)) + 1 digits. You can figure this out yourself by thinking, where does the digit-counting function bump up a notch? At 10, 100, 1000, etc. So asymptotically the number of digits in x is O(log x).

So n! has O(log n!) digits. The trickier part is figuring out or knowing that O(n log n) = O(log n!). Using log ab = log a + log b you can expand out the factorial:

  O(log n!) = O(log (n * (n-1) * ... * n * 1))
            = O(log n + log (n-1) + ... + log 2 + log 1)
            = O(n log n)
In case the last step isn't clear, you can do this splitting-in-half bounding trick. Since each element in the sum is less than log n you can bound from above with

  log n + log (n-1) + ... + log 2 + log 1 < n log n
And if you just take the larger half of the list you can bound from below with

  log n + log (n-1) + ... + log 2 + log 2 > log n + log (n-1) + ... + log (n/2)
                                          > (n/2) log (n/2)
which is itself O(n log n). So O(log n!) = O(n log n).

In general the rules of thumb you use to reduce O(log n!) are:

  1. complicated expressions inside factorials are ugly, you should simplify them
  2. O(sum of n things) is usually O(n * the biggest thing)
Make sense?

-----

1 point by kens 5987 days ago | link

You're multiplying O(n) numbers, each of which is O(log n) digits long, so the result is O(n log n) digits long.

-----

1 point by shader 5987 days ago | link

Usually, it seems to be either (n log n) or (n log n) - 1 digits.

And usually in this case I would leave of the O, as that usually refers to the performance of an algorithm in time or space. I suppose you could construe the number of digits to be "space" but multiplying O(n) numbers doesn't make that much sense.

-----

5 points by lacker 5989 days ago | link | parent | on: Arc Programming Assignment

Hmm, this solution is not big-O-optimal. The solution you give is O(n^3) if you assume O(1) multiplies and divides, and since you are taking factorials that's probably not that great of an assumption either.

You can improve this by using the recursive formula for Pascal's triangle rather than the closed form. Specifically, n choose k equals (n-1) choose (k-1) + (n-1) choose k. Memoize and you can do (pascal-triangle n) in O(n^2), optimal since that's the amount of prints you have to do.

There is also probably an quicker formula for n choose k mod 2, if you don't have to print the entire triangle. Perhaps try using Legendre's formula for the largest power of a prime that divides a factorial.

see: http://mathworld.wolfram.com/Factorial.html

Also you might be able to find some useful recursive formula based on the fact that Pascal's triangle mod 2 looks like a Sierpinski triangle.

-----

2 points by lacker 5989 days ago | link

Yeah here's a faster formula for n choose k mod 2 based on the sierpinski triangle interpretation. In pseudocode:

  def f(n, k):
    if n and k are both 0:
      return 1
    if n is even and k is odd:
      return 0
    return f(floor(n/2), floor(k/2))
Should be able to reduce this to bit operations if you really cared.

-----

3 points by almkglor 5989 days ago | link

  (def f (n k)
    (if
      (is n k 0)
        1
      (and (even n) (odd k))
        0
      ; else
        (f (round:/ n 2) (round:/ k 2)))) ; works if n and k are ints

-----

3 points by eds 5989 days ago | link

Not quite, since 'round returns the nearest even integer on halves. s/round/trunc/g gives the correct solution.

  arc> (round 4.5)
  4
  arc> (round 5.5)
  6
  arc> (trunc 4.5)
  4
  arc> (trunc 5.5)
  5

-----

2 points by bOR_ 5989 days ago | link

Ah.. I remember reading about the reasoning behind that (Round-to-even)

from: http://en.wikipedia.org/wiki/Rounding

  Although it is customary to round the number 4.5 up to 5, 
  in fact 4.5 is no nearer to 5 than it is to 4 (it is 0.5 
  away from both). When dealing with large sets of 
  scientific or statistical data, where trends are 
  important, traditional rounding on average biases the data
  upwards slightly. Over a large set of data, or when many 
  subsequent rounding operations are performed as in digital
  signal processing, the round-to-even rule tends to reduce 
  the total rounding error, with (on average) an equal 
  portion of numbers rounding up as rounding down. This 
  generally reduces upwards skewing of the result.

-----

1 point by lacker 6075 days ago | link | parent | on: Confused by Arc threads

What are these mzscheme functions you are referring to? I was just using a table keyed on thread for threadlocal storage. (Do I need to wrap table access in an atomic-invoke, by the way?)

I wish scheme documentation was more googleable. It's the opposite of javascript, in which you can find 100 ways to do anything, but they are all terribly ugly.

-----

2 points by drcode 6075 days ago | link

http://download.plt-scheme.org/doc/mzscheme/mzscheme-Z-H-7.h...

Actually, how did you manage to key on thread? I couldn't figure out how to do this myself, since there isn't a "thread id" or anything that would work as a key...

I would assume (but don't know for sure) that arc tables are thread-safe- I would think you didn't need to worry about atomicity, here...

-----

1 point by lacker 6075 days ago | link

Thanks for the pointer. Looks like tables are indeed threadsafe. From your link -

"The hash-table-put! procedure is not atomic, but the table is protected by a lock."

As far as threadlocal goes... new-thread returns the thread descriptor and you can use that directly as a hash key.

  arc> (= a (table))
  #hash()
  arc> (= (a (new-thread (fn () (sleep 10)))) 5)
  5
  arc> a
  #hash((#<thread> . 5))
This is inconvenient by itself since in arc you can't access the current thread descriptor without some trickery. It seemed easiest to expose mzscheme's current-thread. I imagine that will have to end up in arc eventually.

-----

4 points by lacker 6080 days ago | link | parent | on: Quick question - un-setting a symbol?

Yeah, like unintern from CL. Although more closely something like python's del.

in python:

  >>> a = 3
  >>> a
  3
  >>> del a
  >>> a
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  NameError: name 'a' is not defined

edit: I investigated a little more and found mzscheme's namespace-undefine-variable! I think this is going to work. Thanks for the hint ;-)

-----

2 points by lacker 6080 days ago | link | parent | on: Quick question - un-setting a symbol?

The part I can't figure out is how to make

  (w/temps
    (def foo (x) (+ x y))
    (tempset y 10)
    (foo 5))
work correctly.

-----

1 point by aston 6080 days ago | link

Might wanna have w/temps work more like

  (w/temps x y
    (def foo (x) (+ x y))
    (tempset y 10)
    (foo 5))
That is, like a with, but with x and y set to some dummy values. You wouldn't even need a tempset then, right?

-----

2 points by bogomipz 6079 days ago | link

Right indeed. The normal way to do this would be;

  (with (y nil foo nil)
    (= foo (fn (x) (+ x y)))
    (= y 10)
    (foo 5))
You can't use 'def there because, unlike in Scheme, def always makes a global binding in Arc. Including x in the with is not necessary, by the way.

From the sound of it, this does not solve lacker's problem, however, because he does not know up front what variables he needs to declare.

-----

1 point by absz 6079 days ago | link

As a first cut, I would scan through to find the tempsets, take out the variables, and then expand to a let or with block now that you know their names.

-----

2 points by almkglor 6079 days ago | link

  (mac make-things-difficult (c v)
    (if (some-property v)
      `(tempset ,c ,v)
      `(if (another-property ,v)
          (tempset ,c ,v)
          (= ,c ,v))))

-----

1 point by absz 6079 days ago | link

Ah. Right. (And I even worried about similar things when writing make-br-fn...). Well, it would work in simple cases, but let/with are looking like better ideas.

Actually, if we add another primitive (with $ or xdef) which works using mzscheme's namespace-undefine-value!, we could have tempset maintain a global list of tempset variables, and cleartemps go through and undefine them.

-----

2 points by lacker 6080 days ago | link | parent | on: Quick question - un-setting a symbol?

Yeah, definitely. But, this specific problem is in the context of writing a restricted eval, like for a browser plugin or something like that. I was thinking of doing a global replace of set with tempset and then cleaning up after one plugin runs. But maybe that is the wrong way to do it.

-----

4 points by drcode 6079 days ago | link

It might make more sense to have the eval prepend a suffix on all variables before "evaling"- Sort of like a poor man's namespace.

-----

2 points by eds 6079 days ago | link

I still don't see why something like this wouldn't work for most cases:

http://arclanguage.org/item?id=4481

If you did this for all local variables, and then used namespace-undefine-variable! to delete any defs that had been evaluated, I think you would get a fairly well restricted eval.

-----

4 points by lacker 6079 days ago | link

I agree with you - I think this is roughly the right idea, I just hadn't found namespace-undefine-variable! when I asked this question.

I think there's still somewhat of a problem with wrapping variables in a "let" with their deep copies - that prevents internal code from wrapping builtins and having other builtins use the wrap, because the let will make the builtin-wrap scope different from the outer scope. But you can do the special-suffix thing in this case.

-----

1 point by lacker 6081 days ago | link | parent | on: Question about "subclassing" lists

Mmm interesting series, I'll have to look it over. Thanks for the pointers!

-----

1 point by almkglor 6081 days ago | link

A bit more of some history, and why there are two implementations of what is essentially the same add-on to Arc:

My implementation (lib/settable-fn.arc) is first described here: http://arclanguage.org/item?id=3595

nex-3's alternative (lib/settable-fn2.arc) is similar to absz's suggestion here: http://arclanguage.org/item?id=3723

The difference lies mostly in what the use of the type in 'annotate is. nex-3 views this type as the "real" type, with any "claimed" (user-interface) type ducked by overloading 'isa. I view this type as the "claimed" (user-interface) type, with the "real" type being the underlying implementation (usually a function) for this.

-----

2 points by nex3 6081 days ago | link

Very minor nitpick: "nex3". The hyphen is only in my URL because nex3.com was taken :-p.

Also, I don't like overriding isa; it's just necessary sometimes because the standard libs aren't really geared towards duck typing. I much prefer overriding basic functions and having more complex functions assume those base functions will work. This is really what duck typing (as I understand it) is all about.

-----

2 points by almkglor 6081 days ago | link

^^; lol. I think I've been rather consistently calling you nex-3 with a hyphen O.O;

The problem, I suppose, is the rather inconsistent formalization/nonformalization of type classes/interfaces/hindley-milner typesystem. In Ruby it's informal - you just say this or that object has this or that function which will just work, in the expected manner. In Haskell and Java it's formal - you say this type provides this type class or presents this interface, and must therefore have this or that function which must work, in the expected manner.

annotated types are kinda like a formal type class, except existing type classes are not trivially extendable except informally (via redef).

-----

4 points by nex3 6081 days ago | link

Yeah, I think that's right. And I think Arc should go the path of informality, for a couple reasons. First, it's dynamically typed, and duck typing (or informal typing) has historically worked well with dynamically-typed languages (with Ruby, Python, and maybe Smalltalk). CLOS also tends to that side of the spectrum, although I'm not sure how explicitly it embraces duck typing.

Second, and I think more importantly, duck typing gives the programmer more power, in exchange for more opportunity to screw stuff up. This is very much in line with Arc's philosophy.

Using formal interfaces, both the people writing the polymorphic code and the people writing the polymorphic objects have to explicitly code polymorphically. Using informal interfaces, only the people writing polymorphic objects have to be explicit. The other people can* be aware of the polymorphism, which allows powerful stuff like Ruby's Enumerable module, but as long as the objects behave correctly ("quack like a duck"), you can use pass them to code that doesn't expect them at all and they'll still work.

* I know less about Smalltalk than I want to, so I don't know how much it makes use of duck typing.

-----

2 points by almkglor 6080 days ago | link

Here's another interesting bit: car and cdr could be modified into settable-fn's and their defset's could be completely removed from arc.arc ^^. Also, it should be possible to additionally modify compose to work on the '= attachments ^^.

-----

1 point by lacker 6081 days ago | link | parent | on: How to implement a no-side-effects macro?

Interesting, I hadn't thought of this. You have to somehow let people still refer to, say, the + function, by its global name. Maybe that could be via some sort of const wrapper thing.

-----

1 point by lacker 6083 days ago | link | parent | on: How to implement a no-side-effects macro?

Interesting. I think this roughly makes sense.

There are still some parts that are stumping me. It seems like I need to either (a) mock annotate to use a different mechanism, or (b) prevent the code from annotating pre-existing variables. The same goes for accessing sig.

Also, I'm not sure how to handle scar/scdr/sref. It don't think places are first-class, so I can't have a table of untouchable places, unless I start working in mzscheme. (Which I would rather avoid.)

-----

2 points by kens1 6083 days ago | link

If you want the gory details of places, see my recent document: http://arcfn.com/doc/setforms.html

-----

1 point by lacker 6083 days ago | link

Wow... that is gory. :-/

It seems like the most plausible way to keep scar/scdr/sref from mutating variables outside the no-side-effects scope is to keep a table of the variables they are allowed to operate on, which is basically anything the user created with cons or table inside the no-side-effects scope. This should invalidate code like

(= a (table))

(no-side-effects (= b a) (= (b "key") "value"))

Is there any more natural way to track this than keeping a table keyed by symbol?

-----

3 points by eds 6083 days ago | link

This is probably a horribly inefficient way to implement this... but couldn't you just copy all the variables before executing the body?

Assuming

  (= a (table 'foo 'bar))
then a line like this

  (no-side-effects (= a!foo 'baz) a)
might be converted to something roughly like this

  (let a (copy a)
    (= a!foo 'baz)
    a)
a is unchanged after evaluation because the let made a deep copy that overshadowed the global value of a.

You would still have to think about constructs like def and mac, but at least this deals with shared structure of lists and tables.

Maybe something like http://arclanguage.org/item?id=3572 ?

-----

1 point by lacker 6083 days ago | link | parent | on: How to implement a no-side-effects macro?

Sorry I was unclear - I was indeed actually trying to ask a hard question.

I don't know a priori what variables this code will try to access. I could possibly figure that out with a macro, but some of the details are still unclear to me. I also don't want side effects if annotate, def, mac, or anything like that was run from the no-side-effect'd code.

At any rate, I think just wrapping in a let is insufficient, even if you do know all the variable names. E.g. you can break out of the sandbox with scdr:

> (= a '(1 2))

(1 2)

> (let a a (scdr a '(3)))

(3)

> a

(1 3)

-----

More