2

I have a problem with my custom iterator, so I'm asking for your help. I have class MyIterator, which is an iterator with transformation. This class has methods:

  • next() - returns next element
  • hasNext() - check if next element exists
  • fromIterator - static method, which converts Iterator to MyIterator
  • map - method which takes functional interface and returns MyIterator with transformation rule corresponding to this interface
  • forEach - method which takes functional interface and iterates over all remaining objects according to the interface.

    My realisation is

    import java.util.Iterator;
    import java.util.function.Consumer;
    import java.util.function.Function;

    public class MyIterator<K, V> {
        private final Iterator<K> iterator;
        private final Function<K, V> function;

        @SuppressWarnings("unchecked")
        public static <K, V> MyIterator<K, V> fromIterator(Iterator<K> iterator) {
            return new MyIterator<>(iterator, k -> (V) k);
        }

        private MyIterator(Iterator<K> iterator, Function<K, V> function) {
            this.iterator = iterator;
            this.function = function;
        }

        public V next() {
            return this.function.apply(iterator.next());
        }

        public boolean hasNext() {
            return this.iterator.hasNext();
        }

        public MyIterator<K, V> map(Function<K, V> function) {
            return new MyIterator<K, V>(this.iterator, this.function);
        }

        public void forEach(Consumer<V> action) {
            while (hasNext()) {
                action.accept(this.next());
            }
        }
    }

So, I did this task, but I can't understand, how to change the method map into chaining method (pipeline). I mean the following:


    MyIterator<String, Integer> myIterator3 = MyIterator.fromIterator(stringsArray.iterator()).map(s -> s.length()).map(i -> i.toString()).map(s -> s.length());

For example, I have String "England". After first map I want to get 7 ("England" consists of 7 characters), than "7", than 1 (because String "7" consists of 1 character).
My assumption is that I should use methods andThen/compose in my method map, by I can't understand, how.

6
  • Firstly, your map method takes a function as a parameter which you completely ignore. Commented May 16, 2020 at 13:18
  • Why do you think an iterator needs two generic type parameters? K and V traditionally stand for "key" and "value". They don't make sense in this context. This k -> (V) k should act as a clue that what you're doing does not make a lot of sense Commented May 16, 2020 at 13:20
  • @Michael, thank you for answer! I use two generic type parameters, because otherwise I won't be able to have Function<K, V> as a field of my class. You also wrote about map method which ignores function, I understand it, but I can't figure out how to use this function apart from this: return new MyIterator (it is neccessary) with the function field, where the function that looks like f1.compose(f2).compose(f3). e.t.c., depends on how many times .map was called on this MyIterator object. Anyway, I really can't grip how to. So, is it rirght? Commented May 16, 2020 at 13:41
  • Why don't you try it and see? Commented May 16, 2020 at 13:42
  • @Michael, I tried it, but it didn't work, because method andThen requires Function<V, V>, so I can't just write return new MyIterator(this.iterator, this.function.andThen(function));. Therefore, this approach is incorrect or I don't understand how this function works Commented May 16, 2020 at 13:48

2 Answers 2

2

As Iterator accepts one parameter, here is how I modified your code.

public class ChainedIterator<T> {

    private Function<T, ?> action;

    private ChainedIterator<T> chain;

    private final Iterator<?> iterator;

    private <R> ChainedIterator(Iterator<?> iterator, Function<T, R> action, ChainedIterator<T> prev) {
      this.action = action;
      this.chain = prev;
      this.iterator = iterator;
    }

    public static <T> ChainedIterator<T> fromIterator(Iterator<T> iterator) {
      return new ChainedIterator<>(iterator, Function.identity(), null);
    }

    public T next() {
      return (T) this.action.apply((T) (Objects.nonNull(this.chain) ? this.chain.next() : this.iterator.next()));
    }

    public boolean hasNext() {
      return this.iterator.hasNext();
    }

    public <R> ChainedIterator<R> map(Function<T, R> action) {
      return new ChainedIterator(this.iterator, action, this);
    }

    public void forEach(Consumer<T> action) {
      while (hasNext()) {
        action.accept(this.next());
      }
    }
 }

Usage Example

Iterator<String> stringIterator = Arrays.asList("England", "India").iterator();

ChainedIterator<Integer> iterator = ChainedIterator.fromIterator(stringIterator)
  .map(s -> s.length())
  .map(i -> String.valueOf(i))
  .map(s -> s.length());

I hope this helps :)

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, lucid! Very elegant and concise solution! It helps :)
2

Update your custom Iterator and allow fromIterator() takes function rather you define it


public class MyIterator<K, V> {

    private Iterator<K> iterator;
    private List<Function<K, ?>> functions;

    public static <K, V> MyIterator<K, V> fromIterator(Iterator<K> iterator) {
        return new MyIterator<>(iterator);
    }

    private MyIterator(Iterator<K> iterator) {
        this.iterator = iterator;
        functions = new ArrayList<>();
    }

    private MyIterator(Iterator<K> iterator, Function<K, ?> function) {
        this.iterator = iterator;
        functions = new ArrayList<>();
        functions.add(function);
    }

    private MyIterator(Iterator<K> iterator, List<Function<K, ?>> functions) {
        this.iterator = iterator;
        this.functions = functions;
    }

    public Object next() {
        K key = iterator.next();
        Object val = null;
        for (int i = 0; i < functions.size(); i++) {
            val = functions.get(i).apply(key);
            key = (K) val;
        }
        return val;
    }

    public boolean hasNext() {
        return iterator.hasNext();
    }

    public <R, RR> MyIterator<R, RR> map(Function<K, R> function) {
        List<Function<K, ?>> functions2 = this.functions;
        functions2.add(function);
        return new MyIterator(iterator, functions2);
    }

    public void forEach(Consumer<Object> action) {
        while (hasNext()) {
            action.accept(next());
        }
    }
}

, main


    public static void main(String[] args) throws Exception {
        Iterator<String> sIterator = Arrays.asList("aaa", "bbbb", "cccc", "ddddd").iterator();
        MyIterator.<String, Object>fromIterator(sIterator).map(s -> s.length()).map(i -> i + "")
                .map(str -> str.length()).forEach(System.out::println);
    }

, output

1
1
1
1

5 Comments

thank you for your answer, but it doesn't work as I asked for. I wanted to get chaining logic, .map(s -> s.length()).map(i -> Integer.toString(i)).map(s -> s.length()). so, for "aaa" the result should be 1 ("aaa"->3->"3"->1)
understand your idea,it works! thank you a lot :D, Also, may I ask a question: is it possible to do it like I mentioned above (using andThen or compose, without List<> field or I'm wrong?
I have tested the above code and it compiles. it's building the map from the previous result R to be K in next map
above code, it's a simple one, and I didn't try with compose/then you can give a try
sure, sorry, a missed it. It is a good decision, thank you for help and explanations, sc0der! Can you answer a last question in my last comment, please? You obviously have more experience and I would be grateful to you! :)

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.