7

I have created some tuple classes, and put them into Java collection. But I don't want to use tuple as function parameters directly when iterating collection.So I implemented tuple unpacking as the following code.

Basically it works, but the problem is that a type casting is needed:

map((Func2<Long, Long, Long>) (a, b) -> a + b)

Is there any way to remove the type casting here?


edit:

Maybe I didn't make it clear, not only tuple2, but also tuple3, tuple4... should be supported. @Flown's answer works greatly for Tuple2, but doesn't work for tuple2, tuple3, tuple4 in the meantime

package test;


import com.google.common.collect.Iterables;

import java.util.Arrays;
import java.util.function.Function;

import static test.TupleIterable.Tuple.tuple;

public interface TupleIterable<T> {

    Iterable<T> apply();

    static <E> TupleIterable<E> from(Iterable<E> iterable) {
        return () -> iterable;
    }

    default <E> TupleIterable<E> map(Function<? super T, ? extends E> op) {
        return () -> Iterables.transform(TupleIterable.this.apply(), op::apply);
    }

    interface Func2<T1, T2, R> extends Function<Tuple.Tuple2<T1, T2>, R> {

        R apply(T1 t1, T2 t2);

        @Override
        default R apply(Tuple.Tuple2<T1, T2> t) {
            return apply(t.t1, t.t2);
        }
    }

    interface Func3<T1, T2, T3, R> extends Function<Tuple.Tuple3<T1, T2, T3>, R> {

        R apply(T1 t1, T2 t2, T3 t3);

        @Override
        default R apply(Tuple.Tuple3<T1, T2, T3> t) {
            return apply(t.t1, t.t2, t.t3);
        }
    }

    interface Tuple {

        static <T1, T2> Tuple2<T1, T2> tuple(T1 t1, T2 t2) {
            return new Tuple2<>(t1, t2);
        }

        static <T1, T2, T3> Tuple3<T1, T2, T3> tuple(T1 t1, T2 t2, T3 t3) {
            return new Tuple3<>(t1, t2, t3);
        }

        class Tuple2<T1, T2> implements Tuple {

            public T1 t1;

            public T2 t2;

            public Tuple2(T1 t1, T2 t2) {
                this.t1 = t1;
                this.t2 = t2;
            }
        }

        class Tuple3<T1, T2, T3> implements Tuple {

            public T1 t1;

            public T2 t2;

            public T3 t3;

            public Tuple3(T1 t1, T2 t2, T3 t3) {
                this.t1 = t1;
                this.t2 = t2;
                this.t3 = t3;
            }
        }
    }

    public static void main(String[] args) {
        TupleIterable.from(Arrays.asList(1L, 2L))
                .map(x -> tuple(x, x)) // map long to tuple2
                .map((Func2<Long, Long, Tuple.Tuple3<Long, Long, Long>>) (a, b) -> tuple(a, b, a + b)) // map tuple2 to tuple3
                .map((Func3<Long, Long, Long, Long>) (a, b, c) -> a + b + c) // map tuple3 to Long
                .apply()
                .forEach(System.out::println);
    }
}

3 Answers 3

3

You can do the same trick as the JDK developer did for the Stream specializations of primitive types. Introduce mapToTuple and unwrap in different interfaces.

I rewrote some of your code and used some existing FunctionalInterfaces.

import java.util.Arrays;
import java.util.Iterator;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class Test {

  public static void main(String... args) {
    MyIterable.from(Arrays.asList(1L, 2L)).mapToTuple(l -> Tuple2.tuple(l, l + 1L)).unwrap((a, b) -> a + b).get()
        .forEach(System.out::println);
  }
}

final class Tuple2<T1, T2> {

  public static <T1, T2> Tuple2<T1, T2> tuple(T1 t1, T2 t2) {
    return new Tuple2<>(t1, t2);
  }

  public final T1 t1;
  public final T2 t2;

  private Tuple2(T1 t1, T2 t2) {
    this.t1 = t1;
    this.t2 = t2;
  }
}

@FunctionalInterface
interface TupleIterable<T1, T2> extends Supplier<Iterable<Tuple2<T1, T2>>> {

  default <E> MyIterable<E> unwrap(BiFunction<T1, T2, E> func) {
    return () -> Iterables.transform(get(), t -> func.apply(t.t1, t.t2));
  }
}

@FunctionalInterface
interface MyIterable<T> extends Supplier<Iterable<T>> {

  static <E> MyIterable<E> from(Iterable<E> iterable) {
    return () -> iterable;
  }

  default <E> MyIterable<E> map(Function<? super T, ? extends E> mapper) {
    return () -> Iterables.transform(get(), mapper::apply);
  }

  default <T1, T2> TupleIterable<T1, T2> mapToTuple(Function<? super T, ? extends Tuple2<T1, T2>> tupleMapper) {
    return () -> Iterables.transform(get(), tupleMapper::apply);
  }
}

final class Iterables {
  public static <T, E> Iterable<E> transform(Iterable<T> iterable, Function<? super T, ? extends E> mapper) {
    return () -> new Iterator<E>() {

      private final Iterator<T> iter = iterable.iterator();

      @Override
      public boolean hasNext() {
        return iter.hasNext();
      }

      @Override
      public E next() {
        return mapper.apply(iter.next());
      }
    };
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks to you for great answer! I have another question, does this support other tuples, such as Tuple3, Tuple4, etc... It seems that I need to create many methods, such as mapToTuple3, mapToTuple4 ...
Tuple3Iterable, Tuple4Iterable are not compatible to each other. That's to say, arguments cannot be unpacked in Tuple3Iterable::mapToTuple4
@moshangcheng If you want to be that generic, then you can't avoid the casting.
1

Instead of doing type casting, I created a unpack function to unpack tuples. It works like map(unpack((a, b) -> tuple(a, b, a + b))), though still not intuitive enough.

package test;


import com.google.common.collect.Iterables;

import java.util.Arrays;
import java.util.function.Function;

import static test.TupleIterable.Func.unpack;
import static test.TupleIterable.Tuple.tuple;

public interface TupleIterable<T> {

    Iterable<T> apply();

    static <E> TupleIterable<E> from(Iterable<E> iterable) {
        return () -> iterable;
    }

    default <E> TupleIterable<E> map(Function<? super T, E> op) {
        return () -> Iterables.transform(TupleIterable.this.apply(), op::apply);
    }

    interface Func {

        static <T1, T2, R> Function<Tuple.Tuple2<T1, T2>, R> unpack(Func2<T1, T2, R> f) {
            return t -> f.apply(t.t1, t.t2);
        }

        static <T1, T2, T3, R> Function<Tuple.Tuple3<T1, T2, T3>, R> unpack(Func3<T1, T2, T3, R> f) {
            return t -> f.apply(t.t1, t.t2, t.t3);
        }

        @FunctionalInterface
        interface Func2<T1, T2, R> {

            R apply(T1 t1, T2 t2);
        }

        @FunctionalInterface
        interface Func3<T1, T2, T3, R> {

            R apply(T1 t1, T2 t2, T3 t3);
        }
    }

    interface Tuple {

        static <T1, T2> Tuple2<T1, T2> tuple(T1 t1, T2 t2) {
            return new Tuple2<>(t1, t2);
        }

        static <T1, T2, T3> Tuple3<T1, T2, T3> tuple(T1 t1, T2 t2, T3 t3) {
            return new Tuple3<>(t1, t2, t3);
        }

        class Tuple2<T1, T2> implements Tuple {

            public T1 t1;

            public T2 t2;

            public Tuple2(T1 t1, T2 t2) {
                this.t1 = t1;
                this.t2 = t2;
            }
        }

        class Tuple3<T1, T2, T3> implements Tuple {

            public T1 t1;

            public T2 t2;

            public T3 t3;

            public Tuple3(T1 t1, T2 t2, T3 t3) {
                this.t1 = t1;
                this.t2 = t2;
                this.t3 = t3;
            }
        }
    }

    public static void main(String[] args) {
        TupleIterable.from(Arrays.asList(1L, 2L))
                .map(x -> tuple(x, x)) // map long to tuple2
                .map(unpack((a, b) -> tuple(a, b, a + b))) // map tuple2 to tuple3
                .map(unpack((a, b, c) -> a + b + c)) // map tuple3 to Long
                .apply()
                .forEach(System.out::println);
    }
}

Comments

0

I can't really tell if this is too "dirty" or not, but why not provide getters for T1 and T2 inside Tuple2 and do:

.map(x -> tuple(x, x + 1)) // map long to tuple2
.map(a -> a.getT1() + a.getT2()) // map tuple2 to long

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.