From: Leslie P. Polzer
Subject: Series solution?
Date: 
Message-ID: <37439b55-0649-4035-8ad2-210340e278a7@e39g2000hsf.googlegroups.com>
Problem:

Model the adventure results for a player. Adventures consist of a
number of sub-adventures (RUNS), ranging from 1 to 12 (integral). Each
sub-adventure returns the multiple values CLASS GOLD+ XP+ HP- where
CLASS is an integer classification of the result ranging from 0 to 5.
The end result consists of

  * the number of successful runs
  * whether all runs were of class 0 (this means total failure)
  * the adjusted sum of GOLD+ and XP+
  * the plain sum of HP-
  * the mode of the classes

If the sum total of HP- is larger than the player's current hit points
(PLAYER-HP), don't do any more runs and return the current results.

I tried to solve this with SERIES, but I'm a beginner at it and
haven't succeeded even remotely, because the problem is a mix of
termination conditions, reduction and collection.

Here's a LOOP solution that does what I need (slightly simplified):

    (loop until (>= hp-- PLAYER-HP)
          for nsubs from 1 to RUNS
          for (class gold+ xp+ hp-) = (multiple-value-list (sub-
adventure-results))

          summing gold+ into gold++
          summing xp+ into xp++
          summing hp- into hp--
          collecting class into classes

          finally (return (values
                            (1- nsubs)
                            (not (every (curry #'eql 0) classes))
                            (time-adjusted-bonus gold++)
                            (time-adjusted-bonus xp++)
                            hp--
                            (car (random-distributions::mode
classes)))))

Any general suggestions are also welcome.
I'm not very interested in recursive solutions, though.

I hope I managed to state the problem clearly.

  Leslie

From: Rainer Joswig
Subject: Re: Series solution?
Date: 
Message-ID: <joswig-391B9A.12351919062008@news-europe.giganews.com>
In article 
<····································@e39g2000hsf.googlegroups.com>,
 "Leslie P. Polzer" <·············@gmx.net> wrote:

> Problem:
> 
> Model the adventure results for a player. Adventures consist of a
> number of sub-adventures (RUNS), ranging from 1 to 12 (integral). Each
> sub-adventure returns the multiple values CLASS GOLD+ XP+ HP- where
> CLASS is an integer classification of the result ranging from 0 to 5.
> The end result consists of
> 
>   * the number of successful runs
>   * whether all runs were of class 0 (this means total failure)
>   * the adjusted sum of GOLD+ and XP+
>   * the plain sum of HP-
>   * the mode of the classes
> 
> If the sum total of HP- is larger than the player's current hit points
> (PLAYER-HP), don't do any more runs and return the current results.
> 
> I tried to solve this with SERIES, but I'm a beginner at it and
> haven't succeeded even remotely, because the problem is a mix of
> termination conditions, reduction and collection.
> 
> Here's a LOOP solution that does what I need (slightly simplified):
> 
>     (loop until (>= hp-- PLAYER-HP)
>           for nsubs from 1 to RUNS
>           for (class gold+ xp+ hp-) = (multiple-value-list (sub-
> adventure-results))
> 
>           summing gold+ into gold++
>           summing xp+ into xp++
>           summing hp- into hp--
>           collecting class into classes
> 
>           finally (return (values
>                             (1- nsubs)
>                             (not (every (curry #'eql 0) classes))
>                             (time-adjusted-bonus gold++)
>                             (time-adjusted-bonus xp++)
>                             hp--
>                             (car (random-distributions::mode
> classes)))))
> 
> Any general suggestions are also welcome.
> I'm not very interested in recursive solutions, though.
> 
> I hope I managed to state the problem clearly.
> 
>   Leslie

a few remarks:

* I never really wanted to use Series that much, even though I tried it a few times

* you can do    for nsubs below RUNS  and get rid of  the 1-

* you might want to use NOTEVERY instead of (NOT (EVERY ...))
  you can get rid of (curry #'eql 0) by just using #'zerop

  (notevery #'zerop classes)

CL-USER 3 > (notevery #'zerop '(0 0 0 0 1 0 0 0))
T

CL-USER 4 > (notevery #'zerop '(0 0 0 0 0 0 0 0))
NIL


* your usage of LOOP has an error: you can't use UNTIL at that place
  as the first clause:

  LOOP has the syntax:

    loop [name-clause] {variable-clause}* {main-clause}* => result*

  UNTIL is not a variable-clause, but a main-clause.

  You have to use UNTIL after all FOR, AS and WITH clauses.

  Many Lisp implementations will run similar code, some not.
  It is not ANSI CL anyway, so it is best to avoid it
  for portable code.


If LOOP is too ugly or lacks a feature, the ITERATE macro
is a good alternative. I found SERIES to be more trouble
than it is worth (IMHO) for more than trivial tasks.

-- 
http://lispm.dyndns.org/
From: Leslie P. Polzer
Subject: Re: Series solution?
Date: 
Message-ID: <f5bf3875-2859-482d-b8d1-1810aaa66feb@v26g2000prm.googlegroups.com>
Thanks Rainer,

helpful as always.

  Leslie
From: Raymond Toy (RT/EUS)
Subject: Re: Series solution?
Date: 
Message-ID: <sxdtzfpb6az.fsf@rtp.ericsson.se>
>>>>> "Leslie" == Leslie P Polzer <·············@gmx.net> writes:

    Leslie> Model the adventure results for a player. Adventures consist of a
    Leslie> number of sub-adventures (RUNS), ranging from 1 to 12 (integral). Each
    Leslie> sub-adventure returns the multiple values CLASS GOLD+ XP+ HP- where
    Leslie> CLASS is an integer classification of the result ranging from 0 to 5.
    Leslie> The end result consists of

    Leslie>   * the number of successful runs
    Leslie>   * whether all runs were of class 0 (this means total failure)
    Leslie>   * the adjusted sum of GOLD+ and XP+
    Leslie>   * the plain sum of HP-
    Leslie>   * the mode of the classes

    Leslie> If the sum total of HP- is larger than the player's current hit points
    Leslie> (PLAYER-HP), don't do any more runs and return the current results.

    Leslie> I tried to solve this with SERIES, but I'm a beginner at it and
    Leslie> haven't succeeded even remotely, because the problem is a mix of
    Leslie> termination conditions, reduction and collection.

    Leslie> Here's a LOOP solution that does what I need (slightly simplified):

Why do you need to use series?

    Leslie>     (loop until (>= hp-- PLAYER-HP)
    Leslie>           for nsubs from 1 to RUNS
    Leslie>           for (class gold+ xp+ hp-) = (multiple-value-list (sub-
    Leslie> adventure-results))

    Leslie>           summing gold+ into gold++
    Leslie>           summing xp+ into xp++
    Leslie>           summing hp- into hp--
    Leslie>           collecting class into classes

    Leslie>           finally (return (values
    Leslie>                             (1- nsubs)
    Leslie>                             (not (every (curry #'eql 0) classes))
    Leslie>                             (time-adjusted-bonus gold++)
    Leslie>                             (time-adjusted-bonus xp++)
    Leslie>                             hp--
    Leslie>                             (car (random-distributions::mode
    Leslie> classes)))))

I think if you use COLLECT-FN you can get what you want, mostly.  The
series-input could be (scan-range :below RUNS), and the collecting
function would call SUB-ADVENTURE-RESULTS repeatedly and and
accumulate class, gold+, xp+, and hp- appropriately.

I guess COLLECT-FN would return 4 values, and you would then massage
those 4 values to return the 6 values that the loop would return.

Ray
From: Leslie P. Polzer
Subject: Re: Series solution?
Date: 
Message-ID: <50a16117-3a60-4b77-a4a2-b4eade204b4d@l64g2000hse.googlegroups.com>
> Why do you need to use series?

I don't need to, of course. Just curious :)

I thought there might be a neat solution that builds a series
of SUB-ADVENTURE-RESULTS and then works on them...

Naturally your proposed way of using series doesn't work that
way...
From: Raymond Toy (RT/EUS)
Subject: Re: Series solution?
Date: 
Message-ID: <sxdprqcb6rk.fsf@rtp.ericsson.se>
>>>>> "Leslie" == Leslie P Polzer <·············@gmx.net> writes:

    >> Why do you need to use series?

    Leslie> I don't need to, of course. Just curious :)

    Leslie> I thought there might be a neat solution that builds a series
    Leslie> of SUB-ADVENTURE-RESULTS and then works on them...

    Leslie> Naturally your proposed way of using series doesn't work that
    Leslie> way...

How about this then:

(scan-fn (values t t t t)
         #'(lambda ()
             (sub-adventure-results))
         #'(lambda (t1 t2 t3 t4)
             (declare (ignore t1 t2 t3 t4))
             (sub-adventure-results)))

This should produce 4 separate series, one for each of the values
returned by sub-adventure-results.

Then you should be able combine the 4 series together in the way that you want.

Ray
From: Leslie P. Polzer
Subject: Re: Series solution?
Date: 
Message-ID: <37ae5677-cb26-4c4c-b0d1-4fbcce66ac17@m36g2000hse.googlegroups.com>
> How about this then:
>
> (scan-fn (values t t t t)
>          #'(lambda ()
>              (sub-adventure-results))
>          #'(lambda (t1 t2 t3 t4)
>              (declare (ignore t1 t2 t3 t4))
>              (sub-adventure-results)))
>
> This should produce 4 separate series, one for each of the values
> returned by sub-adventure-results.
>
> Then you should be able combine the 4 series together in the way that you want.

Heh, yeah, I got this far.

But the central question is what happens then. It seems I need some
combination
of UNTIL-IF, COLLECT and whatever, but I couldn't think of any clean
solution.
From: Raymond Toy (RT/EUS)
Subject: Re: Series solution?
Date: 
Message-ID: <sxdlk10aqm7.fsf@rtp.ericsson.se>
>>>>> "Leslie" == Leslie P Polzer <·············@gmx.net> writes:

    >> How about this then:
    >> 
    >> (scan-fn (values t t t t)
    >> #'(lambda ()
    >> (sub-adventure-results))
    >> #'(lambda (t1 t2 t3 t4)
    >> (declare (ignore t1 t2 t3 t4))
    >> (sub-adventure-results)))
    >> 
    >> This should produce 4 separate series, one for each of the values
    >> returned by sub-adventure-results.
    >> 
    >> Then you should be able combine the 4 series together in the way that you want.

    Leslie> Heh, yeah, I got this far.

    Leslie> But the central question is what happens then. It seems I
    Leslie> need some combination of UNTIL-IF, COLLECT and whatever,
    Leslie> but I couldn't think of any clean solution.

I don't use series too often anymore, so it always takes a while to
figure this out.

First, your loop has a running sum of hp- in hp-- which you compare
against player-hp.  So you need a cumsum function:

(defun cumsum (s)
  (declare (optimizable-series-function 1))
  (collecting-fn t
		 #'(lambda () 0)
		 #'(lambda (sum x)
		     (+ sum x))
		 s))

Then something like this might work:

(defun run-it ()
  (multiple-value-bind (class gold+ xp+ hp-)
      (scan-fn '(values t t t t)
	       #'(lambda ()
		   (sub-adventure-results))
	       #'(lambda (t1 t2 t3 t4)
		   (declare (ignore t1 t2 t3 t4))
		   (sub-adventure-results)))
    (multiple-value-bind  (c g xp hp nsubs)
	(cotruncate class gold+ xp+ (until-if #'(lambda (x)
						  (>= x player-hp))
					      (cumsum hp-))
		    (scan-range :from 1 :upto runs))
      (values (collect-last nsubs)
	      (not (every (curry #'eql 0) (collect c)))
	      (time-adjusted-bonus (collect-sum g))
	      (time-adjusted-bonus (collect-sum xp))
	      (collect-last hp)
	      (car (random-distributions::mode (collect c)))))))

The UNTIL-IF is to stop the iterations if hp-- >= player-hp.  The
SCAN-RANGE is for the max number of runs.  The call to COTRUNCATE is
it make all the series have the same length.

Or maybe the termination cases can be placed in SCAN-FN itself.  I
think that would make it even harder to understand.

There are probably better ways to do this, but that's all I could
think of in a few minutes.

Ray