Count number of gemstones in a set of minerals This is the "Gemstones" problem on Hackerrank. 


  John has collected various rocks. Each rock has various minerals embeded in it. Each type of mineral is designated by a lowercase letter in the range a-z. There may be multiple occurrences of a mineral in a rock. A mineral is called a gemstone if it occurs at least once in each of the rocks in John's collection.
  Given a list of minerals embedded in each of John's rocks, display the number of types of gemstones he has in his collection.
  For example, the array of mineral composition strings [abc, abc, bc] The minerals b and c appear in each composite, so there are 2 gemstones.


This is my Python code:

def gemstones(arr):  
    for i in range(len(arr)):
        arr[i] = "".join(set(arr[i]))

    long = max(arr, key=len)
    arrlen = len(arr)
    flag,count = 0,0
    for i in long: 
        for j in range(arrlen):
            if i not in arr[j]: flag = 1
        if flag is 0: count += 1
        flag = 0
    return count


Are there ways to improve this code? I feel I'm not using the full functionality of Python.

A test case:

&gt;&gt;&gt; arr = ['abcdde', 'baccd', 'eeabg']
&gt;&gt;&gt; print(gemstones(arr))
2
You need to stop iterating over indexes and loop like a native. A first rewrite with that in mind would yield:

def gemstones(collection):
    collection = [''.join(set(rock)) for rock in collection]
    biggest_rock = max(collection, key=len)

    count = 0
    for mineral in biggest_rock:
        for rock in collection:
            if mineral not in rock:
                break
        else:
            count += 1
    return count


This version make use for the for...else construct. But the all() builtin would be more expressive:

def gemstones(collection):
    collection = [''.join(set(rock)) for rock in collection]
    biggest_rock = max(collection, key=len)

    count = 0
    for mineral in biggest_rock:
        if all(mineral in rock for rock in collection):
            count += 1

    return count


And counting a condition in a for-loop can be more efficient using sum():

def gemstones(collection):
    collection = [''.join(set(rock)) for rock in collection]
    biggest_rock = max(collection, key=len)

    return sum(
        all(mineral in rock for rock in collection)
        for mineral in biggest_rock
    )


Other than that, you won't have more gemstones than the amount of minerals in the smallest rock, so why doing extra work by using the biggest one to start with?



But you are going back and forth between strings and sets when the problem clearly calls for set intersection repeated on several elements of an array. Luckily, the set.intersection method accept a variable number of arguments. Just make sure to catch any error thrown in case the original collection is empty:

def gemstone(collection):
    rocks = map(set, collection)
    try:
        minerals = next(rocks)
    except StopIteration:
        return 0  # If the collection is empty, there is no gemstones
    return len(minerals.intersection(*rocks))