Arc Forumnew | comments | leaders | submitlogin
How can I build a list of lists quickly?
2 points by bh 3920 days ago | 2 comments
I used to use Python. If I wanted 3 lists from 1 to 10 I'd do:

    [range(10) for i in range(3)]
How would I do this in Arc?


6 points by fallintothis 3919 days ago | link

Short answer: to reproduce the exact example you have...

  arc> (n-of 3 (range 0 9))
  ((0 1 2 3 4 5 6 7 8 9) (0 1 2 3 4 5 6 7 8 9) (0 1 2 3 4 5 6 7 8 9))
Long answer:

Depends on what you're trying to do. :)

I could sit here and rattle off builtin Arc helper functions & macros, but there's a bigger lesson to be learned here about functional programming. Specifically, the list comprehensions you naturally gravitate towards are just syntactic sugar for the famous map & filter higher-order functions (https://en.wikipedia.org/wiki/Map_(higher-order_function) & https://en.wikipedia.org/wiki/Filter_(higher-order_function)).

In Python (at least in Python 2.x, where map and filter return lists instead of generators), the equivalence looks like this:

  [expr_involving_x for x in xs]      == map(lambda x: expr_involving_x, xs)
  [x for x in xs if expr_involving_x] == filter(lambda x: expr_involving_x, xs)
For example,

  >>> [x + 1 for x in [1,2,3]] == map(lambda x: x + 1, [1,2,3])
  True
  >>> [x for x in [1,2,3] if x == 2] == filter(lambda x: x == 2, [1,2,3])
  True
You can apply these transformations recursively until you remove the list comprehension notation altogether. E.g.,

  [x + 1 for x in [1,2,3] if x == 2] -> map(lambda x: x + 1, [x for x in [1,2,3] if x == 2])
                                     -> map(lambda x: x + 1, filter(lambda x: x == 2, [1,2,3]))
Nested comprehensions have a transformation rule, too, but it's somewhat more complicated:

  [expr_involving_y for x in xs for y in expr_involving_x] == sum([[expr_involving_y for y in expr_involving_x] for x in xs], [])
For example,

  >>> [y+1 for x in [1,2,3] for y in [x+1,x+2,x+3]] \
  ... == sum([[y+1 for y in [x+1,x+2,x+3]] for x in [1,2,3]], []) \
  ... == sum([map(lambda y: y+1, [x+1,x+2,x+3]) for x in [1,2,3]], []) \
  ... == sum(map(lambda x: map(lambda y: y+1, [x+1,x+2,x+3]), [1,2,3]), [])
  True
So, list comprehensions are just a way of saying the same things as higher-order functions. Arc generally favors the higher-order function flavor of this idiom. map is still called map, filter is called keep, and Python's sum(..., []) is spelled (apply join ...):

  arc> (map (fn (x) (+ x 1)) (list 1 2 3))
  (2 3 4)
  arc> (keep (fn (x) (is x 2)) (list 1 2 3))
  (2)
  arc> (apply join (map (fn (x) (map (fn (y) (+ y 1)) (list (+ x 1) (+ x 2) (+ x 3)))) (list 1 2 3)))
  (3 4 5 4 5 6 5 6 7)
You might think Arc's preference for these comes from Lisp dialects naturally eschewing specialized syntax. But, Arc does like syntactic shortcuts; just ones that apply more generally to functions, like bracket notation (e.g., [+ _ 1]) and ssyntax (e.g., ~f and f:g come up a lot in higher-order calls).

It's also interesting to note that due to the previous rules, you can easily write a macro that turns list comprehensions into the equivalent chain of higher-order function calls.

There are many Arc builtins that can help build lists, too. They're all defined in Arc itself, so just take a look through arc.arc to figure out how they work. Functions I spy that may help for list building (list of lists or otherwise): rem, mappend, trues, firstn, nthcdr, cut, tuples, pair, n-of, bestn, split, retrieve, dedup, reduce, rreduce, range, ... Really just depends on what you're looking to do!

-----

2 points by bh 3918 days ago | link

Wow, awesome answer. Thanks!

-----