Search code examples
pythonlistfunctional-programmingoperator-precedence

Left to right application of operations on a list in Python 3


Is there any possible way to achieve a non-lazy left to right invocation of operations on a list in Python?

E.g. Scala:

 val a = ((1 to 50)
  .map(_ * 4)
  .filter( _ <= 170)
  .filter(_.toString.length == 2)
  .filter (_ % 20 == 0)
  .zipWithIndex
  .map{ case(x,n) => s"Result[$n]=$x"}
  .mkString("  .. "))

  a: String = Result[0]=20  .. Result[1]=40  .. Result[2]=60  .. Result[3]=80

While I realize many folks will not prefer the above syntax, I like the ability to move left to right and add arbitrary operations as we go.

The Python for comprehension is IMO not easy to read when there are three or more operations. The result seems to be we're required to break everything up into chunks.

[f(a) for a in g(b) for b in h(c) for ..]

Is there any chance for the approach mentioned?

Note: I tried out a few libraries including toolz.functoolz. That one is complicated by Python 3 lazy evaluation: each level returns a map object. In addition, it is not apparent that it can operate on an input list.


Solution

  • The answer from @JohanL does a nice job of seeing what the closest equivalent is in standard python libraries.

    I ended up adapting a gist from Matt Hagy in November 2019 that is now in pypi

    https://pypi.org/project/infixpy/

    from infixpy import *
    a = (Seq(range(1,51))
         .map(lambda x: x * 4)
         .filter(lambda x: x <= 170)
         .filter(lambda x: len(str(x)) == 2)
         .filter( lambda x: x % 20 ==0)
         .enumerate() 
         .map(lambda x: 'Result[%d]=%s' %(x[0],x[1]))
         .mkstring(' .. '))
    print(a)
    
      # Result[0]=20  .. Result[1]=40  .. Result[2]=60  .. Result[3]=80
    

    Other approaches described in other answers

    Older approaches

    I found a more appealing toolkit in Fall 2018

    https://github.com/dwt/fluent

    enter image description here

    After a fairly thorough review of the available third party libraries it seems the Pipe https://github.com/JulienPalard/Pipe best suits the needs .

    You can create your own pipeline functions. I put it to work for wrangling some text shown below. the bolded line is where the work happens. All those @Pipe stuff I only have to code once and then can re-use.

    The task here is to associate the abbreviation in the first text:

    rawLabels="""Country: Name of country
    Agr: Percentage employed in agriculture
    Min: Percentage employed in mining
    Man: Percentage employed in manufacturing
    PS: Percentage employed in power supply industries
    Con: Percentage employed in construction
    SI: Percentage employed in service industries
    Fin: Percentage employed in finance
    SPS: Percentage employed in social and personal services
    TC: Percentage employed in transport and communications"""
    

    With an associated tag in this second text:

    mylabs = "Country Agriculture Mining Manufacturing Power Construction Service Finance Social Transport"
    

    Here's the one-time coding for the functional operations (reuse in subsequent pipelines):

    @Pipe
    def split(iterable, delim= ' '):
        for s in iterable: yield s.split(delim)
    
    @Pipe
    def trim(iterable):
        for s in iterable: yield s.strip()
    
    @Pipe
    def pzip(iterable,coll):
        for s in zip(list(iterable),coll): yield s
    
    @Pipe
    def slice(iterable, dim):
      if len(dim)==1:
        for x in iterable:
          yield x[dim[0]]
      elif len(dim)==2:
        for x in iterable:
          for y in x[dim[0]]:
            yield y[dim[1]]
        
    @Pipe
    def toMap(iterable):
      return dict(list(iterable))
    

    And here's the big finale : all in one pipeline:

    labels = (rawLabels.split('\n') 
         | trim 
         | split(':')
         | slice([0])
         | pzip(mylabs.split(' '))
         | toMap )
    

    And the result:

    print('labels=%s' % repr(labels))
    
    labels={'PS': 'Power', 'Min': 'Mining', 'Country': 'Country', 'SPS': 'Social', 'TC': 'Transport', 'SI': 'Service', 'Con': 'Construction', 'Fin': 'Finance', 'Agr': 'Agriculture', 'Man': 'Manufacturing'}