Skip to content
This repository has been archived by the owner on Jun 3, 2023. It is now read-only.

Is it possible to write a transducer to perform zip? #11

Open
RGBboy opened this issue Nov 14, 2014 · 8 comments
Open

Is it possible to write a transducer to perform zip? #11

RGBboy opened this issue Nov 14, 2014 · 8 comments

Comments

@RGBboy
Copy link

RGBboy commented Nov 14, 2014

I wasn't sure where best to ask this question. Please point me in the right direction if this is not the best place.

I was wondering if it possible to create a transducer that acted like rx's zip? For example say I had a collection of collections [[a, b, c][1, 2, 3]] I would like it to be transformed into [[a,1], [b,2], [c,3]].

Taking this a step further it would be great to be able to write it in a more reusable way, for example a combinator transducer that takes a function that will be called with the nth element of each nested collection:

var zip2 = function (x, y) {
  return [x, y];
};
var apush = function (arr, x) {
  arr.push(x);
  return arr;
};
var xf = t.combinator(zip2);
transduce(xf, apush, [], [[a, b, c][1, 2, 3]]); // [[a,1], [b,2], [c,3]]
@RGBboy RGBboy changed the title Is it possible to write a transducer to perform collection combination e.g. zip? Is it possible to write a transducer to perform zip? Nov 14, 2014
@swannodette
Copy link
Contributor

It is possible, in fact Clojure/ClojureScript don't even bother with zip as this behavior is provided by map. However in order for this to work transduce would need to be changed to support multiple collections.

@RGBboy
Copy link
Author

RGBboy commented Nov 14, 2014

I'm intrigued that Clojure/ClojureScript provide this via map. Could you elaborate how this is achieved?

@swannodette
Copy link
Contributor

In Clojure/ClojureScript map has a fourth variadic arity, this arity is leveraged by MultiStepper coupled with LazyTransformer. See the Clojure/ClojureScript source for details.

I'll ask around to see if adding zip like functionality to map to this library is desirable.

@RGBboy
Copy link
Author

RGBboy commented Nov 16, 2014

OK great, thanks for looking into this.

@hura
Copy link

hura commented Feb 3, 2015

+1

@nmn
Copy link

nmn commented Apr 16, 2015

This can be done easily with reduce:

[['a', 'b', 'c'][1, 2, 3]].reduce(function(result, item){
   return item.map(function(elem, index){
     return [].concat(result[index]).concat(elem)
   }
}, [])

I wasn't quite sure how to write that with transducers right now

@shaunc
Copy link

shaunc commented Apr 25, 2017

Can be done like this...

import tr from 'transducers-js';

function zip() {
  return xf => Zip(xf);
}
const sub = Symbol('sub');
function Zip(xf) {
  return {
    ['@@transducer/init']() {
      const result = { [sub]: [] };
      // if init is not implemented in wrapped, ignore
      try {
        result.wrapped = xf['@@transducer/init']();
      }
      catch(err) { }
      return result;
    },
    ['@@transducer/result'](result) {
      if(result[sub] == null || result[sub].length === 0) {
        return result.wrapped || result;  
      }
      const wrappedResult = result[sub][0].reduce((acc, input, i)=>
        xf['@@transducer/step'](acc, result[sub].map((a)=>a[i]))
      , result.wrapped);
      return xf['@@transducer/result'](wrappedResult);
    },
    ['@@transducer/step'](result, input) {
      if(result[sub] == null) {
        // "into" - init not called
        const acc = this['@@transducer/init']();
        // pass the evil on to the wrapped accumulator
        acc.wrapped = result;
        result = acc;
      }
      result[sub].push(input);
      return result;
    }
  };
}
console.log(tr.into([], zip(), [[1,2,3], [4,5,6]]));
// output: [ [ 1, 4 ], [ 2, 5 ], [ 3, 6 ] ]

Grumble

IMHO, the protocol should always call '@@transducer/init' -- every transformer should be able to set up its own accumulator. As the point is to abstract away from the underlying object collections, there is no reason my "step" function should be passed the "empty" object from into -- rather I should get my own accumulator on each step -- and pass the next transformer in the chain its own accumulator (which I could have wrapped in mine and stored in "init" -- but more conveniently, perhaps, would be passed in for me).

Perhaps I am missing something? Instead of putting state into the accumulator, I could store it in the transformer itself (or also in the closure that creates the transformer object). But why chain the accumulators at all if they aren't meant to store state? The question would be -- what would be expected of the code:

const zip0 = zip();
console.log(tr.into([], zip0, [[1,2,3], [4,5,6]]));
console.log(tr.into([], zip0, [[1,2,3], [4,5,6]]));

Presumably this is wrong (?) -- into() expects something stateless and then (should) set up?

@xgbuils
Copy link

xgbuils commented Jun 10, 2019

Hi @shaunc

Your zip transducer doesn't work well:

const input = [[1,2,3], [4,5,6]]
const transducer = tr.comp(
  zip(),
  tr.take(2)
)
const output = tr.into([], transducer, input)
console.log(output); /* { 
  '@@transducer/reduced': true,
  '@@transducer/value': [ [ 1, 4 ], [ 2, 5 ] ]
} */

Cheers!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants