how to make a generator use the last yielded value when it regainscontrol

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • John Salerno

    how to make a generator use the last yielded value when it regainscontrol

    Ok, I wrote this all by myself, which explains why it doesn't work. It
    is meant to take a number and generate the next number that follows
    according to the Morris sequence. It works for a single number, but what
    I'd like it to do is either:

    1. repeat indefinitely and have the number of times controlled elsewhere
    in the program (e.g., use the morris() generator in a for loop and use
    that context to tell it when to stop)

    2. just make it a function that takes a second argument, that being the
    number of times you want it to repeat itself and create numbers in the
    sequence

    Here's the code so far, and any general comments are always appreciated
    as well:

    def morris(seed):
    seed = list(str(seed))
    grouping = []
    nextNum = []
    for i in range(len(seed) ):
    try:
    if seed[i] == seed[i + 1]:
    grouping.append (seed[i])
    else:
    grouping.append (seed[i])
    nextNum.append( str(len(groupin g)) + seed[i])
    grouping = []
    except IndexError:
    grouping.append (seed[i])
    nextNum.append( str(len(groupin g)) + seed[i])
    seed = ''.join(nextNum )
    yield seed

    I thought the second to last line might rebind 'seed' to the new value,
    and it would use that again the next time through, but it seems to just
    keep using the original value each time and yielding the same output.
  • John Salerno

    #2
    Re: how to make a generator use the last yielded value when it regainscontrol

    John Salerno wrote:
    [color=blue]
    > 1. repeat indefinitely and have the number of times controlled elsewhere
    > in the program (e.g., use the morris() generator in a for loop and use
    > that context to tell it when to stop)
    >
    > 2. just make it a function that takes a second argument, that being the
    > number of times you want it to repeat itself and create numbers in the
    > sequence[/color]

    Well, I suppose I could just do:

    num = 1
    for x in range(some_limi t):
    num = morris(num)
    print num,

    But that isn't as much of a challenge as the other two options. :) I'd
    like the function to do all the work of returning multiple numbers
    (which probably means that option 1 isn't the best either)

    Comment

    • John Salerno

      #3
      Re: how to make a generator use the last yielded value when it regainscontrol

      John Salerno wrote:[color=blue]
      > 2. just make it a function that takes a second argument, that being the
      > number of times you want it to repeat itself and create numbers in the
      > sequence[/color]

      Here's what I've come up with so far. Probably not the most elegant
      solution because of the nested function, but it does work! :)

      def morris(seed, limit):
      num = seed
      numberSet = []

      def nextNum(num):
      num = list(str(num))
      grouping = []
      nextNum = []
      for i in range(len(num)) :
      try:
      if num[i] == num[i + 1]:
      grouping.append (num[i])
      else:
      grouping.append (num[i])
      nextNum.append( str(len(groupin g)) + num[i])
      grouping = []
      except IndexError:
      grouping.append (num[i])
      nextNum.append( str(len(groupin g)) + num[i])
      return ''.join(nextNum )

      for x in range(limit):
      numberSet.appen d(int(num))
      num = nextNum(num)

      return numberSet

      Comment

      • Lonnie Princehouse

        #4
        Re: how to make a generator use the last yielded value when it regains control

        The generator in your original post /does/ rebind seed, but only on the
        last iteration of the loop. You'll need to wrap that loop in another
        loop if you want the generator to yield more than once.

        As for "communicat ing" with a generator --- e.g. telling it to stop ---
        this might be done by passing some kind of mutable argument to the
        generator and then changing the value of that mutable object. However,
        it's not a very elegant solution, and in this case there's not really
        any reason to do it. Instead, if you want the generator to stop, just
        stop asking it to yield:

        for number in morris_sequence _generator(seed ):
        if it_is_time_to_s top():
        break

        And:
        - No need to convert str(seed) to a list! Strings are indexable.
        - Instead of using "try...exce pt IndexError" to detect the end of the
        loop, just change the loop to range(len(seed) - 1) and do the yield
        after the loop finishes.
        - Use xrange instead of range. range is evil.

        Bonus points:
        Write the generator to work on a seed which is an iterable of unknown
        length.

        Super bonus points:
        Print the Nth element in the sequence without holding more than N
        groups of {digit, number of occurences} of state information. You'll
        need to do this if you want to get very far: According to Wikipedia,
        the 70th term of the look-and-say sequence has 179,691,598 digits.

        Comment

        • Ben Cartwright

          #5
          Re: how to make a generator use the last yielded value when it regains control

          John Salerno wrote:[color=blue]
          > It
          > is meant to take a number and generate the next number that follows
          > according to the Morris sequence. It works for a single number, but what
          > I'd like it to do is either:
          >
          > 1. repeat indefinitely and have the number of times controlled elsewhere
          > in the program (e.g., use the morris() generator in a for loop and use
          > that context to tell it when to stop)
          >
          > 2. just make it a function that takes a second argument, that being the
          > number of times you want it to repeat itself and create numbers in the
          > sequence[/color]

          Definitely go for (1). The Morris sequence is a great candidate to
          implement as a generator. As a generator, it will be more flexible and
          efficient than (2).

          def morris(num):
          """Generate the Morris sequence starting at num."""
          num = str(num)
          yield num
          while True:
          result, cur, run = [], None, 0
          for digit in num+'\n':
          if digit == cur:
          run += 1
          else:
          if cur is not None:
          result.append(s tr(run))
          result.append(c ur)
          cur, run = digit, 1
          num = ''.join(result)
          yield num

          # Example usage:
          from itertools import islice
          for n in islice(morris(1 ), 10):
          print n

          # Output:
          """
          1
          11
          21
          1211
          111221
          312211
          13112221
          1113213211
          31131211131221
          132113111231131 12211
          """

          --Ben

          Comment

          • John Salerno

            #6
            Re: how to make a generator use the last yielded value when it regainscontrol

            Ben Cartwright wrote:
            [color=blue]
            > Definitely go for (1). The Morris sequence is a great candidate to
            > implement as a generator. As a generator, it will be more flexible and
            > efficient than (2).[/color]

            Actually I was just thinking about this and it seems like, at least for
            my purpose (to simply return a list of numbers), I don't need a
            generator. My understanding of a generator is that you do something to
            each yielded value before returning to the generator (so that you might
            not return at all), but since I'm not handling the individual numbers,
            just getting a list, it seems I don't need them to be yielded. Of
            course, a generator would allow the process to be done over and over, I
            suppose, which is what I wanted, I just couldn't figure out how to keep
            using the new values.

            Comment

            • Ben Cartwright

              #7
              Re: how to make a generator use the last yielded value when it regains control

              John Salerno wrote:[color=blue]
              > Actually I was just thinking about this and it seems like, at least for
              > my purpose (to simply return a list of numbers), I don't need a
              > generator.[/color]

              Yes, if it's just a list of numbers you need, a generator is more
              flexibility than you need. A generator would only come in handy if,
              say, you wanted to give your users the option of getting the next N
              items in the sequence, *without* having to recompute everything from
              scratch.

              [color=blue]
              > My understanding of a generator is that you do something to
              > each yielded value before returning to the generator (so that you might
              > not return at all),[/color]

              A generator is just an object that spits out values upon request; it
              doesn't care what the caller does with those values.

              There's many different ways to use generators; a few examples:

              # Get a list of the first 10
              from itertools import islice
              m = [n for n in islice(morris(1 ), 10)]

              # Prompt user between each iteration
              for n in morris(1):
              if raw_input('keep going? ') != 'y':
              break
              print n

              # Alternate way of writing the above
              g = morris(1)
              while raw_input('keep going? ') == 'y':
              print g.next()

              --Ben

              Comment

              • Lonnie Princehouse

                #8
                Re: how to make a generator use the last yielded value when it regains control

                Here's my take on the thing. It only prints one term, though.



                (a bit too long to post)

                Comment

                • Azolex

                  #9
                  Re: how to make a generator use the last yielded value when it regainscontrol

                  just couldn't help taking the bait...

                  def morris(seed) :

                  """[color=blue][color=green][color=darkred]
                  >>> m = morris('3447221 ')
                  >>> m.next()[/color][/color][/color]
                  '1324172211'[color=blue][color=green][color=darkred]
                  >>> m.next()[/color][/color][/color]
                  '11131214111722 21'[color=blue][color=green][color=darkred]
                  >>> m.next()[/color][/color][/color]
                  '31131112111431 173211'
                  """

                  assert isinstance(seed ,basestring) and seed.isdigit(), "bad seed"

                  def itially(z) :
                  feedback.z = z
                  while True :
                  yield feedback.z

                  def feedback(gen) :
                  while True :
                  feedback.z = gen.next()
                  yield feedback.z

                  def morrisify(numbe r) :
                  from itertools import groupby
                  for digit,sequence in groupby(number) :
                  yield str(len(tuple(s equence)))
                  yield digit

                  return feedback(''.joi n(morrisify(num ber))
                  for number in itially(seed))

                  Comment

                  • John Salerno

                    #10
                    Re: how to make a generator use the last yielded value when it regainscontrol

                    Lonnie Princehouse wrote:[color=blue]
                    > Here's my take on the thing. It only prints one term, though.
                    >
                    > http://www.magicpeacefarm.com/lonnie...morris.py.html
                    >
                    > (a bit too long to post)
                    >[/color]

                    yikes, scary! :)

                    there was always the hint that using itertools might be helpful, as you
                    guys are doing, but i could never quite figure out how, but looking at
                    these alternate methods is definitely helpful

                    Comment

                    • Michael Spencer

                      #11
                      Re: how to make a generator use the last yielded value when it regainscontrol

                      John Salerno wrote:[color=blue]
                      > Ben Cartwright wrote:
                      >[color=green]
                      >> Definitely go for (1). The Morris sequence is a great candidate to
                      >> implement as a generator. As a generator, it will be more flexible and
                      >> efficient than (2).[/color]
                      >
                      > Actually I was just thinking about this and it seems like, at least for
                      > my purpose (to simply return a list of numbers), I don't need a
                      > generator. My understanding of a generator is that you do something to
                      > each yielded value before returning to the generator (so that you might
                      > not return at all), but since I'm not handling the individual numbers,
                      > just getting a list, it seems I don't need them to be yielded. Of
                      > course, a generator would allow the process to be done over and over, I
                      > suppose, which is what I wanted, I just couldn't figure out how to keep
                      > using the new values.[/color]

                      itertools.group by makes this very straightforward :
                      [color=blue][color=green][color=darkred]
                      >>> from itertools import groupby[/color][/color][/color]
                      ....[color=blue][color=green][color=darkred]
                      >>> def lookandsay(seed ):[/color][/color][/color]
                      .... seed = str(seed)
                      .... while 1:
                      .... seed = "".join("%s %s" % (len(list(group )), item)
                      .... for item, group in groupby(seed))
                      .... yield seed
                      ....[color=blue][color=green][color=darkred]
                      >>>
                      >>> seq = lookandsay(1)
                      >>> seq.next()[/color][/color][/color]
                      '11'[color=blue][color=green][color=darkred]
                      >>> seq.next()[/color][/color][/color]
                      '21'[color=blue][color=green][color=darkred]
                      >>> seq.next()[/color][/color][/color]
                      '1211'[color=blue][color=green][color=darkred]
                      >>> seq.next()[/color][/color][/color]
                      '111221'[color=blue][color=green][color=darkred]
                      >>> seq.next()[/color][/color][/color]
                      '312211'

                      If you want to get just part of the infinite series, use itertools.islic e:
                      [color=blue][color=green][color=darkred]
                      >>> from itertools import islice
                      >>> list(islice(loo kandsay(1),10))[/color][/color][/color]
                      ['11', '21', '1211', '111221', '312211', '13112221', '1113213211',
                      '31131211131221 ', '13211311123113 112211', '11131221133112 132113212221'][color=blue][color=green][color=darkred]
                      >>> list(islice(loo kandsay(1),10,2 0))[/color][/color][/color]
                      ['31131122212321 121113122113121 13211',
                      '13211321321112 131221123113112 221131112211312 21',
                      '11131221131211 131231121113112 221121321132132 211331222113112 211',
                      '31131122211311 123113111213211 231132132211211 131221131211132 221231132211321 2221',
                      '13211321322113 311213211331121 113122112132113 121113222112311 311222113111231 133211121321132 2211312113211',
                      '11131221131211 132221232112111 312212321123113 112221121113122 113111231133221 121321132132211 331121321231231 121113122113322 113111221131221 ',
                      '31131122211311 123113321112131 221123113112211 121312211213211 321322112311311 222113311213212 322211211131221 131211132221232 112111312111213 111213211231131 122212322211331 222113112211',
                      '13211321322113 311213212312311 211131122211213 211321223112111 311222112111312 211312111322211 213211321322123 211211131211121 332211231131122 211311123113321 112131221123113 111231121113311 211131221121321 132132111213322 123113221132122 21',
                      '11131221131211 132221232112111 312111213111213 211231132132211 211131221131211 221321123113213 221123113112221 131112311332211 211131221131211 132211121312211 231131112311211 232221121321132 132211331121321 231231121113112 221121321133112 132112312321123 113112221121113 122113121113123 112112322111213 211322211312113 211',
                      '31131122211311 123113321112131 221123113111231 121113311211131 221121321131211 132221123113112 221131112212211 131221121321131 211132221121321 132132211331121 321232221123113 112221131112311 322311211131122 211213211331121 321122112133221 121113122113121 113222123211211 131211121311121 321123113213221 121113122123211 211131221121311 121312211213211 321322112311311 222113111231131 112132112211213 223112111312211 332211311122113 1221'][color=blue][color=green][color=darkred]
                      >>>[/color][/color][/color]


                      HTH
                      Michael

                      Comment

                      • John Salerno

                        #12
                        Re: how to make a generator use the last yielded value when it regainscontrol

                        Michael Spencer wrote:
                        [color=blue]
                        > itertools.group by makes this very straightforward :[/color]

                        I was considering this function, but then it seemed like it was only
                        used for determing consecutive numbers like 1, 2, 3 -- not consecutive
                        equivalent numbers like 1, 1, 1. But is that not right?

                        Comment

                        • Gerard Flanagan

                          #13
                          Re: how to make a generator use the last yielded value when it regains control

                          John Salerno wrote:
                          [color=blue]
                          > Michael Spencer wrote:
                          >[color=green]
                          > > itertools.group by makes this very straightforward :[/color]
                          >
                          > I was considering this function, but then it seemed like it was only
                          > used for determing consecutive numbers like 1, 2, 3 -- not consecutive
                          > equivalent numbers like 1, 1, 1. But is that not right?[/color]


                          data = [1, 1, 1, 2, 2, 3, 4, 4, 3, 2, 2, 1, 1, 2, 2,4, 2, 2]

                          from itertools import groupby


                          for k, g in groupby( data ):
                          print k, list(g)

                          1 [1, 1, 1]
                          2 [2, 2]
                          3 [3]
                          4 [4, 4]
                          3 [3]
                          2 [2, 2]
                          1 [1, 1]
                          2 [2, 2]
                          4 [4]
                          2 [2, 2]

                          for k, g in groupby( data, lambda x: x<2 ):
                          print k, list(g)

                          True [1, 1, 1]
                          False [2, 2, 3, 4, 4, 3, 2, 2]
                          True [1, 1]
                          False [2, 2, 4, 2, 2]

                          Gerard

                          Comment

                          • Michael Spencer

                            #14
                            Re: how to make a generator use the last yielded value when it regainscontrol

                            John Salerno wrote:[color=blue]
                            > Michael Spencer wrote:
                            >[color=green]
                            >> itertools.group by makes this very straightforward :[/color]
                            >
                            > I was considering this function, but then it seemed like it was only
                            > used for determing consecutive numbers like 1, 2, 3 -- not consecutive
                            > equivalent numbers like 1, 1, 1. But is that not right?[/color]


                            With one argument, groupby assembles groups of equal consecutive elements:[color=blue][color=green][color=darkred]
                            >>> list((key, list(group)) for key, group in groupby("AAABBC AAA"))[/color][/color][/color]
                            [('A', ['A', 'A', 'A']), ('B', ['B', 'B']), ('C', ['C']), ('A', ['A', 'A', 'A'])]

                            With a second keyfunc argument, groupby assembles groups where keyfunc(element )
                            is equal for consecutive elements[color=blue][color=green][color=darkred]
                            >>> list((key, list(group)) for key, group in groupby("AAAaaa AAA",str.isuppe r))[/color][/color][/color]
                            [(True, ['A', 'A', 'A']), (False, ['a', 'a', 'a']), (True, ['A', 'A', 'A'])][color=blue][color=green][color=darkred]
                            >>>[/color][/color][/color]

                            HTH
                            Michael

                            Comment

                            • John Salerno

                              #15
                              Re: how to make a generator use the last yielded value when it regainscontrol

                              Gerard Flanagan wrote:[color=blue]
                              > John Salerno wrote:
                              >[color=green]
                              >> Michael Spencer wrote:
                              >>[color=darkred]
                              >>> itertools.group by makes this very straightforward :[/color]
                              >> I was considering this function, but then it seemed like it was only
                              >> used for determing consecutive numbers like 1, 2, 3 -- not consecutive
                              >> equivalent numbers like 1, 1, 1. But is that not right?[/color]
                              >
                              >
                              > data = [1, 1, 1, 2, 2, 3, 4, 4, 3, 2, 2, 1, 1, 2, 2,4, 2, 2]
                              >
                              > from itertools import groupby
                              >
                              >
                              > for k, g in groupby( data ):
                              > print k, list(g)
                              >
                              > 1 [1, 1, 1]
                              > 2 [2, 2]
                              > 3 [3]
                              > 4 [4, 4]
                              > 3 [3]
                              > 2 [2, 2]
                              > 1 [1, 1]
                              > 2 [2, 2]
                              > 4 [4]
                              > 2 [2, 2]
                              >
                              > for k, g in groupby( data, lambda x: x<2 ):
                              > print k, list(g)
                              >
                              > True [1, 1, 1]
                              > False [2, 2, 3, 4, 4, 3, 2, 2]
                              > True [1, 1]
                              > False [2, 2, 4, 2, 2]
                              >
                              > Gerard
                              >[/color]

                              Interesting. After following along with the doc example, it seemed like
                              I had to do complicated stuff with the keys parameter, and I kind of
                              lost track of it all.

                              Comment

                              Working...