Java | JavaScript | TypeScript | C++ | C# | Kotlin | Go Solution.
```Java []
public class Solution {

    private static final int BIT_SET_SUBGROUPS = 2;
    private static final int[] RANGE_OF_VALUES = {1, 100};
    private int minNumberOfUniqueValueConnections;

    public int numberOfComponents(int[][] properties, int minNumberOfUniqueValueConnections) {
        this.minNumberOfUniqueValueConnections = minNumberOfUniqueValueConnections;
        long[][] bitSetGroups = createBitSetGroups(properties);
        return findNumberOfConnectedGroups(properties, bitSetGroups);
    }

    private long[][] createBitSetGroups(int[][] properties) {
        /*
        To keep the number of bit shifts within a 64-bit integer, each bitSetGroups[i] has: 
        bitSetGroups[i][0] => stores unique values from 1 to 50 
        bitSetGroups[i][1] => stores unique values from 51 to 100 
         */
        long[][] bitSetGroups = new long[properties.length][BIT_SET_SUBGROUPS];

        for (int i = 0; i < properties.length; ++i) {
            for (int value : properties[i]) {

                int index = value / (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1);
                int bitShifts = 1 + value % (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1);
                long bitStamp = 1L << bitShifts;

                bitSetGroups[i][index] |= bitStamp;
            }
        }
        return bitSetGroups;
    }

    private int findNumberOfConnectedGroups(int[][] properties, long[][] bitSetGroups) {
        UnionFind unionFind = new UnionFind(properties.length);
        int numberOfComponents = properties.length;

        for (int i = 0; i < bitSetGroups.length; ++i) {
            for (int j = i + 1; j < bitSetGroups.length; ++j) {

                int totalConnections = 0;
                for (int g = 0; g < BIT_SET_SUBGROUPS; ++g) {
                    totalConnections += countBitsSetToOne(bitSetGroups[i][g] & bitSetGroups[j][g]);
                }

                if (totalConnections >= minNumberOfUniqueValueConnections && unionFind.joinByRank(i, j)) {
                    --numberOfComponents;
                }
            }
        }
        return numberOfComponents;
    }

    private int countBitsSetToOne(long value) {
        int bitsSetToOne = 0;
        while (value > 0) {
            bitsSetToOne += (value & 1);
            value >>= 1;
        }
        return bitsSetToOne;
    }
}

class UnionFind {

    private int[] parent;
    private int[] rank;

    UnionFind(int numberOfElements) {
        parent = new int[numberOfElements];
        rank = new int[numberOfElements];
        for (int i = 0; i < numberOfElements; ++i) {
            parent[i] = i;
            rank[i] = 1;
        }
        /*
        Alternatively:
        parent = IntStream.range(0, numberOfElements).toArray();
        rank = new int[numberOfElements];
        Arrays.fill(rank, 1);
         */
    }

    private int findParent(int index) {
        if (parent[index] != index) {
            parent[index] = findParent(parent[index]);
        }
        return parent[index];
    }

    boolean joinByRank(int indexOne, int indexTwo) {
        int first = findParent(indexOne);
        int second = findParent(indexTwo);
        if (first == second) {
            return false;
        }

        if (rank[first] >= rank[second]) {
            rank[first] += rank[second];
            parent[second] = first;
        } else {
            rank[second] += rank[first];
            parent[first] = second;
        }
        return true;
    }
}
```
```JavaScript []
/**
 * @param {number[][]} properties
 * @param {number} minNumberOfUniqueValueConnections
 * @return {number}
 */
var numberOfComponents = function (properties, minNumberOfUniqueValueConnections) {
    this.BIT_SET_SUBGROUPS = 4;
    this.RANGE_OF_VALUES = [1, 100];
    this.minNumberOfUniqueValueConnections = minNumberOfUniqueValueConnections;

    const bitSetGroups = createBitSetGroups(properties);
    return findNumberOfConnectedGroups(properties, bitSetGroups);
};

/**
 * @param {number[][]} properties
 * @return {number[][]}
 */
function createBitSetGroups(properties) {
    /*
     To keep the number of bit shifts within a 32-bit integer, each bitSetGroups[i] has: 
     bitSetGroups[i][0] => stores unique values from 1 to 25 
     bitSetGroups[i][1] => stores unique values from 26 to 50 
     bitSetGroups[i][2] => stores unique values from 51 to 75 
     bitSetGroups[i][3] => stores unique values from 76 to 100 
     
     While the solutions in the other languages keep the number of bit shifts within a 64-bit integer,
     thus bitSetGroups[i] is divided into two parts, from 1 to 50 and from 51 to 100, in JavaScript, 
     correspondingly in TypeScript, when performing bitwise operations on their standard number, 
     implemented in double-precision 64-bit binary format IEEE 754, it is treated in a 32-bit binary format. 
     
     To avoid overflowing, one option is to apply their inbuilt BigInt class (the less efficient option) 
     or, as is done here, to divide the bitSetGroups[i] into four equal parts, 
     so that the bit shifts do not exceed the 32-bit.
     */
    const bitSetGroups = Array.from(new Array(properties.length), () => new Array(this.BIT_SET_SUBGROUPS).fill(0));

    for (let i = 0; i < properties.length; ++i) {
        for (let value of properties[i]) {

            const index = Math.floor(value / (this.RANGE_OF_VALUES[1] / this.BIT_SET_SUBGROUPS + 1));
            const bitShifts = 1 + (value % (this.RANGE_OF_VALUES[1] / this.BIT_SET_SUBGROUPS + 1));
            const bitStamp = 1 << bitShifts;

            bitSetGroups[i][index] |= bitStamp;
        }
    }
    return bitSetGroups;
}

/**
 * @param {number[][]} properties
 * @param {number[][]} bitSetGroups
 * @return {number}
 */
function findNumberOfConnectedGroups(properties, bitSetGroups) {
    const unionFind = new UnionFind(properties.length);
    let numberOfComponents = properties.length;

    for (let i = 0; i < bitSetGroups.length; ++i) {
        for (let j = i + 1; j < bitSetGroups.length; ++j) {

            let totalConnections = 0;
            for (let g = 0; g < this.BIT_SET_SUBGROUPS; ++g) {
                totalConnections += countBitsSetToOne(bitSetGroups[i][g] & bitSetGroups[j][g]);
            }

            if (totalConnections >= this.minNumberOfUniqueValueConnections && unionFind.joinByRank(i, j)) {
                --numberOfComponents;
            }
        }
    }
    return numberOfComponents;
}

/**
 * @param {number} value
 * @return {number}
 */
function  countBitsSetToOne(value) {
    let bitsSetToOne = 0;
    while (value > 0) {
        bitsSetToOne += (value & 1);
        value >>= 1;
    }
    return bitsSetToOne;
}


class UnionFind {

    #parent;
    #rank;

    /**
     * @param {number} numberOfElements
     */
    constructor(numberOfElements) {
        this.#parent = new Array(numberOfElements);
        this.#rank = new Array(numberOfElements);
        for (let i = 0; i < numberOfElements; ++i) {
            this.#parent[i] = i;
            this.#rank[i] = 1;
        }
        /*
         Alternatively:
         this.parent = Array.from(Array(numberOfElements).keys());
         this.rank = new Array(numberOfElements).fill(1);
         */
    }

    /**
     * @param {number} index
     * @return {number}
     */
    #findParent(index) {
        if (this.#parent[index] !== index) {
            this.#parent[index] = this.#findParent(this.#parent[index]);
        }
        return this.#parent[index];
    }

    /**
     * @param {number} indexOne
     * @param {number} indexTwo
     * @return {boolean}
     */
    joinByRank(indexOne, indexTwo) {
        const first = this.#findParent(indexOne);
        const second = this.#findParent(indexTwo);
        if (first === second) {
            return false;
        }

        if (this.#rank[first] >= this.#rank[second]) {
            this.#rank[first] += this.#rank[second];
            this.#parent[second] = first;
        } else {
            this.#rank[second] += this.#rank[first];
            this.#parent[first] = second;
        }
        return true;
    }
}
```
```TypeScript []
function numberOfComponents(properties: number[][], minNumberOfUniqueValueConnections: number): number {
    this.BIT_SET_SUBGROUPS = 4;
    this.RANGE_OF_VALUES = [1, 100];
    this.minNumberOfUniqueValueConnections = minNumberOfUniqueValueConnections;

    const bitSetGroups = createBitSetGroups(properties);
    return findNumberOfConnectedGroups(properties, bitSetGroups);
};

function createBitSetGroups(properties: number[][]): number[][] {
    /*
     To keep the number of bit shifts within a 32-bit integer, each bitSetGroups[i] has: 
     bitSetGroups[i][0] => stores unique values from 1 to 25 
     bitSetGroups[i][1] => stores unique values from 26 to 50 
     bitSetGroups[i][2] => stores unique values from 51 to 75 
     bitSetGroups[i][3] => stores unique values from 76 to 100 
     
     While the solutions in the other languages keep the number of bit shifts within a 64-bit integer,
     thus bitSetGroups[i] is divided into two parts, from 1 to 50 and from 51 to 100, in JavaScript, 
     correspondingly in TypeScript, when performing bitwise operations on their standard number, 
     implemented in double-precision 64-bit binary format IEEE 754, it is treated in a 32-bit binary format. 
     
     To avoid overflowing, one option is to apply their inbuilt BigInt class (the less efficient option) 
     or, as is done here, to divide the bitSetGroups[i] into four equal parts, 
     so that the bit shifts do not exceed the 32-bit.
     */
    const bitSetGroups = Array.from(new Array(properties.length), () => new Array(this.BIT_SET_SUBGROUPS).fill(0));

    for (let i = 0; i < properties.length; ++i) {
        for (let value of properties[i]) {

            const index = Math.floor(value / (this.RANGE_OF_VALUES[1] / this.BIT_SET_SUBGROUPS + 1));
            const bitShifts = 1 + (value % (this.RANGE_OF_VALUES[1] / this.BIT_SET_SUBGROUPS + 1));
            const bitStamp = 1 << bitShifts;

            bitSetGroups[i][index] |= bitStamp;
        }
    }
    return bitSetGroups;
}

function findNumberOfConnectedGroups(properties: number[][], bitSetGroups: number[][]): number {
    const unionFind = new UnionFind(properties.length);
    let numberOfComponents = properties.length;

    for (let i = 0; i < bitSetGroups.length; ++i) {
        for (let j = i + 1; j < bitSetGroups.length; ++j) {

            let totalConnections = 0;
            for (let g = 0; g < this.BIT_SET_SUBGROUPS; ++g) {
                totalConnections += countBitsSetToOne(bitSetGroups[i][g] & bitSetGroups[j][g]);
            }

            if (totalConnections >= this.minNumberOfUniqueValueConnections && unionFind.joinByRank(i, j)) {
                --numberOfComponents;
            }
        }
    }
    return numberOfComponents;
}

function countBitsSetToOne(value: number): number {
    let bitsSetToOne = 0;
    while (value > 0) {
        bitsSetToOne += (value & 1);
        value >>= 1;
    }
    return bitsSetToOne;
}


class UnionFind {

    private parent: number[];
    private rank: number[];

    constructor(numberOfElements: number) {
        this.parent = new Array(numberOfElements);
        this.rank = new Array(numberOfElements);
        for (let i = 0; i < numberOfElements; ++i) {
            this.parent[i] = i;
            this.rank[i] = 1;
        }
        /*
         Alternatively:
         this.parent = Array.from(Array(numberOfElements).keys());
         this.rank = new Array(numberOfElements).fill(1);
         */
    }

    private findParent(index: number): number {
        if (this.parent[index] !== index) {
            this.parent[index] = this.findParent(this.parent[index]);
        }
        return this.parent[index];
    }

    joinByRank(indexOne: number, indexTwo: number): boolean {
        const first = this.findParent(indexOne);
        const second = this.findParent(indexTwo);
        if (first === second) {
            return false;
        }

        if (this.rank[first] >= this.rank[second]) {
            this.rank[first] += this.rank[second];
            this.parent[second] = first;
        } else {
            this.rank[second] += this.rank[first];
            this.parent[first] = second;
        }
        return true;
    }
}
```
```C++ []
#include <span>
#include <vector>
using namespace std;

class UnionFind {

    vector<int> parent;
    vector<int> rank;

public:
    UnionFind(int numberOfElements) {
        parent.resize(numberOfElements);
        rank.resize(numberOfElements);
        for (int i = 0; i < numberOfElements; ++i) {
            parent[i] = i;
            rank[i] = 1;
        }
        /*
        Alternatively:
        parent.resize(numberOfElements);
        ranges::iota(parent, 0);
        rank.resize(numberOfElements, 1);
        */
    }

    int findParent(int index) {
        if (parent[index] != index) {
            parent[index] = findParent(parent[index]);
        }
        return parent[index];
    }

    bool joinByRank(int indexOne, int indexTwo) {
        int first = findParent(indexOne);
        int second = findParent(indexTwo);
        if (first == second) {
            return false;
        }

        if (rank[first] >= rank[second]) {
            rank[first] += rank[second];
            parent[second] = first;
        }
        else {
            rank[second] += rank[first];
            parent[first] = second;
        }
        return true;
    }
};

class Solution {

    static const int BIT_SET_SUBGROUPS = 2;
    static constexpr array<int, 2> RANGE_OF_VALUES = { 1, 100 };
    int minNumberOfUniqueValueConnections{};

public:
    int numberOfComponents(vector<vector<int>>& properties, int minNumberOfUniqueValueConnections) {
        this->minNumberOfUniqueValueConnections = minNumberOfUniqueValueConnections;
        vector<vector<long long>> bitSetGroups = createBitSetGroups(properties);
        return findNumberOfConnectedGroups(properties, bitSetGroups);
    }

private:
    vector<vector<long long>> createBitSetGroups(span<const vector<int>> properties) const {
        /*
        To keep the number of bit shifts within a 64-bit integer, each bitSetGroups[i] has:
        bitSetGroups[i][0] => stores unique values from 1 to 50
        bitSetGroups[i][1] => stores unique values from 51 to 100
         */
        vector<vector<long long>> bitSetGroups(properties.size(), vector<long long>(BIT_SET_SUBGROUPS)); ;

        for (int i = 0; i < properties.size(); ++i) {
            for (int value : properties[i]) {

                int index = value / (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1);
                int bitShifts = 1 + value % (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1);
                long long bitStamp = 1LL << bitShifts;

                bitSetGroups[i][index] |= bitStamp;
            }
        }
        return bitSetGroups;
    }

    int findNumberOfConnectedGroups(span<const vector<int>> properties, span<const vector<long long>> bitSetGroups) const {
        UnionFind unionFind(properties.size());
        int numberOfComponents = properties.size();

        for (int i = 0; i < bitSetGroups.size(); ++i) {
            for (int j = i + 1; j < bitSetGroups.size(); ++j) {

                int totalConnections = 0;
                for (int g = 0; g < BIT_SET_SUBGROUPS; ++g) {
                    totalConnections += countBitsSetToOne(bitSetGroups[i][g] & bitSetGroups[j][g]);
                }

                if (totalConnections >= minNumberOfUniqueValueConnections && unionFind.joinByRank(i, j)) {
                    --numberOfComponents;
                }
            }
        }
        return numberOfComponents;
    }

    int countBitsSetToOne(long long value) const {
        int bitsSetToOne = 0;
        while (value > 0) {
            bitsSetToOne += (value & 1);
            value >>= 1;
        }
        return bitsSetToOne;
    }
};
```
```C# []
using System;

public class Solution
{
    private static readonly int BIT_SET_SUBGROUPS = 2;
    private static readonly int[] RANGE_OF_VALUES = { 1, 100 };
    private int minNumberOfUniqueValueConnections;

    public int NumberOfComponents(int[][] properties, int minNumberOfUniqueValueConnections)
    {
        this.minNumberOfUniqueValueConnections = minNumberOfUniqueValueConnections;
        long[][] bitSetGroups = CreateBitSetGroups(properties);
        return FindNumberOfConnectedGroups(properties, bitSetGroups);
    }

    private long[][] CreateBitSetGroups(int[][] properties)
    {
        /*
        To keep the number of bit shifts within a 64-bit integer, each bitSetGroups[i] has: 
        bitSetGroups[i][0] => stores unique values from 1 to 50 
        bitSetGroups[i][1] => stores unique values from 51 to 100 
         */
        long[][] bitSetGroups = new long[properties.Length][];

        for (int i = 0; i < properties.Length; ++i)
        {
            bitSetGroups[i] = new long[BIT_SET_SUBGROUPS];

            foreach (int value in properties[i])
            {

                int index = value / (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1);
                int bitShifts = 1 + value % (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1);
                long bitStamp = 1L << bitShifts;

                bitSetGroups[i][index] |= bitStamp;
            }
        }
        return bitSetGroups;
    }

    private int FindNumberOfConnectedGroups(int[][] properties, long[][] bitSetGroups)
    {
        UnionFind unionFind = new UnionFind(properties.Length);
        int numberOfComponents = properties.Length;

        for (int i = 0; i < bitSetGroups.Length; ++i)
        {
            for (int j = i + 1; j < bitSetGroups.Length; ++j)
            {

                int totalConnections = 0;
                for (int g = 0; g < BIT_SET_SUBGROUPS; ++g)
                {
                    totalConnections += CountBitsSetToOne(bitSetGroups[i][g] & bitSetGroups[j][g]);
                }

                if (totalConnections >= minNumberOfUniqueValueConnections && unionFind.JoinByRank(i, j))
                {
                    --numberOfComponents;
                }
            }
        }
        return numberOfComponents;
    }

    private int CountBitsSetToOne(long value)
    {
        int bitsSetToOne = 0;
        while (value > 0)
        {
            bitsSetToOne += (int)(value & 1);
            value >>= 1;
        }
        return bitsSetToOne;
    }
}

class UnionFind
{

    private int[] parent;
    private int[] rank;

    public UnionFind(int numberOfElements)
    {
        parent = new int[numberOfElements];
        rank = new int[numberOfElements];
        for (int i = 0; i < numberOfElements; ++i)
        {
            parent[i] = i;
            rank[i] = 1;
        }
        /*
         Alternatively:
         parent = Enumerable.Range(0, numberOfElements).ToArray();
         rank = new int[numberOfElements];
         Array.Fill(rank, 1);
         */
    }

    private int FindParent(int index)
    {
        if (parent[index] != index)
        {
            parent[index] = FindParent(parent[index]);
        }
        return parent[index];
    }

    public bool JoinByRank(int indexOne, int indexTwo)
    {
        int first = FindParent(indexOne);
        int second = FindParent(indexTwo);
        if (first == second)
        {
            return false;
        }

        if (rank[first] >= rank[second])
        {
            rank[first] += rank[second];
            parent[second] = first;
        }
        else
        {
            rank[second] += rank[first];
            parent[first] = second;
        }
        return true;
    }
}
```
```Kotlin []
class Solution {

    private companion object {
        const val BIT_SET_SUBGROUPS = 2
        val RANGE_OF_VALUES = intArrayOf(1, 100)
    }

    private var minNumberOfUniqueValueConnections = 0

    fun numberOfComponents(properties: Array<IntArray>, minNumberOfUniqueValueConnections: Int): Int {
        this.minNumberOfUniqueValueConnections = minNumberOfUniqueValueConnections
        val bitSetGroups: Array<LongArray> = createBitSetGroups(properties)
        return findNumberOfConnectedGroups(properties, bitSetGroups)
    }

    private fun createBitSetGroups(properties: Array<IntArray>): Array<LongArray> {
        /*
        To keep the number of bit shifts within a 64-bit integer, each bitSetGroups[i] has:
        bitSetGroups[i][0] => stores unique values from 1 to 50
        bitSetGroups[i][1] => stores unique values from 51 to 100
         */
        val bitSetGroups = Array(properties.size) { LongArray(BIT_SET_SUBGROUPS) }

        for (i in properties.indices) {
            for (value in properties[i]) {

                val index = value / (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1)
                val bitShifts = 1 + value % (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1)
                val bitStamp : Long = 1L shl bitShifts

                bitSetGroups[i][index] = bitSetGroups[i][index] or bitStamp
            }
        }
        return bitSetGroups
    }

    private fun findNumberOfConnectedGroups(properties: Array<IntArray>, bitSetGroups: Array<LongArray>): Int {
        val unionFind = UnionFind(properties.size)
        var numberOfComponents = properties.size

        for (i in bitSetGroups.indices) {
            for (j in i + 1..<bitSetGroups.size) {

                var totalConnections = 0
                for (g in 0..<BIT_SET_SUBGROUPS) {
                    totalConnections += countBitsSetToOne(bitSetGroups[i][g] and bitSetGroups[j][g])
                }

                if (totalConnections >= minNumberOfUniqueValueConnections && unionFind.joinByRank(i, j)) {
                    --numberOfComponents
                }
            }
        }
        return numberOfComponents
    }

    private fun countBitsSetToOne(value: Long): Int {
        var bitsSetToOne = 0
        var value: Long = value
        while (value > 0) {
            bitsSetToOne += (value and 1).toInt()
            value = value shr 1
        }
        return bitsSetToOne
    }
}

class UnionFind(private val numberOfElements: Int) {

    private val parent = IntArray(numberOfElements)
    private val rank = IntArray(numberOfElements)

    init {
        for (i in 0..<numberOfElements) {
            parent[i] = i
            rank[i] = 1
        }
    }
    /*
     Alternatively:
     private val parent = IntArray(numberOfElements) { i -> i }
     private val rank = IntArray(numberOfElements) { 1 }
     */

    private fun findParent(index: Int): Int {
        if (parent[index] != index) {
            parent[index] = findParent(parent[index])
        }
        return parent[index]
    }

    fun joinByRank(indexOne: Int, indexTwo: Int): Boolean {
        val first = findParent(indexOne)
        val second = findParent(indexTwo)
        if (first == second) {
            return false
        }

        if (rank[first] >= rank[second]) {
            rank[first] += rank[second]
            parent[second] = first
        } else {
            rank[second] += rank[first]
            parent[first] = second
        }
        return true
    }
}
```
```Go []
package main

const BIT_SET_SUBGROUPS = 2
var RANGE_OF_VALUES = [2]int{1, 100}
var minNumberOfUniqueValueConnections int

func numberOfComponents(properties [][]int, minNumberOfConnections int) int {
    minNumberOfUniqueValueConnections = minNumberOfConnections
    var bitSetGroups [][]int64 = createBitSetGroups(properties)
    return findNumberOfConnectedGroups(properties, bitSetGroups)
}

func createBitSetGroups(properties [][]int) [][]int64 {
    /*
     To keep the number of bit shifts within a 64-bit integer, each bitSetGroups[i] has:
     bitSetGroups[i][0] => stores unique values from 1 to 50
     bitSetGroups[i][1] => stores unique values from 51 to 100
    */
    bitSetGroups := make([][]int64, len(properties))

    for i := range properties {

        bitSetGroups[i] = make([]int64, BIT_SET_SUBGROUPS)

        for _, value := range properties[i] {

            index := value / (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS + 1)
            bitShifts := 1 + value % (RANGE_OF_VALUES[1] / BIT_SET_SUBGROUPS+1)
            bitStamp := int64(1) << bitShifts

            bitSetGroups[i][index] |= bitStamp
        }
    }
    return bitSetGroups
}

func findNumberOfConnectedGroups(properties [][]int, bitSetGroups [][]int64) int {
    unionFind := NewUnionFind(len(properties))
    numberOfComponents := len(properties)

    for i := range bitSetGroups {
        for j := i + 1; j < len(bitSetGroups); j++ {

            totalConnections := 0
            for g := range BIT_SET_SUBGROUPS {
                totalConnections += countBitsSetToOne(bitSetGroups[i][g] & bitSetGroups[j][g])
            }

            if totalConnections >= minNumberOfUniqueValueConnections && unionFind.joinByRank(i, j) {
                numberOfComponents--
            }
        }
    }
    return numberOfComponents
}

func countBitsSetToOne(value int64) int {
    bitsSetToOne := 0
    for value > 0 {
        bitsSetToOne += int(value & 1)
        value >>= 1
    }
    return bitsSetToOne
}

type UnionFind struct {
    parent []int
    rank   []int
}

func NewUnionFind(numberOfElements int) *UnionFind {
    unionFind := &UnionFind{
        parent: make([]int, numberOfElements),
        rank:   make([]int, numberOfElements),
    }
    for i := range numberOfElements {
        unionFind.parent[i] = i
        unionFind.rank[i] = 1
    }
    return unionFind
}

func (this *UnionFind) findParent(index int) int {
    if this.parent[index] != index {
        this.parent[index] = this.findParent(this.parent[index])
    }
    return this.parent[index]
}

func (this *UnionFind) joinByRank(indexOne int, indexTwo int) bool {
    first := this.findParent(indexOne)
    second := this.findParent(indexTwo)
    if first == second {
        return false
    }

    if this.rank[first] >= this.rank[second] {
        this.rank[first] += this.rank[second]
        this.parent[second] = first
    } else {
        this.rank[second] += this.rank[first]
        this.parent[first] = second
    }
    return true
}
```
['C++', 'Java', 'Go', 'TypeScript', 'Kotlin', 'JavaScript', 'C#']