Arc Forumnew | comments | leaders | submit | map's commentslogin
1 point by map 6282 days ago | link | parent | on: Arc challenge: 8 Queens Problem

A bit shorter:

  def valid? stack
    q2 = stack.size - 1
    q2.times { |q1|
      return nil if stack[q1] == stack[q2] or (q1-q2).abs == (stack[q1]-stack[q2]).abs
    }
  end

  def queens stack, n
    if n == 8
      p stack
    else
      (1..8).each do |rank|
        stack.push(rank)
        queens(stack, n+1) if valid?(stack)
        stack.pop
      end
    end
  end

  queens [], 0

-----

1 point by map 6282 days ago | link | parent | on: Arc challenge: 8 Queens Problem

Ruby version of the wikipedia algorithm.

  class Array
    def rot
      push( shift )
    end
  end

  nqueens = proc{|n|
    odds, evens = (1..n).partition{|x| x % 2 > 0 }
    case n % 12
      when 2
        odds = [3,1] + odds[3..-1] + [5]
      when 3, 9
        odds.rot.rot
        evens.rot
      when 8
        d = -2
        odds.map!{|x| d *= -1; x + d}
    end
    p evens + odds
  }

  nqueens[8]

-----

2 points by map 6282 days ago | link

Shorter:

  class Array
    def rot
      push( shift )
    end
  end

  proc{|n| p ((1..n).partition{|x|x%2>0}.inject{|o,e|
    e + case n % 12
      when 2
        [3,1]+o[3..-1]+[5]
      when 3,9
        o.rot.rot;e.rot;o
      when 8
        d=-2;o.map{|x| x + d*=-1} end})}[ 8 ]

-----

1 point by cchooper 6282 days ago | link

Getting shorter:

  (def rot ((x . y)) (join y `(,x)))

  (def nqueens (n)
    (withs (m (mod n 12) r (range 1 n) od (keep odd r) cod (cddr od) s '(1 3) ev (keep even r))
           (if (join (pos m '(3 9)) (rot ev) (join cod s))
               (join ev (case m 8 (apply join (map rev (pair od)))
                                2 (join (rev s) (rot cod))
                                od)))))
The things Arc is missing from Ruby are partition and testing against multiple objects in a case. With those it would become:

  (def rot ((x . y)) (join y `(,x)))

  (def nqueens (n)
    (withs ((od ev) (part odd (range 1 n)) cod (cddr od) s '(1 3))
           (join ev (case (mod n 12) 8    (apply join (map rev (pair od)))
                                     2    (join (rev s) (rot cod))
                                     3, 9 (do (zap (rot ev)) (join cod s))
                                     od))))

-----

1 point by map 6281 days ago | link

Oops. Both of my Ruby programs above lack a default for the case statement. E.g., the shorter program needs after the last "when":

      else
       o

-----

2 points by map 6283 days ago | link | parent | on: Dotted lists - HUH! What are they good for?

from the newLISP manual

Evaluation of (cons x '()) yields (x), but (cons x nil) yields (x nil) because nil is treated as a boolean value when evaluated instead of as an empty list. The cons of two atoms in newLISP does not yield a dotted pair, but rather a two-element list. The predicate atom? is true for nil, but false for the empty list. The empty list in newLISP is only an empty list and not equal to nil.

A list in newLISP is a LISP cell of type list. It acts like a container for the linked list of elements making up the list cell's contents. There is no dotted pair in newLISP because the cdr (tail) part of a LISP cell always points to another LISP cell and never to a basic data type, such as a number or a symbol. Only the car (head) part may contain a basic data type.

-----

2 points by Jesin 6274 days ago | link

This is why Arc is better than newLISP. The only reason I can think of for this is that newLISP is trying to protect newbies from the actual structure of lists by telling them that cons adds an element to the beginning of a list, car gets the first element of a list, and cdr gets the rest of the list.

Arc doesn't try to protect people from these kinds of things. It's not like we can't handle the real meaning of cons.

-----


"number?" looks good to me. It's easier to guess what it does than to guess what anumber does. (And that's the type of name that Ruby would use.)

-----

3 points by almkglor 6286 days ago | link

Rather unfortunately, pg is playing around with syntax, and has thus reserved `?' for future syntax.

-----

1 point by map 6286 days ago | link | parent | on: Newbie questions about list operations

It's good to see that (lst n) works in Arc; you can't get any more concise than that!

Perhaps NewLisp's fast (push 'seven lst -1) will be added to Arc someday.

-----

3 points by absz 6286 days ago | link

If you want to get a feel for Arc, I recommend either reading or skimming the tutorial: http://ycombinator.com/arc/tut.txt . It covers the basics of Arc and its idioms.

I have three objections to (push obj lst -1), however. First, it's not pushing (since that only refers to the top of the list). Second, the last argument is meaningless: -1? Why that? Can I do (push obj lst 2)? (push obj lst -4)? And third, pushing on the end can't be as efficient: lists don't know their last element. If we push 'a onto '(b c d), we create a list whose car is 'a and whose cdr is '(b c d); if we shove 'a onto the end of '(b c d), we have to iterate until we find the element whose car is 'd and whose cdr is nil, and then set its cdr to '(a). That will take more time, and there's no way around that, so it's not fast, per se.

-----

3 points by map 6286 days ago | link

--- quote

Can I do (push obj lst 2)? (push obj lst -4)?

--- /quote

Yes, NewLisp:

  > (set 'x '(2 4 6))
  (2 4 6)
  > (push 3 x 1)
  3
  > x
  (2 3 4 6)
  > (push 5 x -2)
  5
  > x
  (2 3 4 5 6)

The next test indicates that appending to the end of a list is just as fast as pushing to the beginning of a list (times are in milliseconds).

Add at the beginning of the list:

  > (set 'x '(2))
  (2)
  > (time (push 8 x) 99000)
  40
  > (x 0)
  8
  > (x -1)
  2
Add at the end of the list:

  > (set 'x '(2))
  (2)
  > (time (push 8 x -1) 99000)
  40
  > (x -1)
  8
  > (length x)
  99001
Don't ask me how Lutz did it. (Maybe he keeps a pointer to the end of the list?) I think that lists in NewLisp aren't quite the same as lists in Common Lisp. Lutz wrote:

The concept of a 'cons cell' is about details of early LISP implementations (CPU registers, etc). newLISP also has a basic cell structure which can be linked to a list but has no concept of a dotted pair. When have you last seen a dotted pair used in a real world program? newLISP has 'cons' but it works differently,

By the way, it would be nice if Arc let you get the last element with -1, as NewLisp does:

  arc> ('(2 3 4) -1)
  Error: "list-ref: expects type <non-negative exact
  integer> as 2nd argument, given: -1;
  other arguments were: (2 3 4 . nil)"

-----

2 points by absz 6285 days ago | link

Huh. That (in my humble opinion) is a terribly misnamed function (push already has a specific meaning; what about ins, for insert, which is what it actually does?), but certainly a very useful one.

I'd be curious to see what (time (push 8 x 4)) looks like: is that also constant-time? What sort of data structure is newLISP using, if not a linked list? Linked list + end pointer, perhaps--but wouldn't that break on a push? Ah well. I (unfortunately) don't really have time to construct an in-depth analysis, but I'm still curious.

I'm not sure why the Anarki won't let you do ('(2 3 4) -1); it would indeed be useful. Mayhap there are efficiency concerns? Negative indexing works in cut.

-----

3 points by map 6285 days ago | link

In Ruby, push appends to a list; unshift prepends:

  E:\>irb --prompt xmp
  lst = [3,4,5]
      ==>[3, 4, 5]
  lst.push( 6 )
      ==>[3, 4, 5, 6]
  lst.unshift( 2 )
      ==>[2, 3, 4, 5, 6]
  lst.pop
      ==>6
  lst.shift
      ==>2
  lst
      ==>[3, 4, 5]
  lst << 6
      ==>[3, 4, 5, 6]
  lst += [7]
      ==>[3, 4, 5, 6, 7]
  lst
      ==>[3, 4, 5, 6, 7]

NewLisp can insert in the midst of the list just as fast.

  > (set 'x '(0 1 2 3 4 5 6))
  (0 1 2 3 4 5 6)
  > (time (push 8 x 4) 99000)
  50
  > (set 'x '(0 1 2 3 4 5 6))
  (0 1 2 3 4 5 6)
  > (time (push 8 x 4) 99000)
  40
  > (set 'x '(2))
  (2)
  > (time (push 8 x) 99000)
  40
Ruby has the opposite problem from conventional Lisp: it appends quickly, but prepends slowly:

  t = Time.now; a=[2]; 99_000.times{ a.push( 8 ) }; p Time.now - t
  0.14
      ==>nil
  t = Time.now; a=[2]; 99_000.times{ a.unshift( 8 ) }; p Time.now - t
  70.902
      ==>nil
  t = Time.now; a=[0,1,2,3,4]; 99_000.times{ a.insert(2, 8) }; p Time.now - t
  73.346
That's 73 seconds! NewLisp can insert in a list 1800 times as fast as Ruby can!

-----

1 point by absz 6285 days ago | link

Right. In Ruby, pushing, unshifting, and spliceing are all different operations, as they should be. Meaningful names are important.

It's not quite as fast to insert in the middle, but much faster than it should be. This would, to me, imply that newLISP isn't even using linked lists at all, but is instead using some form of array, which is decidedly unLispy. If that is the case (if, I say), then of course this will only work with "one reference only", as you can't construct lists which share cdrs; "one reference only" being an awful idea, I would consider the speed a fair tradeoff.

-----

4 points by almkglor 6286 days ago | link

I assume that NewLisp can transform looped code of the form:

  (while (something)
    (push obj lst -1))
to:

  (let tl (lastcdr lst)
    (while (something)
      (= (cdr tl) (cons obj nil))
      (= tl (cdr tl))))

-----

1 point by gaah 6281 days ago | link

Have you never seen a good implementation of singly-linked lists? Because accessing the last element is a relatively common operation, a decent implementation will keep a pointer to the last element. Because it's singly-linked, the last element is the only one that pointer is any good for, so (lst -2) is still O(n), but (lst -1) becomes O(1).

The weird thing is, I actually found this good idea in an APCS review book.

-----

3 points by sacado 6285 days ago | link

"It's good to see that (lst n) works in Arc; you can't get any more concise than that!"

Yes, you can be more concise : lst.n This is shorter (in number of chars), as a consequence it can be done with Arc :)

-----


Later a shorter Ruby one was posted that used regular expressions:

  S =
  [ /father|mother|brother|sister/i, "Tell me about your 0."],
  [ /\b(am|i'm) (.*)/i, ["Why are you 2?","Have you always been 2?"]],
  [ /\bI was (.*)/i, ["Why were you 1?","I can't believe you were 1."]],
  [ /\bI will (.*)/i, "Do you think it's wise to 1?"],
  [ /\bI (.*)/i, "Why do you 1?" ],
  [ /\b(you|your|yours)\b/i, ["We're talking about you, not me.",
             "Please don't be so personal."]],
  [ /.*/, ["That's very interesting. Do go on.",
        "Tell me more.",
        "I'm not sure that I understand you fully.",
        "Can you elaborate on that?" ]]

  P = Hash[ *"I|you|my|your|myself|yourself|you are|I am|you're|I am".
        split('|')]
  ( gets;sub(/[.!?,; ]+$/,"");x=Array(S.find{|a|$m=$_.match(a[0])}[1])
    puts x[rand(x.size)].gsub(/\d/){$m.to_a[$&.to_i].scan(
      /you are|you're|\w+|\W+/).map{|s|P[s]||P.invert[s]||s}.join}
  ) while 9

By eliminating the step of changing the person of pronouns in the user's input (e.g., "your" to "my"), a step that the original Lisp program didn't perform, the Ruby code was reduced to 2 lines (not counting the "script"):

  S =
  [ /father|mother|brother|sister/i, "Tell me about your 0."],
  [ /\b(am|i'm) (.*)/i, ["Why are you 2?","Have you always been 2?"]],
  [ /\bI was (.*)/i, ["Why were you 1?","I can't believe you were 1."]],
  [ /\bI will (.*)/i, "Do you think it's wise to 1?"],
  [ /\bI (.*)/i, "Why do you 1?" ],
  [ /\b(you|your|yours)\b/i, ["We're talking about you, not me.",
             "Please don't be so personal."]],
  [ /.*/, ["That's very interesting. Do go on.",
        "Tell me more.",
        "I'm not sure that I understand you fully.",
        "Can you elaborate on that?" ]]

  (gets;sub(/[.!?,; ]+$/,"");x=Array(S.find{|a|$m=$_.match(a[0])}[1])
  puts x[rand(x.size)].gsub(/\d/){$m.to_a[$&.to_i]}) while 9

-----

1 point by map 6285 days ago | link

Some explanation about the shorter Ruby program. Consider this line from the "script":

  [ /\bI was (.*)/i, ["Why were you 1?","I can't believe you were 1."]],
Regular expressions are usually enclosed in /.../. The "i" at the end makes it case insensitive, so the regex could just as well have contained "i was". "\b" means that "I" will match only at the beginning of a word; we don't want to match "cheri was in the room." The parentheses in the regex denote a "capture", the text of which can be recalled later. The dot (.) matches any single character; the star matches any number of the preceding item. So if "\bI was " is found,

  (.*)
will match the rest of the string.

I've slightly tweeked the "two lines" of the program and broken them into more lines. p, the programmer's print, has been used to show the state of some variables.

  (
    gets
Read a line from the keyboard; instead of assigning it to a variable, rely upon Ruby's assigning it to $_.

    sub( /[.!?,;\s]+$/, "" )
Since we don't say which string variable the sub should act upon, it acts on $_. We're removing puctuation and whitespace at the end of the user's input.

Assume the user typed "I was thinking."

    p $_   # "I was thinking"
The tail of the string has been sanitized.

    x = Array(
In the script, sometimes we have only 1 possible response by the computer (i.e., a string); sometimes we have more than 1 response (an array (list) of strings). Array( let's us easily normalize; it converts something that's not an array to an array of 1 element and leaves arrays untouched.

      S.find{|a|
Iterate through the script list and return the first item for which our code block returns true. a is the parameter of the code block that receives the value of each item.

        $m = $_.match(a[0])
a[0] is the regular expression; we save the match-data in $m.

      }[1]
[1] yields the last part of the script item, the computer's response or responses.

    )
    p x   # ["Why were you 1?", "I can't believe you were 1."]
Since the user typed "I was ...", the computer has 2 answers from which to choose.

    p $m.class   # MatchData
    p $m.to_a   # ["I was thinking", "thinking"]
.to_a converts match-data to an array. The first item is the complete match; the rest of the items are "captures".

    puts x[rand(x.size)].
Randomly choose the computers answer. The "." at the end means a method follows...

      gsub( /\d/ ){|s| $m.to_a[s.to_i] }   # Why were you thinking?
The .gsub finds every numeral (digit) in the string and replaces it with the corresponding item in the match-data array. "0" would be replaced by the entire match; "1" is replaced by the first capture.

  ) while 9
"while 0" would work as well. The only untrue things in Ruby are false and nil. You'll have to break out of the loop with a control-key combination.

-----

2 points by absz 6287 days ago | link

Please note that this forum doesn't use BBCode; to add code, you need to surround the code by blank lines and indent each line by two spaces or more. The indented lines will be formatted like code, and your * s won't disappear. It would be nice to see the code, but it's unreadable this way.

-----

1 point by map 6287 days ago | link

Thanks for the help. I was beginning to sweat.

-----