Home > Software engineering >  How to recursively call a function until the output is met?
How to recursively call a function until the output is met?

Time:12-03

I have a piece of code that contains two functions reverse() that reverses an input list and rotate() that puts the last element to the start of the first.

Now I am given another list, in the function public int minimumOps(List<Integer> a, List<Integer> b) which contains the same elements as the original list but in different order. I am trying to find how many times reverse() and/or rotate() must be called for the new list to be converted back into the original list. For instance, consider S = [1, 2, 3, 4] and T = [2, 1, 4, 3], we can see that

T = rotate(rotate(reverse(S))) gives us the output

But this is not the only way to transform S to T. To illustrate, here are some other sequence of operations:

T = reverse(rotate(rotate(S)))
T = rotate(rotate(rotate(reverse(rotate(S)))))
T = reverse(rotate(rotate(reverse(reverse(S))))))

Our goal in this problem is to find the smallest number of operations to achieve this transformation.

I cannot figure out how to solve this problem. Here is what I have so far:

public int minimumOps(List<Integer> a, List<Integer> b) {
            int count = 0;
            for (int x = 0; x < a.size(); x  ){
                if (Objects.equals(a.get(x), b.get(x))){
                    count  = 0;
                }
                else{
                    while(!Objects.equals(a.get(x),b.get(x))){
                        reverse(b);
                        rotate(b);
                        count  ;
                    }
                }
            }
            return count;
        }
        public static List<Integer> rotate(List<Integer> l) {
            List<Integer> li = new ArrayList<Integer>();
            li.add(l.get(l.size() - 1));
            for (int i = 0; i < l.size() - 1; i  ) {
                li.add(l.get(i));
            }
            return li;
        }

        public static List<Integer> reverse(List<Integer> l) {
            List<Integer> li = new ArrayList<Integer>();
            for (int i = l.size() - 1; i >= 0; i--) {
                li.add(l.get(i));
            }
            return li;
        }

Any ideas on how to approach or solve the minimumOps(a,b) and find the number of rotate() and/or reverse() needed to turn list b into list a would be greatly appreciated

CodePudding user response:

Here's your answer, this will only find the minimum number of transforms:

import java.util.*;

class Main {
    public static void main(String[] args) {
        var a = new ArrayList<>(List.of(1, 2, 3, 4));
        var b = new ArrayList<>(List.of(2, 1, 4, 3));
        var output = minimumOps(a, b);
        if (output == Integer.MAX_VALUE) System.out.println("Can't be solved");
        else System.out.println("Min transforms: " output); 
    }

    /*
        let's draw operations tree, that is, for each list we can either reverse (rev) or rotate (rot)
                              a
                              |
                           /     \
                         rev     rot
                        /   \    /   \
                      rev  rot  rev  rot
                      / \  / \  / \  / \
                       ⋮     ⋮    ⋮     ⋮

        we know that reverse(reverse(list)) == list, so we can prune rev if its parent is rev.

                              a
                              |
                           /     \
                         rev     rot
                          |     /   \
                         rot   rev  rot
                         / \    |   / \
                          ⋮      ⋮    ⋮

       now let's apply this 'blindly'.
    */
    public static int minimumOps(List<Integer> a, List<Integer> b) {
        if (Objects.equals(a, b)) return 0;

        // minimumOpsRec is a helper method that will be called recursively
        int revCount = minimumOpsRec(reverse(a), b, 1, OP.REV);
        int rotCount = minimumOpsRec(rotate(a), b, 1, OP.ROT);

        return Math.min(revCount, rotCount);
    }

    // a and b are lists that we are transforming,
    // count is our counter that will be incremented by each transform
    // parentOP is the previous operation from parent, i.e., rev or rot, see enum
    public static int minimumOpsRec(List<Integer> a, List<Integer> b, int count, OP parentOP) {
        if (Objects.equals(a, b)) return count; // base condition, return if a == b

        // however not all lists can be sorted using this algorithm, generally speaking,
        // if the output of this method greater than the list size then it's not sortable.
        // for example try to solve this using this algorithm by yourself (hint: you cannot): a = [1, 2, 3, 4], b = [4, 2, 1, 3]
        if (count > a.size()) return Integer.MAX_VALUE;

        count  ;

        int rev = Integer.MAX_VALUE, rot;

        if (parentOP == OP.ROT) rev = minimumOpsRec(reverse(a), b, count, OP.REV);

        rot = minimumOpsRec(rotate(a), b, count, OP.ROT);

        return Math.min(rev, rot);
    }

    // don't mutate input
    private static List<Integer> rotate(List<Integer> list) {
        var newList = new ArrayList<>(list);
        Collections.rotate(newList, 1); // try using util methods as much as possible
        return newList;
    }

    // don't mutate input
    private static List<Integer> reverse(List<Integer> list) {
        var newList = new ArrayList<>(list);
        Collections.reverse(newList); // try using util methods as much as possible
        return newList;
    }

    enum OP {
        REV,
        ROT
    }
}

And here if you want to find which transforms:

import java.util.*;

class Test2 {

    public static void main(String[] args) {
        var a = new ArrayList<>(List.of(1, 2, 3, 4));
        var b = new ArrayList<>(List.of(2, 1, 4, 3));
        System.out.println(minimumOps(a, b));
    }

    public static Map.Entry<Integer, List<OP>> minimumOps(List<Integer> a, List<Integer> b) {
        if (Objects.equals(a, b)) return new AbstractMap.SimpleEntry<>(0, new ArrayList<>());

        var rev = minimumOpsRec(reverse(a), b, 1, OP.REV);
        var rot = minimumOpsRec(rotate(a), b, 1, OP.ROT);

        return rot.getKey() >= rev.getKey() ? rev : rot;
    }

    public static Map.Entry<Integer, List<OP>> minimumOpsRec(List<Integer> a, List<Integer> b, int count, OP parent) {
        if (Objects.equals(a, b) || count > a.size())
            return new AbstractMap.SimpleEntry<>(count, new ArrayList<>(List.of(parent)));

        count  ;
        
        Map.Entry<Integer, List<OP>> rev = null;
        Map.Entry<Integer, List<OP>> rot;
        
        if (parent == OP.ROT) rev = minimumOpsRec(reverse(a), b, count, OP.REV);
        
        rot = minimumOpsRec(rotate(a), b, count, OP.ROT);

        if (rev != null && rot.getKey() >= rev.getKey()) {
            rev.getValue().add(parent);
            return rev;
        }
        rot.getValue().add(parent);
        return rot;
    }

    // don't mutate input
    private static List<Integer> rotate(List<Integer> list) {
        var newList = new ArrayList<>(list);
        Collections.rotate(newList, 1); // try using util methods as much as possible
        return newList;
    }

    // don't mutate input
    private static List<Integer> reverse(List<Integer> list) {
        var newList = new ArrayList<>(list);
        Collections.reverse(newList); // try using util methods as much as possible
        return newList;
    }

    enum OP {
        REV,
        ROT
    }

}

Hope it helps, good luck

CodePudding user response:

It looks like your current approach is to iterate through each element in the list a, compare it to the corresponding element in the list b, and then perform the reverse() and rotate() operations until the two elements match.

However, this approach will not work for several reasons. First, the reverse() and rotate() operations do not necessarily have to be performed on a single element in order to transform a into b. Instead, the operations can be performed on the entire list. Second, it is not guaranteed that the reverse() and rotate() operations will eventually produce a matching element, even if the two lists contain the same elements.

Here is one possible approach to solve the problem:

First, check if the two lists are already equal. If they are, then no operations are needed and the function can return 0. If the two lists are not equal, then perform the reverse() operation on list b. If the resulting list is equal to a, then the function can return 1, since only one reverse() operation was needed. If step 2 did not produce a matching list, then perform the rotate() operation on list b repeatedly until it matches a. The number of rotate() operations needed is the minimum number of operations needed to transform a into b. Here is what the updated minimumOps() function might look like:

public int minimumOps(List<Integer> a, List<Integer> b) {
// Check if the lists are already equal
if (a.equals(b)) {
    return 0;
}

// Perform the reverse operation on b
List<Integer> reversedB = reverse(b);
if (a.equals(reversedB)) {
    // If the reverse operation produces a matching list, return 1
    return 1;
}

// If the reverse operation did not produce a matching list, perform the rotate operation repeatedly
int count = 0;
while (!a.equals(b)) {
    b = rotate(b);
    count  ;
}

// Return the number of rotate operations needed
return count;
}

This approach should produce the correct result in all cases, since it checks for the two lists being already equal, and then checks if the reverse() operation alone is sufficient to transform a into b. If neither of these conditions is met, then it performs the rotate() operation repeatedly until a match is found.

I hope this helps! Let me know if you have any other questions.

  • Related