2

I need to create Observable, that will collect information from different sources(other Observables), and each source has impact on the event value, but still, the value is built based on previous value(kind of state machine).

We have message with int value and operation code:

class Message{
    Integer value;
    String operation;

    public Message(Integer value, String operation) {
        this.value = value;
        this.operation = operation;
    }
}

And some source of such values, with init value:

Observable<Message> dynamicMessage = Observable.just(new Message(1, "+"));

Now there are sources of event. These sources will emit values based on which and based on previos value of dynamicMessage the new dynamicMessage value will appear. Actually its events types are:

class Changer1 {
    String operation;

    public Changer1(String operation) {
        this.operation = operation;
    }
}


class Changer2 {
    Integer value;

    public Changer2(Integer value) {
        this.value = value;
    }
}

Changer1 responcible for changing of operation. Changer2 responcible for value of change.

and sources of these values:

static Observable<Changer1> changers1 = Observable.just(new Changer1("-"))
        .delay(1, TimeUnit.SECONDS).concatWith(Observable.just(new Changer1("+"))
                .delay(2, TimeUnit.SECONDS));


static Observable<Changer2> changers2 = Observable.just(new Changer2(2))
        .delay(2, TimeUnit.SECONDS).concatWith(Observable.just(new Changer2(2))
                .delay(2, TimeUnit.SECONDS));

Now I need change dynamicMessage Observable and take in respect messages from changers. here is a diagram: state machine

Also I try to wrote a program, how I see the solution, but it hangs with OutOfMemoryException, rigth after first value:
Message{value=1, operation='+'}
Listing:

import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func1;

public class RxDilemma {


static class Message{
    Integer value;
    String operation;

    public Message(Integer value, String operation) {
        this.value = value;
        this.operation = operation;
    }

    @Override
    public String toString() {
        return "Message{" +
                "value=" + value +
                ", operation='" + operation + '\'' +
                '}';
    }
}

static class Changer1 {
    String operation;

    public Changer1(String operation) {
        this.operation = operation;
    }

    @Override
    public String toString() {
        return "Changer1{" +
                "operation='" + operation + '\'' +
                '}';
    }
}


static class Changer2 {
    Integer value;

    public Changer2(Integer value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Changer2{" +
                "value=" + value +
                '}';
    }
}





static Observable<Changer1> changers1 = Observable.just(new Changer1("-"))
        .delay(1, TimeUnit.SECONDS).concatWith(Observable.just(new Changer1("+"))
                .delay(2, TimeUnit.SECONDS));


static Observable<Changer2> changers2 = Observable.just(new Changer2(2))
        .delay(2, TimeUnit.SECONDS).concatWith(Observable.just(new Changer2(2))
                .delay(2, TimeUnit.SECONDS));


static Observable<Message> dynamicMessage = Observable.just(new Message(1, "+")).mergeWith(changers1.flatMap(new Func1<Changer1, Observable<Message>>() {
    @Override
    public Observable<Message> call(final Changer1 changer) {
        return dynamicMessage.last().map(new Func1<Message, Message>() {
            @Override
            public Message call(Message message) {
                message.operation = changer.operation;
                return message;
            }
        });
    }
})).mergeWith(changers2.flatMap(new Func1<Changer2, Observable<Message>>() {
    @Override
    public Observable<Message> call(final Changer2 changer2) {
        return dynamicMessage.last().map(new Func1<Message, Message>() {
            @Override
            public Message call(Message message) {
                if("+".equalsIgnoreCase(message.operation)){
                    message.value = message.value+changer2.value;
                } else if("-".equalsIgnoreCase(message.operation)){
                    message.value = message.value-changer2.value;
                }
                return message;
            }
        });
    }
}));

public static void main(String[] args) throws InterruptedException {

    dynamicMessage.subscribe(new Action1<Message>() {
        @Override
        public void call(Message message) {
            System.out.println(message);
        }
    });


    Thread.sleep(5000000);
}
}

So what my expected outpur is:

Message{value=1, operation='+'}
Message{value=1, operation='-'}
Message{value=-1, operation='-'}
Message{value=1, operation='+'}
Message{value=2, operation='+'}

Also I thougth about merging all with CombineLatest, but then I found that CombineLatest does not provide which one element was changed, and without this information I will not know which changes in message needs to be done. Any help?

msangel
  • 9,895
  • 3
  • 50
  • 69

3 Answers3

1

In other words, you want to reduce scan your actions to reflect changes in a state. There is an operator, rightly named reduce scan, that does exactly what you are asking for. It takes every event that comes and lets you transform it with the addition of previous result of that transformation, which will be your state.

interface Changer {
    State apply(State state);
}

class ValueChanger implements Changer {
    ...
    State apply(State state){
        // implement your logic of changing state by this action and return a new one
    }
    ...
}


class OperationChanger implements Changer {
    ...
    State apply(State state){
        // implement your logic of changing state by this action and return a new one
    }
    ...
}

Observable<ValueChanger> valueChangers
Observable<OperationChanger> operationChangers

Observable.merge(valueChangers, operationChangers)
    .scan(initialState, (state, changer) -> changer.apply(state))
    .subscribe(state -> {
        // called every time a change happens
    });

Note that I changed your naming a little bit to make more sense. Also, I use Java8 lambda expressions here. Just swap them out for anonymous classes if you can't use them in your project.

EDIT: use scan operator instead of reduce as it delivers every result along the way, rather than emitting only the final result

msangel
  • 9,895
  • 3
  • 50
  • 69
koperko
  • 2,447
  • 13
  • 19
  • This function gives correct final value, but intermediate values are missing. `subscribe` fire only when all changers finish work. – msangel Feb 06 '17 at 14:12
  • `valueChangers = Observable.interval(1, TimeUnit.SECONDS).map( aLong -> new ValueChanger(1) )` cause this hang forever. – msangel Feb 06 '17 at 14:18
  • I am sorry, I haven't used reduce in a while. You can however just swap `reduce` for `scan` with the same parameters and it will work as expected. I'll edit my answer right away – koperko Feb 06 '17 at 15:06
  • I already posted my solution, but your seems to be better. Thanks. – msangel Feb 06 '17 at 15:12
1

Have made some changes to hopefully suffice your needs. Please take a look and get back on results.

static class Message {
    Integer value;
    String operation;

    public Message(Integer value, String operation) {
        this.value = value;
        this.operation = operation;
    }

    @Override
    public String toString() {
        return "Message{" + "value=" + value + ", operation='" + operation
                + '\'' + '}';
    }

    public Message applyChange(Object changer){
        if(changer instanceof Changer1){
            this.operation = ((Changer1)changer).operation;
        }else if(changer instanceof Changer2){
            int v2 = ((Changer2)changer).value;
            if("+".equals(operation)){
                value += v2;
            }else if("-".equals(operation)){
                value -= v2;
            }
        }
        return this;
    }
}

static class Changer1{
    String operation;

    public Changer1(String operation) {
        this.operation = operation;
    }

    @Override
    public String toString() {
        return "Changer1{" + "operation='" + operation + '\'' + '}';
    }
}

static class Changer2{
    Integer value;

    public Changer2(Integer value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Changer2{" + "value=" + value + '}';
    }
}

static Observable<? extends Object> changers1 = Observable
        .just(new Changer1("-"))
        .delay(1, TimeUnit.SECONDS)
        .concatWith(
                Observable.just(new Changer1("+")).delay(2,
                        TimeUnit.SECONDS));

static Observable<? extends Object> changers2 = Observable
        .just(new Changer2(2))
        .delay(2, TimeUnit.SECONDS)
        .concatWith(
                Observable.just(new Changer2(2)).delay(2, TimeUnit.SECONDS));


static Observable<Message> dynamicMessage = Observable
        .just(new Message(1, "+"))
        .flatMap(m -> ((Observable<Object>)changers1).mergeWith((Observable<Object>)changers2).map(c -> m.applyChange(c)));

public static void main(String[] args) throws InterruptedException {
    dynamicMessage.toBlocking().subscribe(v -> System.out.println(v));
}

Changes:

Message has a new method which is called every time a Changer is emitted, it changes the value of the Message object. This is used because you just have one Message object which is actually the init value.

public Message applyChange(Object changer){
    if(changer instanceof Changer1){
        this.operation = ((Changer1)changer).operation;
    }else if(changer instanceof Changer2){
        int v2 = ((Changer2)changer).value;
        if("+".equals(operation)){
            value += v2;
        }else if("-".equals(operation)){
            value -= v2;
        }
    }
    return this;
}

dynamicMessage is modified as needed -

static Observable<Message> dynamicMessage = Observable
    .just(new Message(1, "+"))
    .flatMap(m -> ((Observable<Object>)changers1)
        .mergeWith((Observable<Object>)changers2)
        .map(c -> m.applyChange(c)));
Pavan Kumar
  • 4,182
  • 1
  • 30
  • 45
  • Ok, I have tried this. It works. I had to spend another 5 minutes in thinking ***how and why?***. Short conclusion: it works, but the first flatMap function receive only the one value, so all operations in nested observables are performed physically on the same object. Making `applyChange` function synchronized will prevent me from broken state. The second answer seems to be working to, so I will mark as connect none. (But the readers of this will put another vote for accepted for them answer). Thank you. – msangel Feb 06 '17 at 11:25
0

Ok, I have two answers here. One of them is working, but it has the design issue, that force to control concurrent execution by hands. Another one not working by design (maybe after some manipulations with making reduce operator work not for all but for each pair of data it will work). But I am posting own answer here because I found more elegant, and more correct solution.

But before answer, take a look at the question. Why does it hang? Answer is: because operator last() is blocking. So I need to force it to be non-blocking. Based on another answer here: https://stackoverflow.com/a/37015509/449553, I can use BehaviorSubject class for that.

Here's my working code:

    final BehaviorSubject<Message> subject = BehaviorSubject.create(new Message(1, "+"));
    Observable<Message> observable = Observable.<Changer>merge(changers1, changers2).map(new Func1<Changer, Message>() {
        @Override
        public Message call(Changer changer) {
            Message value = subject.getValue();
            Message v = value.applyChange(changer);
            subject.onNext(v);
            return v;
        }
    });

(missing parts of code you will find in other answers here and here) Still, I will not mark the answer as correct, I believe, someone might want to solve this puzzle himself in more proper way.

EDIT: Proper way was found, see marked answer.

Community
  • 1
  • 1
msangel
  • 9,895
  • 3
  • 50
  • 69