Arc Forumnew | comments | leaders | submitlogin
popnth
3 points by evanrmurphy 5565 days ago | 9 comments
How do you destructively remove the nth element of a list? Currently I do:

  (def rmnth (n lst)
    (join (firstn n lst) (nthcdr inc.n lst)))

  (mac popnth (n lst)
    `(= ,lst (rmnth ,n ,lst)))
I tried simply:

  (mac popnth (n lst)
    `(pop (nthcdr ,n ,lst)))
And shader taught me why this doesn't work [1], but is there a better solution than my above popnth via rmnth?

[1] http://arclanguage.org/item?id=11475



2 points by thaddeus 5565 days ago | link

  (= ls (list 1 2 3 4))

  (defset nthcdr (n xs)
    (w/uniq g
      (list (list g xs)
            `(nthcdr ,n ,g)
            `(fn (val) (scdr ,g val)))))

  arc> (pop (nthcdr 1 ls))
  2

  arc> ls
  (1 3 4)

-----

3 points by shader 5565 days ago | link

Actually, I don't think that's a complete solution. It sets the cdr of ls to be the nthcdr of ls. What you want is to set the nthcdr of ls to the nthcdr+1 of ls.

Ex. with your defset:

  arc> (= ls '(1 2 3 4 5))
  (1 2 3 4 5)
  arc> (pop (nthcdr 2 ls))
  3
  arc> ls
  (1 4 5)
As you can see, it effectively removed both the 2nd and 3rd elements, instead of just the 3rd.

A better defset would be:

  (defset nthcdr (n xs)
    (w/uniq g
      (list (list g xs)
            `(nthcdr ,n ,g)
            `(fn (val) (scdr (nthcdr (- ,n 1) ,g) val)))))
Ex:

  arc> (= a '(1 2 3 4 5))
  (1 2 3 4 5)
  arc> (pop (nthcdr 2 a))
  3
  arc> a
  (1 2 4 5)
With this version, only the nthcdr itself is popped.

-----

1 point by thaddeus 5565 days ago | link

yup, I should have seen that :)

-----

2 points by shader 5565 days ago | link

It's still not perfect. Since it's based on scdr, if you do (pop (nthcdr 0 a)), it doesn't pop the first element, which it probably should.

Oh well. I can't really imagine anyone would actually do that, given that you could use (pop a) instead ;)

-----

1 point by evanrmurphy 5565 days ago | link

Wow, looking back it was awfully dense of me to have your clear explanation about needing to define a setter and then coming over here to post some hack using join. Sorry for that.

Even though my popnth function works, it's a worse solution because by ignoring setforms it only works for the specific case of pop rather than the whole family of destructive functions. Is this accurate?

I'm a good deal more comfortable with the setter concept after your and thaddeus' examples. Thanks to both of you for your patience with a newb. I've been super impressed with this forum so far: the community is small but outstanding.

-----

1 point by shader 5564 days ago | link

Yep. By defining popnth, you get a single function that performs a specific task. But if you define a setform, now any function that wants to operate on a "place" can do so.

In theory, you can now do things like

  (= (nthcdr 3 lst) '(a s d f))
and set the 3rd cdr of lst to (a s d f)

-----

1 point by evanrmurphy 5565 days ago | link

That's probably more natural and efficient than my solution.

-----

2 points by garply 5565 days ago | link

I wrote a popnth in lib/util.arc a little while back which behaves well. Although it errors when you try to pop an empty list or a non-existent element - do you guys think it should return nil instead?

-----

1 point by rig 5565 days ago | link

I would write popnths this way:

    (def popnth (n ls)
      (case n
        0 (pop ls)
        1 (do1 (cadr ls) (scdr ls (cddr ls)))
        (popnth (- n 1) (cdr ls))))

-----