2
\$\begingroup\$

Task description:

Declare a function twoInThree(a: Set, b: Set, c: Set), which returns the set of all elements, which are contained in exactly two of the three given sets a, b, c.

My solution:

fun main(args: Array<String>) {
    var a = setOf<Int>(1, 2, 5)
    var b = setOf<Int>(2, 7, 0)
    var c = setOf<Int>(3, 5)

    val result = twoInThree(a, b, c)
    println(result) // [2, 5]
}

fun twoInThree(a: Set<Int>, b: Set<Int>, c: Set<Int>): MutableSet<Int> {
    var results = mutableSetOf<Int>()
    var intersectOfAll = a.intersect(b).intersect(c)

    val aIntersectB = a.intersect(b)
    val aIntersectC = a.intersect(c)
    val bIntersectC = b.intersect(c)

    results.addAll(aIntersectB)
    results.addAll(aIntersectC)
    results.addAll(bIntersectC)

    results.removeAll(intersectOfAll)

    return results
}

If got the same result as the instructor and tried it with other sets. It always returned the expected result. So, I guess my solution is formally correct.

But: Could the exercise become solved in a better way?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Before I go into another possible solution I will give you some suggestions on your current code:

  • Use val instead of var whenever possible. This is possible for all variables in your code.

  • No need to specify <Int> on setOf, Kotlin can figure out the best applicable type itself.

  • The function twoInThree is capable of handling any kinds of sets, not just ints. You can use a generic type parameter for this.

    Change the method to fun <T> twoInThree(a: Set<T>, b: Set<T>, c: Set<T>): MutableSet<T>

    And naturally also val results = mutableSetOf<T>()

  • Prefer to return immutable types unless you really need to do otherwise, in this case prefer to return a Set over MutableSet (callers don't need to care about the fact that you are using a MutableSet in your implementation)


The approach

Your approach involves checking for multiple intersections and then creating a union of all sets and removing the intersection of all sets.

The assignment is about finding elements which exists in exactly two of the three given sets a, b, c.

Your approach is fine, there's nothing wrong with it, but... Will it scale?

If you would check for elements which exists in all three of the sets, or in exactly one set, that could be done quite easily by returning different intersections and unions and so on. But what if there would be five sets?

The assignment is about exactly two of the three sets.

One, two, three.

We can use counting!

By counting the number of times each element appears, we can iterate over all sets (no matter if it's three, five, or twenty) exactly once.

By counting the number of times each element appears, we can easily change the code to return elements that occur only once, twice, or three times.

So how to do this?


My approach

Start with creating a list of all sets. listOf(a, b, c)

Then go through the list and map each set to all its elements. .flatMap { it } which can also be written as .flatten()

Then create a grouping of the elements .groupingBy { it }

Then create a map with the counts of each element .eachCount()

listOf(a, b, c).flatten().groupingBy { it }.eachCount()

This gives you a map Map<Int, Int> where the keys are the elements and the values are the number of times each element occurs.

Now you can filter this map and return the keys which matches your requirement .filter { it.value == 2 }.map { it.key }

Full code:

val result = listOf(a, b, c).flatten()
     .groupingBy { it }
     .eachCount()
     .filter { it.value == 2 }
     .map { it.key }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.