views:

1467

answers:

16

I am trying to name what I think is a new idea for a higher-order function. To the important part, here is the code in Python and Haskell to demonstrate the concept, which will be explained afterward.

Python:

>>> def pleat(f, l):
       return map(lambda t: f(*t), zip(l, l[1:]))
>>> pleat(operator.add, [0, 1, 2, 3])
[1, 3, 5]

Haskell:

Prelude> let pleatWith f xs = zipWith f xs (drop 1 xs)
Prelude> pleatWith (+) [0,1,2,3]
[1,3,5]

As you may be able to infer, the sequence is being iterated through, utilizing adjacent elements as the parameters for the function you pass it, projecting the results into a new sequence. So, has anyone seen the functionality we've created? Is this familiar at all to those in the functional community? If not, what do we name it?

---- Update ----

Pleat wins!

Prelude> let pleat xs = zip xs (drop 1 xs)
Prelude> pleat [1..4]
[(1,2),(2,3),(3,4)]

Prelude> let pleatWith f xs = zipWith f xs (drop 1 xs)
Prelude> pleatWith (+) [1..4]
[3,5,7]
+4  A: 

Here's another implementation for Python which works if l is a generator too

import itertools as it

def apply_pairwise(f, l):
    left, right = it.tee(l)
    next(right)
    return it.starmap(f, it.izip(left, right))

I think apply_pairwise is a better name

gnibbler
+.5 for name, +.5 for implementation.
aaronasterling
+6  A: 

Since it's similar to "fold" but doesn't collapse the list into a single value, how about "crease"? If you keep "creasing", you end up "folding" (sort of).

We could go with a cooking metaphor and call it "pinch", like pinching the crust of a pie, though this might suggest a circular zipping, where the last element of the list is paired with the first.

def pinch(f, l):
    return map(lambda t: f(*t), zip(l, l[1:]+l[:1]))

(If you only like one of "crease" or "pinch", please note so as a comment. Should these be separate suggestions?)

outis
You can do an "infiniplow" where you can reduce the sequence to a single value by continually "plowing" it. I've called this PlowReduce hitherto. For example, a PlowReduce would plow a sequence [0..9] to the value 2304.
Legatou
"Plow" doesn't seem like a good description, since plows turn over, break things up and aerate, none of which seem to apply to what the function does.
outis
I think pinch is a great candidate!
Legatou
No, no, "crease" clearly means a fold followed by an unfold.
camccann
I love the "crease" one.
JB
+1  A: 

So because there seems to be no name for this I suggest 'merger' or simple 'merge' because you are merging adjacent values together.

So merge is already taken so I now suggest 'meld' (or 'merger' still but that may be too close to 'merge')

For example:

meld :: (a -> a -> b) -> [a] -> [b]
meld _ [] = []
meld f xs = zipWith f (init xs) (tail xs)

Which can be used as:

> meld (+) [1..10]
[3,5,7,9,11,13,15,17,19]
> meld compare "hello world"
[GT,LT,EQ,LT,GT,LT,GT,LT,GT,GT]

Where the second example makes no real sense but makes a cool example.

Robert Massaioli
I am currently favoring this name. I think it's sufficiently descriptive.
Legatou
It's ok but conflicts with `merge :: Ord a => [a] -> [a] -> [a]` which merges two sorted lists together into one sorted list.
TomMD
@TomMD: good point, changing to 'meld' instead. It is not taken, I checked and it is short and descriptive. "Just, meld this list with the (+) function."
Robert Massaioli
+3  A: 

I really can't see any codified names for this anywhere in Python, that's for sure. "Merge" is good but spoken for in a variety of other contexts. "Plow" tends to be unused and supplies a great visual of pushing steadily through a line of soil. Maybe I've just spent too much time gardening.

I also expanded the principle to allow functions that receive any number of parameters.

You might also consider: Pleat. It describes well the way you're taking a list (like a long strand of fabric) and bunching segments of it together.

import operator

def stagger(l, w):
    if len(l)>=w:
        return [tuple(l[0:w])]+stagger(l[1:], w)
    return []

def pleat(f, l, w=2):
    return map(lambda p: f(*p), stagger(l, w))

print pleat(operator.add, range(10))
print pleat(lambda x, y, z: x*y/z, range(3, 13), 3)
print pleat(lambda x: "~%s~"%(x), range(10), 1)
print pleat(lambda a, b, x, y: a+b==x+y, [3, 2, 4, 1, 5, 0, 9, 9, 0], 4)
Ishpeck
better to use itertools.starmap instead of `map(lambda p: f(*p)...`
gnibbler
+2  A: 

zipWithTail or adjacentPairs.

wnoise
+2  A: 

I vote for smearWith or smudgeWith because it's like you are smearing/smudging the operation across the list.

Jake McArthur
A: 

BinaryOperate or BinaryMerge

pirhac
If you're going to confine this to only allowing functions that receive two parameters, "dyadic" would be a better prefix. But the principle here, using elements in a sequence as parameters, should, in theory, expand to more than just dyadic functions.
Ishpeck
+4  A: 

In Python the meld equivalent is in the itertools receipes and called pairwise.

from itertools import starmap, izp, tee

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

So I would call it:

def pairwith(func, seq):
    return starmap(func, pairwise(seq))

I think this makes sense because when you call it with the identity function, it simply returns pairs.

THC4k
+13  A: 

Hmm... a counterpoint.

(`ap` tail) . zipWith

doesn't deserve a name.

BTW, quicksilver says:

 zip`ap`tail

The Aztec god of consecutive numbers

Don Stewart
Why wouldn't it? As counter-counterpoints, `flip mapM`, `foldr (>>) (return ())` and `liftM2 id` *have* been attributed some. (NB I'm not arguing it should, I'm pointing out the argument is weak)
JB
map and fold are the two fundamental higher order functions, as a result, uses like this are very common. tail is moderately rare, zip `ap` tail is exceedingly rare. rare + trivial == not a good reason to name.
Don Stewart
I did not know about this though and I think that other people may be in the same boat, irrespective of how long they have used Haskell for. Is there anywhere that goes through a bunch of these useful code snippets, stuff that is not as common as prelude code but common enough to make these posts? Or do most people stumble upon this themselves?
Robert Massaioli
I have seen this higher-order function used quit often by science graduate students. Mostly in MatLab and Mathematica and once or twice in python. Knowledge of it was either passed down from whom ever taught them or rediscovered.
Davorak
+1  A: 

Using Mathematica

Plus @@@ Partition[{0, 1, 2, 3}, 2, 1] or either of these more verbose alternatives

Apply[Plus, Partition[{0, 1, 2, 3}, 2, 1], {1}]
Map[Apply[Plus, #] &, Partition[{0, 1, 2, 3}, 2, 1]]

I have used and enjoyed this higher order function in many languages but I have enjoyed it the most in Mathematica; it seems succinct and flexible broken down into Partition and Apply with levelspec option.

Davorak
+2  A: 

this seems like ruby's each_cons

ruby-1.9.2-p0 > (1..10).each_cons(2).to_a

=> [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10]] 
Martin DeMello
+2  A: 

This reminds me of convolution from image processing. But not sure if this is mathematically correct.

sfty
In image processing images are functions represented as arrays. The example 'binaryProjection (+) [0,1,2,3]' is like convolving a '[1,1]' kernel over a 1D-image (first multiply overlapping coefficients than sum). Though being able to use any other function than '+' makes the higher-order function in question more general.
sfty
This is convolution of a sort. The two functions convolved are `f 1 = 1, f 2 = 1` and the function from natural numbers to the input list. The binary operation is specified by the function argument to `plow` or `meld` or whatever it's called. Very similar to the DSP sense of convolution of signals where the binop is (+).
John
@John, good eye. I withdraw my statement.
aaronasterling
@AaronMcSmooth, no problem. As @sfty notes, this is an interesting hybrid which is mostly like signal convolution but more general because of the varying binop. In the more general sense of convolution (rather than the restricted DSP version), this extra generality isn't a difficulty. Anyway, @sfty noticed it; I didn't on my own.
John
+2  A: 

The generalized variant of the plain zip version of this is what I would think of as window. Not at a ghci terminal right now, but I think window n = take n . tails. Then your function is zipWith (\[x,yj -> f x y) . window 2. This sort of style naturally works better when f is of type [a] -> b.

sclv
+1  A: 

I'd be tempted to call it contour as I've used it for "contour" processing in music software - at the time I called it twomap or something silly like that.

There are also two specific named 'contours' in music processing one is gross contour - does the pitch go up or down. The other is refined contour where the the contour is either up, down, leap up or leap down, though I can't seem to find a reference for how large the semitone difference has to be to make a leap.

Stephen Tetley
+2  A: 

in C++ Standard Template Library, it is called adjacent_difference (though the operator can be any operation, not just subtraction)

newacct
+1  A: 

Nice idiom! I just needed to use this in Perl to determine the time between sequential events. Here's what I ended up with.

sub pinch(&@) {
  my ( $f, @list ) = @_;
  no strict "refs";

  use vars qw( $a $b );

  my $caller = caller;
  local( *{$caller . "::a"} ) = \my $a;
  local( *{$caller . "::b"} ) = \my $b;

  my @res;
  for ( my $i = 0; $i < @list - 1; ++$i ) {
    $a = $list[$i];
    $b = $list[$i + 1];
    push( @res, $f->() );
  }
  wantarray ? @res : \@res;
}

print join( ",", pinch { $b - $a } qw( 1 2 3 4 5 6 7 ) ), $/;
# ==> 1,1,1,1,1,1

The implementation could probably be prettier if I'd made it dependent on List::Util, but... meh!

Chris Waterson
Congrats, necromancer ;) Oh wait... for the necromancer badge, you need +5 for this answer...
delnan
looks like 1337 speak to me.
aaronasterling
Yeah, Perl gets ugly fast, sigh!
Chris Waterson