1

the method: public static Set<Function<T,T>> bijectionsFinder(Set<T> d)

say d= {1,2,3}. We are supposed to find all bijections from d -> d and return a set of those bijections.

Im not sure how to start.

Main method:

public static void main(String... args) {
    Set<Integer> a_few = Stream.of(1, 2, 3).collect(Collectors.toSet());
    Set<Function<T, T>> bijections = bijectionsOf(a_few);
    bijections.forEach(aBijection -> {
       a_few.forEach(n -> System.out.printf("%d --> %d; ", n, aBijection.apply(n)));
       System.out.println();
    });
}

Expected output if d= {1,2,3} :

enter image description here

  • 1
    Why is every bijection repeated twice in your expected output? – Adwait Kumar Nov 22 '22 at 14:16
  • I believe its becuase the other permutations change. The number of bijections from A -> A is n!, I hope this helps – Saurav Sharma Nov 22 '22 at 14:20
  • I understand this question mathematically, however I am confused how to start this and implement this using functional programming – Saurav Sharma Nov 22 '22 at 14:21
  • 1
    It wouldn't be a `Set>`: no `T` is defined in `#main`. What you're asking is essentially the possible permutations of the set (in this case, `S_3`, so 6 permutations of `()`, `(12)`, `(23)`, `(13)`, `(123)`, and `(132)` in cyclic notation). There's a _lot_ of ways to achieve this, but it sounds like you'd want to work with a `List` (more easily ordered) or `LinkedHashSet`, and then return the 6 new `LinkedHashSet`/`List` objects representing your permuted original `List`/`LinkedHashSet`. From there, iterate both simultaneously to print the bijections. – Rogue Nov 22 '22 at 15:01
  • @Rogue `LinkedHashSet` is a good choice, used this suggestion in the [implementation](https://stackoverflow.com/a/74539531/17949945). – Alexander Ivanchenko Nov 23 '22 at 20:25

1 Answers1

1

Bijection of the dataset to itself results in a set of Permutations of the given data.

And each Bijection should meet the definition:

For a pairing between X and Y (where Y need not be different from X) to be a bijection, four properties must hold:

  • each element of X must be paired with at least one element of Y,

  • no element of X may be paired with more than one element of Y,

  • each element of Y must be paired with at least one element of X, and

  • no element of Y may be paired with more than one element of X.

I.e. in a valid Bijection every element should be paired, a not none of the elements can be paired with more than one element.

To approach this a problem in a way that is easier to digest, we can describe Bijections as objects.

Let's define the following classes:

  • Bijection - representing a Bijection of the given dataset to itself. Since Bijection consists of pairs of elements, an instance of Bijection holds a reference to a list of Pairs.

  • Pair - represent a pair of elements in a Bijection.

While initializing a new instance of Bijection based on the given data (see the method init()) a List of Pairs is being created. To meet the definition shown above, the number of Pair is equal to the number of elements in the given dataset, and initially each Pair is being assigned with an element from the given dataset as its first element (a).

During the process of generating permutations, each pair is being provided with the second element (b). To track the pair that needs to be changed, Bijection has a property cursor

Both Bijection and Pair expose method copy() to facilitate the process of generating permutations.

public class Bijection<T> {
    private List<Pair<T>> pairs;
    private int cursor;

    public Bijection(List<Pair<T>> pairs) {
        this.pairs = pairs;
    }

    public Bijection(List<Pair<T>> pairs, int cursor) {
        this.pairs = pairs;
        this.cursor = cursor;
    }
    
    public static <T> Bijection<T> init(Collection<T> source) {
        return new Bijection<>(
            source.stream().map(Pair::init).toList()
        );
    }
    
    public void add(T b) {
        if (cursor == pairs.size()) throw new IllegalStateException();
        
        pairs.get(cursor++).add(b);
    }
    
    public Bijection<T> copy() {
        
        return pairs.stream()
            .map(Pair::copy)
            .collect(Collectors.collectingAndThen(
                Collectors.toList(),
                list -> new Bijection<>(list, cursor)
            ));
    }
    
    @Override
    public String toString() {
        return "Bijection{ " + pairs.stream().map(Pair::toString).collect(Collectors.joining(", ")) + " }";
    }
    
    public static class Pair<T> {
        private T a;
        private T b;
        
        public Pair(T a, T b) {
            this.a = a;
            this.b = b;
        }
        
        public static <T> Pair<T> init(T a) {
            return new Pair<>(a, null);
        }
        
        public void add(T b) {
            this.b = b;
        }
        
        public Pair<T> copy() {
            return new Pair<>(a, b);
        }
        
        @Override
        public String toString() {
            return "Pair{" + a + " -> " + b + '}';
        }
    }
}

Here's the logic for generating all possible Bijections (i.e. all Permutations of elements in the given dataset expressed as connected pairs) encapsulated into a utility class.

It's a basic recursive implementation. The Base case of recursion is when provide source of data is empty, i.e. no elements left to use and Bijection (Permutation) gets added to the resulting list.

As the source of data for generating Permutations, I'll use a LinkedHashSet (as suggested by @Rogue in a comment) because it facilitates removal of elements in constant time and guaranties consistent order of iteration.

public static class Bijections {
    
    private Bijections() {}
    
    public static <T> List<Bijection<T>> getBijections(Collection<T> source) {
        Set<T> set = new LinkedHashSet<>(source);
        List<Bijection<T>> bijections = new ArrayList<>();
        
        permute(set, Bijection.init(set), bijections);
        
        return bijections;
    }
    
    public static <T> void permute(Set<T> source,
                                   Bijection<T> bijection, // <- current Bijection
                                   List<Bijection<T>> bijections) {
        
        if (source.isEmpty()) {
            bijections.add(bijection);
            return;
        }
        
        for (T next : source) {
            // generate a new bijection based on the existing one and update it (the initial bijection doesn't change)
            Bijection<T> bijectionToUpdate = bijection.copy();
            bijectionToUpdate.add(next);
            // create a copy of the source a remove the current element from the copy (since it has been already used)
            Set<T> updatedSource = new LinkedHashSet<>(source);
            updatedSource.remove(next);
            // perform recursive call
            permute(updatedSource, bijectionToUpdate, bijections);
        }
    }
}

main()

public static void main(String[] args) {
    Bijections.getBijections(List.of(1, 2, 3))
        .forEach(System.out::println);
}

Output:

Bijection{ Pair{1 -> 1}, Pair{2 -> 2}, Pair{3 -> 3} }
Bijection{ Pair{1 -> 1}, Pair{2 -> 3}, Pair{3 -> 2} }
Bijection{ Pair{1 -> 2}, Pair{2 -> 1}, Pair{3 -> 3} }
Bijection{ Pair{1 -> 2}, Pair{2 -> 3}, Pair{3 -> 1} }
Bijection{ Pair{1 -> 3}, Pair{2 -> 1}, Pair{3 -> 2} }
Bijection{ Pair{1 -> 3}, Pair{2 -> 2}, Pair{3 -> 1} }
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46