6 Replies Latest reply on Feb 10, 2009 9:59 AM by Newsgroup_User

    Interesting code shortcut - lists definitions as objects

    Level 7
      Even though I've been using Director since - what, D4? Been a while anyhow.
      But sometimes, it still surprises me. Just discovered a new trick. Used to
      be, if I wanted a random value out of a list, I'd have to do something like:

      aList = ["Bob","Joe","Henry","Steve"]
      aVal = aList[random(4)]

      But I just noticed that you can actually treat a list definition as an
      object, and thus reduce that to one line, and not bother creating an extra
      list variable that you don't use anywhere like so:

      aVal = ["Bob","Joe","Henry","Steve"][random(4)]

      Nice. Also works for any variable in place of the random number, of course.
      Now, I'm not sure if there's a performance difference for doing it this way,
      but I'd imagine it'd be quicker if anything, due to one less variable being
      generated. You can also mix it up several times in one line:

      aVal = ["Bob","Joe","Henry","Steve"][random(4)] &&
      ["Smith","Jones","Johnson","Brown"][random(4)]

      That would replace 3 lines of code with the traditional method, as each list
      would have to be declared separately, and then a third would pull the
      values. One minor downside is that if I have a list which might be variable
      in count, the old method is still needed, i.e.:

      aList = ["Bob","Joe","Henry","Steve"]
      aVal = aList[random(aList.count)]

      Then if I later add a new name to the aList, I don't have to change the
      second line. With the shortcut method, I do need to know the count of items
      beforehand, so if I add a name, I have to increase the count as well.
      Unless I duplicate the list like so:

      aVal =
      ["Bob","Joe","Henry","Steve"][random(["Bob","Joe","Henry","Steve"].count)]

      Which is kind of pointless, as I'd still need to add any new name to both
      copies of the list. Even so, there are so many places where this trick can
      reduce the amount of code needed to perform certain tasks. And it appears
      to be almost completely undocumented. I looked at all the help file pages
      relating to lists, and the only place where I can see code of this type
      being used is in reference to the "count" property, where it gives the
      example:

      put [10,20,30].count
      -- 3

      Nothing about using [list definition][index] to pick a parameter out of a
      list on the fly. Has anyone else used this technique? Or know of any
      reason why it might not be such a good idea? I haven't noticed any
      performance issues doing this, unless maybe it's one of those things that
      builds up and leaks memory over time or something. But it seems to work
      fine for me.


        • 1. Re: Interesting code shortcut - lists definitions as objects
          James Newton, ACP Level 3
          Well spotted! But take a step back and see what this implies.

          If you are working in the Message window to test an idea, then this might save you a few seconds of typing. If you are writing a script, then you really want to keep your data and your logic separate. Muddling the two together makes it much more difficult to maintain your code.

          Most programmers spend more time maintaining code (i.e. fixing bugs, retrofitting features) than they do writing the original code. As a result, ease-of-maintenance is important. To create a random name, I would create something like the parent script below.

          Here's an example of how to use it:

          vInstance = script("Random Name").new()
          put vInstance.GetRandomName() -- repeat several times
          -- "Bob Johnson"
          -- "Bob Jones"
          -- "Henry Jones"
          -- "Henry Smith"
          -- "Henry Jones"
          -- "Joe Johnson"
          -- "Joe Johnson"

          Do you see how the same first names and last names can appear twice or more in a row, and how the same complete name can appear twice? Perhaps you don't want this in your program. That's where ease of maintenance is important.

          If you uncomment the two lines in the mGetRandom() handler, this can no longer happen. It's easy to see where to make the change. Suppose you want to add more names to the lists? It's easy to see where to do that, too.

          Keeping the data and the logic apart mean that someone else can quickly see where and how to fix your code. That "someone" may be you in two weeks' time.

          A non-negligeable bonus: several well-though-out lines of code often execute faster than a single line of spaghetti.

          Moral: If it's throwaway code for the Message window, then your new discovery is useful. Just don't confuse typing fewer lines of code with saving time.
          • 2. Re: Interesting code shortcut - lists definitions as objects
            Production Monkey Level 3
            I have used this technique a few times over the years and I have seen others post code that use it.

            In general I don't find many situations where instantiating a list momentarily to pull a value out of it is helpful. The same would be true of strings. i.e.

            put "abcdefghijkl".char[random(1,12)]

            The only time I have used this technique is when creating a random number that can be positive or negative, and is restricted to a certain range.

            Val = random(10, 100) * [-1,1][random(1,2)]

            I agree with everything James said. However, momentary instantiation of a throw away object has its occasional uses.
            • 3. Re: Interesting code shortcut - lists definitions as objects
              Level 7
              > Do you see how the same first names and last names can appear twice or
              > more in
              > a row, and how the same complete name can appear twice? Perhaps you don't
              > want
              > this in your program. That's where ease of maintenance is important.

              Well, for the record, creating fake random names is not what I'm actually
              doing with this code right now, I just used that as an example that's easy
              to understand. In the situation I'm currently working on, there's
              absolutely nothing wrong with having duplicate entries in a row. (It's a
              random level-generator for a game.) Though of course, if I *were* using
              this to generate random names, I'd probably have a lot more than 4 first and
              last names to choose from, which would greatly reduce the odds of
              duplicates.

              Here's a pseudo-example of some of the code I actually used this for. I
              have a bunch of graphics that are referred to as <some gem>Gem<some number>,
              e.g.: "RubyGem3". For each of 10 gem types, there are 12 frames, which show
              the gem rotating, but because I don't want all the gems rotating in perfect
              sync, I have them start on a random frame. I converted a long case
              statement with a good deal of redundant code into one simple line that does
              the exact same thing:

              Old code:

              spr = sprite(me.spriteNum)
              case random(10) of
              1: spr.member = "RubyGem" & random(12)
              2: spr.member = "EmeraldGem" & random(12)
              3: spr.member = "DiamondGem" & random(12)
              4: spr.member = "SapphireGem" & random(12)
              5: spr.member = "AmethystGem" & random(12)
              6: spr.member = "OnyxGem" & random(12)
              7: spr.member = "JadeGem" & random(12)
              8: spr.member = "TopazGem" & random(12)
              9 spr.member = "OpalGem" & random(12)
              10: spr.member = "GarnetGem" & random(12)
              end case

              New code:

              spr = sprite(me.spriteNum)
              spr.member =
              ["Ruby","Emerald","Diamond","Sapphire","Amethyst","Onyx","Jade","Topaz","Opal","Garnet"][ random(10)]
              & "Gem" & random(12)

              And that's one of the simpler examples of where this sort of thing is used.
              There are some types of objects for which I'm pulling from multiple random
              lists, which previously required 2 or even 3 levels of nested case and/or if
              statements, all of which can be simply reduced to a single line. I'm pretty
              certain that this will execute faster, mainly because I've watched the
              debugger parsing a case statement, and I know that if, say, that first
              random number rolls a 10, then Director performs 10 value comparisons before
              executing the code. Whereas with the new method, it only has to look up one
              value in a list. Now, I don't know how it'd compare to:

              spr = sprite(me.spriteNum)
              gemList =
              ["Ruby","Emerald","Diamond","Sapphire","Amethyst","Onyx","Jade","Topaz","Opal","Garnet"]
              spr.member = gemList[random(gemList.count)] & "Gem" & random(12)

              Except that this method unnecessarily creates a new variable in the form of
              a list which will never be referenced again, thus wasting a bit of memory.
              Multiply that by the 300-odd items executing this code and it really starts
              to add up. I could make the gemList a global declared at startMovie or
              something, but now I've got this global that is only used in one place and
              never altered, so that's also somewhat wasteful.

              Unfortunately, while it's relatively easy to compare various coding methods
              for speed, (execute a large number of times and count ticks before and
              after), it's somewhat harder to compare them in terms of memory usage, or
              for that matter memory leaks. I'm not sure how Director treats this small
              chunk of data that it creates temporarily for the sole purpose of grabbing a
              value from it. Does it go away when the handler ends? Or does it stick
              around along as the movie is running? If it's the latter, then I guess
              there's not really much difference between this and creating a list
              separately - only difference is now the data has a name that can be accessed
              later if you want - even though I don't need to ever. However, if the
              temporary data is purged as soon as the handler finishes running, this would
              definitely save memory in the long run, especially when multiplied so many
              times.


              • 4. Re: Interesting code shortcut - lists definitions as objects
                Level 7
                Another point, or possibly to add to a point made, it's a lot easier to use
                the debugger when your code is a bit more verbose. Even if I need to do
                something like this:

                sprite (me.spriteNum).locH = SendAllSprites (#GetMovieTime)
                (where GetMovieTime is a handler attached to a video sprite that returns the
                current movie time)

                which of course isn't something you'd see very often but for illustrative
                purposes, I think it will work...

                I would actually do this

                newLocH = SendAllSprites (#GetMovieTime)
                sprite (me.spriteNum).locH = newLocH

                that way, if I need to put a breakpoint into this code, I'll be able to see
                the value of newLocH. I find that I need to do this type of thing a lot.
                And if I do bunch up code into as few lines as possible, I inevitably break
                it up to figure out what's going wrong (not that my code ever goes wrong ;))

                It actually makes code easier to read even though it seems that we're all
                taught at some point to put as much into on line as possible.

                Craig


                "Darrel Hoffman" <no.address@all.com> wrote in message
                news:gmfj3q$gk2$1@forums.macromedia.com...
                > Even though I've been using Director since - what, D4? Been a while
                > anyhow. But sometimes, it still surprises me. Just discovered a new
                > trick. Used to be, if I wanted a random value out of a list, I'd have to
                > do something like:
                >
                > aList = ["Bob","Joe","Henry","Steve"]
                > aVal = aList[random(4)]
                >
                > But I just noticed that you can actually treat a list definition as an
                > object, and thus reduce that to one line, and not bother creating an extra
                > list variable that you don't use anywhere like so:
                >
                > aVal = ["Bob","Joe","Henry","Steve"][random(4)]
                >
                > Nice. Also works for any variable in place of the random number, of
                > course. Now, I'm not sure if there's a performance difference for doing it
                > this way, but I'd imagine it'd be quicker if anything, due to one less
                > variable being generated. You can also mix it up several times in one
                > line:
                >
                > aVal = ["Bob","Joe","Henry","Steve"][random(4)] &&
                > ["Smith","Jones","Johnson","Brown"][random(4)]
                >
                > That would replace 3 lines of code with the traditional method, as each
                > list would have to be declared separately, and then a third would pull the
                > values. One minor downside is that if I have a list which might be
                > variable in count, the old method is still needed, i.e.:
                >
                > aList = ["Bob","Joe","Henry","Steve"]
                > aVal = aList[random(aList.count)]
                >
                > Then if I later add a new name to the aList, I don't have to change the
                > second line. With the shortcut method, I do need to know the count of
                > items beforehand, so if I add a name, I have to increase the count as
                > well. Unless I duplicate the list like so:
                >
                > aVal =
                > ["Bob","Joe","Henry","Steve"][random(["Bob","Joe","Henry","Steve"].count)]
                >
                > Which is kind of pointless, as I'd still need to add any new name to both
                > copies of the list. Even so, there are so many places where this trick
                > can reduce the amount of code needed to perform certain tasks. And it
                > appears to be almost completely undocumented. I looked at all the help
                > file pages relating to lists, and the only place where I can see code of
                > this type being used is in reference to the "count" property, where it
                > gives the example:
                >
                > put [10,20,30].count
                > -- 3
                >
                > Nothing about using [list definition][index] to pick a parameter out of a
                > list on the fly. Has anyone else used this technique? Or know of any
                > reason why it might not be such a good idea? I haven't noticed any
                > performance issues doing this, unless maybe it's one of those things that
                > builds up and leaks memory over time or something. But it seems to work
                > fine for me.
                >


                • 5. Re: Interesting code shortcut - lists definitions as objects
                  James Newton, ACP Level 3
                  quote:

                  Originally posted by: Newsgroup User
                  gemList =
                  ["Ruby","Emerald","Diamond","Sapphire","Amethyst","Onyx","Jade","Topaz","Opal","Garnet"]
                  spr.member = gemList[random(gemList.count)] & "Gem" & random(12)

                  Except that this method unnecessarily creates a new variable in the form of
                  a list which will never be referenced again, thus wasting a bit of memory.
                  Multiply that by the 300-odd items executing this code and it really starts
                  to add up.


                  Are you basing this assertion on empirical tests, or on simple expectation? Creating a list, then destroying it takes a number of CPU cycles, on each of your 300-odd iterations. Creating it once, then using it 300 times saves 299 of those extra chunks of CPU time. My test code is below. I find that it is faster to maintain the list in memory while all 300 random items are created, then destroy it at the end. What results do you get on your machine?
                  • 6. Re: Interesting code shortcut - lists definitions as objects
                    Level 7
                    > Are you basing this assertion on empirical tests, or on simple
                    > expectation?
                    > Creating a list, then destroying it takes a number of CPU cycles, on each
                    > of
                    > your 300-odd iterations. Creating it once, then using it 300 times saves
                    > 299
                    > of those extra chunks of CPU time. My test code is below. I find that it
                    > is
                    > faster to maintain the list in memory while all 300 random items are
                    > created,
                    > then destroy it at the end. What results do you get on your machine?

                    That might work if gems were the only thing I was populating the board with.
                    However, there's actually dozens of other things that the blocks could be
                    besides gems, and I'd need to create about 20 or so such global lists. On
                    top of that, about 75% of the blocks end up being empty spaces, and thus
                    don't need to pull values from any lists. So the way it works is basically
                    a series of case statements - 25% of the blocks are assigned to be -
                    something. Then based on a random number it decides if that something will
                    be a gem (of 10 types and 12 starting frames), a bonus item (one of 12), a
                    block (of 8 different materials), a machine (of 6 different types, each with
                    several variants depending on type), etc. It's really a lot more code than
                    I want to post here, but the long and short of it is that only about 5-10%
                    of the blocks end up being gems, and the others (that aren't blank spaces)
                    each pull from their own random lists based on what type they wind up being.

                    I suppose there might be a minor performance improvement using a bunch of
                    global lists vs. generating them on the fly, but since this code only
                    happens once (at the beginning of each level), and my current code is
                    certainly fast *enough* - the whole board generates nearly instantly - I
                    don't see much need to completely redo everything to use globals, and thus
                    saddle myself with increased overhead (however small) from having all those
                    globals in memory even after they're no longer needed.