views:

185

answers:

3

I have a generator that takes a number as an argument and yields other numbers. I want to use the numbers yielded by this generator and pass them as arguments to the same generator, creating a chain of some length.

For example, mygenerator(2) yields 5, 4 and 6. Apply mygenerator to each of these numbers, over and over again to the numbers yielded. The generator always yields bigger numbers than the one passed as argument, and for 2 different numbers will never yield the same number.

mygenerator(2): 4 5 mygenerator(4) : 10 11 12 mygenerator(5): 9 300 500

So the set (9,10,11,12,300,500) has "distance" 2 from the original number, 2. If I apply it to the number 9, I will get a set of numbers with distance "3" from the original 2.

Essentially what I want is to create a set that has a specified distance from a given number and I have problems figuring out how to do that in Python. Help much appreciated :)

A: 

I have just started learning Python so bear with me if my answer seems a tad amateurish. What you could do is use a list of lists to populate the values returned from the myGenerator function.

So for eg. with 2 as the starting argument your data-structure would resemble something like

resDataSet = [[2], 
              [4, 5],
              [9, 10, 11, 12, 300 , 500]
              ...
             ]

The row index should give you the distance and you can use methods like extend to add on more data to your list.

muteW
+3  A: 

Suppose our generator yields square and cube of given number that way it will output unique so if we want to get numbers at dist D in simplest case we can recursively get numbers at dist D-1 and then apply generator to them

def mygen(N):
    yield N**2
    yield N**3

def getSet(N, dist):
    if dist == 0:
        return [N]

    numbers = []
    for n in getSet(N, dist-1):
        numbers += list(mygen(n))

    return numbers

print getSet(2,0)
print getSet(2,1)
print getSet(2,2)
print getSet(2,3)

output is

[2]
[4, 8]
[16, 64, 64, 512]
[256, 4096, 4096, 262144, 4096, 262144, 262144, 134217728]
Anurag Uniyal
+1  A: 

This solution does not require to keep all results in memory: (in case it doesn't fit in memory etc)

def grandKids(generation, kidsFunc, val):
  layer = [val]
  for i in xrange(generation):
    layer = concat(itertools.imap(kidsFunc, layer))
  return layer

def concat(xs):
  for x in xs:
    for val in x:
      yield val

Example:

def kids(x): # children indices in a 1-based binary heap
  yield x*2
  yield x*2+1

>>> list(grandKids(3, kids, 2))
[16, 17, 18, 19, 20, 21, 22, 23]

Btw, solution in Haskell:

grandKids generation kidsFunc val =
  iterate (concatMap kidsFunc) [val] !! generation
yairchu
Excellent answer! Thanks
ooboo
Obviously Python will never be as concise as Haskell for this type of problem, but you can at least save yourself the concat helper function by relying on itertools.chain instead.
John Y
@John Y: itertools.chain won't work (well). concat(xs) will be like chain(*xs). that will evaluate xs into a tuple and so will make it use much memory.
yairchu
@yairchu: that's interesting, but you're kind of cheating with the Python solution. If we wrote the Haskell's Prelude functions you use in Python (and that's not difficult), the function would look very similar:def grandKids(generation, kidsFunc, val): return takeN(iterate(partial(concatMap, kidsFunc), [val]), generation)Don't get me wrong, Haskell (and FP) is pure beauty, the moral here is that we can benefit of its lessons when using other languages.Full code: http://gist.github.com/406204
tokland