In [1]:
sc


Out[1]:
<pyspark.context.SparkContext at 0xa5587b8>

In [2]:
numPartitions = 2
import math
from pyspark.mllib.recommendation import ALS

def get_ratings_tuple(entry):
    items = entry.split('::')
    return int(items[0]), float(items[1]), float(items[2])
    
def get_movie_tuple(entry):
    items = entry.split('::')
    return int(items[0]),items[1]
    
    
def getCountsAndAverages(RatingsTuple):
    total = 0.0
    for rating in RatingsTuple[1]:
        total += rating
        
    return ( RatingsTuple[0], (len(RatingsTuple[1]), total/len(RatingsTuple[1])))
    
    
def sortFunction(tuple):
    key = unicode('%.3f' % tuple[0])
    value = tuple[1]
    return (key + ' ' + value)


#calculate RMSE

def computeRMSE(predictedRDD, actualRDD):
    predictedReformattedRDD = (predictedRDD.map(lambda (UserID, MovieId, Rating): ((UserID, MovieId),Rating)))
    
    actualReformattedRDD = (actualRDD.map(lambda (UserID, MovieId, Rating): ((UserID, MovieId),Rating)))
    
    squaredErrorsRDD = (predictedReformattedRDD.join(actualReformattedRDD).map(lambda(k,(a,b)): math.pow((a-b),2)))
    
    totalErrors = squaredErrorsRDD.reduce(lambda a,b: a+b)
    numRatings = squaredErrorsRDD.count()
    
    return math.sqrt(float(totalErrors)/numRatings)
    
    
def apk(actual, predicted, k=10):

    if len(predicted)>k:
        predicted = predicted[:k]

    score = 0.0
    num_hits = 0.0

    for i,p in enumerate(predicted):
        if p in actual and p not in predicted[:i]:
            num_hits += 1.0
            score += num_hits / (i+1.0)

    if not actual:
        return 0.0

    return score / min(len(actual), k) 


rawRatings = sc.textFile('d:/spark/data/mllib/als/sample_movielens_ratings.txt').repartition(numPartitions)
rawMovies = sc.textFile('d:/spark/data/mllib/als/sample_movielens_movies.txt')
    
ratingsRDD = rawRatings.map(get_ratings_tuple).cache()
moviesRDD = rawMovies.map(get_movie_tuple).cache()
ratingsRDD.first()


Out[2]:
(0, 2.0, 3.0)

In [3]:
movieIDsWithRatingsRDD = (ratingsRDD.map(lambda (userId, movieId, rating): (movieId, [rating])).reduceByKey(lambda a,b: a+b))
movieIDsWithRatingsRDD.take(10)
movieNameWithAvgRatingsRDD = movieIDsWithRatingsRDD.map(getCountsAndAverages)
movieNameWithAvgRatingsRDD.take(10)
movieNameWithAvgRatingsRDD = ( moviesRDD .join(movieNameWithAvgRatingsRDD) .map(lambda ( movieid,(name,(ratings, average)) ): (average, name, ratings)) )

movieNameWithAvgRatingsRDD.take(10)
#this is basic recommendations
movieLimitedAndSortedByRatingRDD = ( movieNameWithAvgRatingsRDD.filter( lambda(average,name,ratings): ratings > 2).sortBy(sortFunction, ascending=False))

movieLimitedAndSortedByRatingRDD.take(10)


Out[3]:
[(2.9166666666666665, u'Movie 32', 12),
 (2.8125, u'Movie 90', 16),
 (2.5, u'Movie 30', 14),
 (2.473684210526316, u'Movie 94', 19),
 (2.466666666666667, u'Movie 23', 15),
 (2.4375, u'Movie 49', 16),
 (2.4, u'Movie 29', 20),
 (2.4, u'Movie 18', 15),
 (2.357142857142857, u'Movie 52', 14),
 (2.25, u'Movie 62', 16)]

In [4]:
#write these to a file

#this is for proper recommendations
trainingRDD, validationRDD, testRDD = ratingsRDD.randomSplit([6,2,2], seed=0L)
   
validationForPredictionRDD = validationRDD.map(lambda(userId, movieId, rating): (userId,movieId))

ranks = [4,8,12]
errors = [0,0,0]
err = 0
minError = float('inf')
bestRank = -1
bestIteration = -1

for rank in ranks:
    
    model = ALS.train(trainingRDD, rank, seed=5L, iterations =5, lambda_= 0.1)
    predictedRatingsRDD = model.predictAll(validationForPredictionRDD);
    error = computeRMSE(predictedRatingsRDD, validationRDD)
    errors[err] = error
    err += 1
    if error < minError:
        minError = error
        bestRank = rank

bestModel = ALS.train(trainingRDD, bestRank, seed=5L, iterations=5, lambda_=0.1)

testForPredictingRDD = testRDD.map(lambda (userId, movieId, rating): (userId, movieId))
predictedTestRDD = bestModel.predictAll(testForPredictingRDD)

testRMSE = computeRMSE(testRDD, predictedTestRDD)
#generate new recommendations for all users

print testRMSE


1.20217585459

In [5]:
userRatings = ratingsRDD.map(lambda(userId, movieId, rating): (userId,movieId))
predictionsRDD = model.predictAll(userRatings).map(lambda(userId, movieId, rating): ((userId, movieId),rating))
ratingsAndPredictionsRDD =  ratingsRDD.map(lambda (userId, movieId, rating): ((userId, movieId),rating)).join(predictionsRDD)

ratingsAndPredictionsRDD.take(10)


Out[5]:
[((7, 77.0), (1.0, 1.1726902436479714)),
 ((27, 83.0), (3.0, 2.493215180459293)),
 ((24, 4.0), (1.0, 0.5497256272989122)),
 ((22, 68.0), (4.0, 3.606855753929029)),
 ((25, 1.0), (3.0, 1.2840620428001523)),
 ((22, 6.0), (2.0, 1.943453814031724)),
 ((7, 55.0), (1.0, 1.1291704658016397)),
 ((20, 48.0), (1.0, 1.1715859679617528)),
 ((10, 8.0), (1.0, 0.9384447793190722)),
 ((6, 48.0), (1.0, 0.92150181587232))]

In [6]:
MeanSquaredErr = ratingsAndPredictionsRDD.map(lambda ((UserID,MovieId),(Actual,Predicted)):(Actual-Predicted) **2).sum() / ratingsAndPredictionsRDD.count()
RootMeanSquaredError = math.sqrt(MeanSquaredErr)
randomUserRDD =  userRatings.takeSample('true',1,1)
userID = randomUserRDD[0][0]
print userID
user = userRatings.keyBy(lambda rating: rating[0]).lookup(userID)
print user


pred = predictionsRDD.keyBy(lambda rating: rating[0]).lookup(userID)
print user


26
[(26, 1.0), (26, 3.0), (26, 5.0), (26, 7.0), (26, 14.0), (26, 18.0), (26, 21.0), (26, 23.0), (26, 27.0), (26, 35.0), (26, 40.0), (26, 45.0), (26, 48.0), (26, 50.0), (26, 54.0), (26, 57.0), (26, 61.0), (26, 66.0), (26, 71.0), (26, 76.0), (26, 85.0), (26, 88.0), (26, 94.0), (26, 96.0), (26, 0.0), (26, 2.0), (26, 4.0), (26, 6.0), (26, 13.0), (26, 16.0), (26, 20.0), (26, 22.0), (26, 24.0), (26, 31.0), (26, 36.0), (26, 44.0), (26, 47.0), (26, 49.0), (26, 52.0), (26, 55.0), (26, 58.0), (26, 62.0), (26, 68.0), (26, 73.0), (26, 81.0), (26, 86.0), (26, 91.0), (26, 95.0), (26, 97.0)]
[(26, 1.0), (26, 3.0), (26, 5.0), (26, 7.0), (26, 14.0), (26, 18.0), (26, 21.0), (26, 23.0), (26, 27.0), (26, 35.0), (26, 40.0), (26, 45.0), (26, 48.0), (26, 50.0), (26, 54.0), (26, 57.0), (26, 61.0), (26, 66.0), (26, 71.0), (26, 76.0), (26, 85.0), (26, 88.0), (26, 94.0), (26, 96.0), (26, 0.0), (26, 2.0), (26, 4.0), (26, 6.0), (26, 13.0), (26, 16.0), (26, 20.0), (26, 22.0), (26, 24.0), (26, 31.0), (26, 36.0), (26, 44.0), (26, 47.0), (26, 49.0), (26, 52.0), (26, 55.0), (26, 58.0), (26, 62.0), (26, 68.0), (26, 73.0), (26, 81.0), (26, 86.0), (26, 91.0), (26, 95.0), (26, 97.0)]

In [7]:
actualMovies = [MU[1] for MU in user]

predictedMovies = [MU[1] for MU in pred]

apk10= apk(actualMovies,predictedMovies,10)
print apk10

#http://blog.5ibc.net/p/20398.html
#https://books.google.com.au/books?id=syPHBgAAQBAJ&pg=PA103&lpg=PA103&dq=recommendproducts+movielens&source=bl&ots=X9TEXS538v&sig=lCKSQTf7EIjsQVHlcE5pC2yNxLE&hl=en&sa=X&ved=0ahUKEwjmpfqRzLnJAhWCmZQKHYdtBBMQ6AEIIzAB#v=onepage&q=recommendproducts%20movielens&f=false    
#   I am here
#unratedMoviesByUserRDD = (moviesRDD.map(lambda (movieID, name): movieID).filter(lambda movieID: movieID not in [this[1] for this in myRatedMovies]).map(lambda movieID: (userID, movieID)))

#recommendationRDD = bestModel.predictAll(unratedMoviesByUserRDD)

# get only the most rated movies
#movieCountsRDD = movieIDsWithAvgRatingsRDD.map(lambda (movie_id, (ratings, average)): (movie_id, ratings)) 
#predictedRDD = predictedRatingsRDD.map(lambda (uid, movie_id, rating): (movie_id, rating)) 
#predictedWithCountsRDD= (predictedRDD.join(movieCountsRDD)) 
#ratingsWithNamesRDD = (predictedWithCountsRDD.join(moviesRDD).map(lambda (movie_id, ((pred, ratings), name)): (pred, name, ratings)).filter(lambda (pred, name, ratings): ratings > 75))

#predictedMovies = ratingsWithNamesRDD.takeOrdered(20, key=lambda X: -x[0])


0.0

In [8]:
import numpy as np
itemFactors = model.productFeatures().map(lambda (userId, factor): factor).collect()

itemMatrix = np.array(itemFactors)
imBroadcast = sc.broadcast(itemMatrix)

In [9]:
scoresForUserRDD = model.userFeatures().map(lambda (userId, array): (userId, np.dot(imBroadcast.value, array)))
allRecsRDD = scoresForUserRDD.map(lambda (userId, scores): 
                            (userId, sorted(zip(np.arange(1, scores.size), scores), key=lambda x: x[1], reverse=True))
                           ).map(lambda (userId, sortedScores): (userId, np.array(sortedScores, dtype=int)[:,0]))
print allRecsRDD.first()[0]
print allRecsRDD.first()[1]


0
[64 15 59 84 80 13 20 38 25 68 47 73 10 19  5 74 76 70 65 54 24 49 36 85 72
 75 55 69  8 46 43 62  7  6 32 71 61 37 27 66 53 77 60 91 50 88 94 99 41 67
 33 56 40 39 79 16 48  9 92 78 95 97  4 98 31 12 83 58 89 52 21 86 57 63  3
  2 28 44 96 93 81 29 30 11 14 35 18 51 90 42 26  1 23 82 87 22 34 45 17]

In [10]:
userMoviesRDD = ratingsRDD.map(lambda r: (r[0], r[1])).groupByKey()
print userMoviesRDD.first()[0]
print userMoviesRDD.first()[1].data


0
[2.0, 5.0, 11.0, 15.0, 19.0, 23.0, 27.0, 29.0, 31.0, 37.0, 44.0, 46.0, 48.0, 51.0, 55.0, 61.0, 67.0, 69.0, 72.0, 79.0, 87.0, 91.0, 94.0, 96.0, 99.0, 3.0, 9.0, 12.0, 17.0, 21.0, 26.0, 28.0, 30.0, 34.0, 41.0, 45.0, 47.0, 50.0, 54.0, 59.0, 64.0, 68.0, 71.0, 77.0, 83.0, 89.0, 92.0, 95.0, 98.0]

In [11]:
K = 10
MAPK = allRecsRDD.join(userMoviesRDD).map(lambda (userId, (predicted, actual)):
                                    apk(actual.data, predicted, K)
                                   ).sum() / allRecsRDD.count()
print "Mean Average Precision at K =", MAPK


Mean Average Precision at K = 0.310900793651

In [12]:
from pyspark.mllib.evaluation import RegressionMetrics
predictedAndTrueRDD = ratingsAndPredictionsRDD.map(lambda ((user, product), (predicted, actual)): (predicted, actual))
regressionMetricsRDD = RegressionMetrics(predictedAndTrueRDD)
print "Mean Squared Error =", regressionMetricsRDD.meanSquaredError
print "Root Mean Squared Error =", regressionMetricsRDD.rootMeanSquaredError


Mean Squared Error = 0.61808161731
Root Mean Squared Error = 0.786181669406

In [13]:
from pyspark.mllib.evaluation import RankingMetrics
predictedAndTrueForRankingRDD = allRecsRDD.join(userMoviesRDD).map(lambda (userId, (predicted, actual)):
                                                        (map(int, list(predicted)), actual.data))
rankingMetricsRDD = RankingMetrics(predictedAndTrueForRankingRDD)
print "Mean Average Precision =", rankingMetricsRDD.meanAveragePrecision


Mean Average Precision = 0.512155932392

In [14]:
K = 2000
MAPK2000 = allRecsRDD.join(userMoviesRDD).map(lambda (userId, (predicted, actual)):
                                    apk(actual.data, predicted, K)
                                   ).sum() / allRecsRDD.count()
print "Mean Average Precision at 2000 =", MAPK2000


Mean Average Precision at 2000 = 0.512155932392

In [15]:
allRecsRDD.take(100)


Out[15]:
[(0, array([64, 15, 59, 84, 80, 13, 20, 38, 25, 68, 47, 73, 10, 19,  5, 74, 76,
         70, 65, 54, 24, 49, 36, 85, 72, 75, 55, 69,  8, 46, 43, 62,  7,  6,
         32, 71, 61, 37, 27, 66, 53, 77, 60, 91, 50, 88, 94, 99, 41, 67, 33,
         56, 40, 39, 79, 16, 48,  9, 92, 78, 95, 97,  4, 98, 31, 12, 83, 58,
         89, 52, 21, 86, 57, 63,  3,  2, 28, 44, 96, 93, 81, 29, 30, 11, 14,
         35, 18, 51, 90, 42, 26,  1, 23, 82, 87, 22, 34, 45, 17])),
 (8, array([68, 59, 62, 71, 84, 34, 76, 27, 85, 69, 48, 45, 78, 60, 92, 36, 17,
         94, 10,  5, 13, 96,  6, 40, 75, 31, 80, 20, 25, 64, 47, 19, 51, 95,
         32, 97, 28, 89,  3, 73, 99, 82, 61, 50, 56, 55, 38, 90, 79, 26, 41,
         53, 21, 54, 12, 66, 24, 88, 72,  9, 15, 58, 33, 46, 57, 44,  7, 49,
         70, 39, 52,  2, 23, 91, 22, 42, 63,  8, 93, 29,  1, 30, 16,  4, 98,
         86, 18, 74, 77, 67, 65, 81, 37, 35, 83, 43, 14, 87, 11])),
 (16,
  array([83, 68, 79, 88, 59, 12, 36, 73, 84, 13, 49, 74, 39, 75, 47, 80, 46,
         38, 10, 61, 89, 91, 85, 53, 29, 67, 41, 65, 56, 62, 96, 70,  8, 19,
         22, 64, 16,  5,  6, 77, 76, 23, 92, 63, 40, 78, 94, 99,  4, 28, 17,
         25, 54, 43, 50, 57, 69, 72, 34, 30, 93,  3, 32, 14, 60, 33, 44, 48,
         45, 24, 27, 31, 20, 42, 86, 35, 66, 37, 87,  2, 71, 90, 81,  9, 52,
         26, 51, 21, 58,  1, 11, 15, 97, 98, 55,  7, 18, 82, 95])),
 (24,
  array([59, 80, 13, 73, 38, 10, 46, 79, 47, 49,  5, 68, 88, 74, 61, 65, 43,
         84, 64, 36, 85, 77, 83, 14, 56, 12, 39, 19, 62, 71, 15, 40, 28, 70,
         60, 78,  8, 29, 16, 89, 30, 20, 44, 91, 33, 75, 55, 98,  7, 57, 24,
          9, 76, 92, 72, 66, 95, 99, 22, 67,  3, 41, 48, 94, 82, 42, 81, 23,
         52, 51, 34, 69, 37,  4, 11, 63, 96, 25, 35, 45, 86, 97,  1, 26, 93,
         21,  2, 58, 54,  6, 90, 50, 31, 53, 17, 27, 32, 87, 18])),
 (1, array([84, 68, 75, 20, 12, 83, 67, 64, 59, 27, 85, 36, 25, 19, 88, 53, 79,
          5,  8, 80, 91, 32, 61, 49, 46, 15, 99, 48, 73, 50, 96,  3, 17, 74,
         94, 47, 13, 70, 38, 55, 78, 66, 41,  7, 57, 62, 89, 60,  9, 23,  6,
         39, 45, 65, 16, 92, 10, 72, 29, 34, 24, 54,  4, 56, 33, 44, 63, 37,
         76, 28, 18, 40, 51, 58, 90, 35, 86, 26, 95, 43,  2, 87, 77, 97, 22,
         69, 52, 81, 93,  1, 21, 31, 98, 71, 82, 42, 11, 14, 30])),
 (9, array([ 5, 20,  9, 84,  7, 27, 38, 80,  3, 92, 43, 91, 94, 45, 66, 16, 95,
         44, 62, 78, 88, 25, 48, 68, 32, 82, 59, 34, 51, 85, 64, 24, 28, 69,
         99, 79, 76, 13, 14, 35, 57, 75, 37, 36, 19, 71,  8, 97, 60, 98, 10,
         55, 46, 61, 56, 33, 31, 26, 67, 23, 39,  4, 17, 47, 89, 40, 90, 50,
         49, 15, 53, 22, 54, 42, 30, 18, 96, 41, 65,  1, 73, 93,  2, 72, 81,
         83, 21, 70, 86, 29, 52,  6, 12, 87, 58, 63, 77, 74, 11])),
 (17,
  array([38, 82,  5, 95, 88, 79, 28, 80,  9,  7, 43, 91, 94, 51, 20, 66, 23,
         44,  3, 61, 57, 16, 31, 99, 46, 35, 33, 30, 92, 98, 14,  8, 49, 55,
         39, 29, 74, 45, 27, 19, 22, 89, 13, 12, 32, 71, 34, 56, 97, 15, 53,
         50, 78, 87, 65, 36,  1, 26, 85, 84, 81, 67, 90, 68, 42, 69, 70, 93,
          2, 48, 18, 47, 41, 83, 59, 11, 73, 63, 75, 60, 72, 24, 52, 21, 86,
         10, 77, 76, 62, 96,  4,  6, 25, 40, 58, 17, 37, 64, 54])),
 (25,
  array([94, 31, 51, 92,  9, 82, 34, 44, 95, 28, 30, 38, 45,  3, 62, 16, 68,
         88,  5, 76, 43, 14, 20, 69, 22,  7, 27, 48, 89, 17, 71,  8, 33, 84,
         29, 90, 78, 15, 96, 23, 91, 55, 13, 32, 97, 75, 66, 98, 80, 39, 36,
         59, 57, 61, 85,  6, 10, 79, 19, 99, 72,  4,  1, 40, 35, 21, 42, 26,
         60, 70,  2, 63, 67, 58, 47, 25, 50, 65, 18, 77, 86, 64, 87, 81, 12,
         41, 52, 37, 74, 73, 53, 83, 24, 46, 56, 49, 54, 93, 11])),
 (2, array([76, 69, 31,  6, 84, 54, 68, 64, 53, 97, 24, 41, 92, 27, 25,  4, 71,
         93, 62, 59,  5, 34, 16, 89, 20, 91, 98, 21, 29, 13, 85, 12, 60, 88,
         42, 96,  3, 48, 35, 70, 32,  7, 56, 19, 94, 36, 10, 50, 44, 81, 78,
         52, 66, 45,  9, 72, 43, 15, 75, 63, 37, 49, 11, 17, 83, 79, 28, 22,
         23, 95, 40, 51, 74, 61, 18, 47, 90, 99,  8, 65, 77, 33,  2, 30, 57,
         86, 58, 80, 87, 55, 67, 46,  1, 38, 26, 82, 39, 73, 14])),
 (10,
  array([84, 27, 20, 25, 64, 32, 53,  5, 91, 68,  7, 69, 24, 66, 75, 99, 50,
         76, 19, 95,  3, 48, 54, 15, 85, 41, 67, 36, 92,  6, 88,  9, 94, 35,
          8, 97, 93, 78, 18, 55, 62, 16, 37, 57, 56, 60, 31, 12, 96, 23, 82,
         80, 34, 45, 59, 71,  4, 72, 70, 49, 79, 43, 83, 38, 87, 89, 51, 33,
         98, 28, 44, 61, 26, 52, 63, 74, 86,  2, 21, 46, 81, 17, 90, 39, 58,
         65, 11,  1, 47, 42, 40, 22, 29, 13, 10, 77, 30, 73, 14])),
 (18,
  array([93, 56, 41, 24, 53, 11, 35, 69, 88, 38, 54, 79, 49, 83, 97, 65, 64,
         25,  3, 16, 27, 76, 52,  6, 36,  5, 50, 91, 46, 13, 47, 14, 37, 39,
         85, 43, 84, 81, 95, 44, 74, 28, 98, 33, 32, 20, 77, 42, 61, 17, 21,
         45, 82, 34,  7, 96, 30, 66, 63, 18, 89,  8, 71,  9, 68, 31, 86, 92,
         80, 23, 26, 73, 59,  4, 57, 19, 75, 90, 70, 12, 67,  2, 22, 87,  1,
         99, 48, 60, 51, 55, 78, 15, 72, 58, 10, 29, 40, 62, 94])),
 (26,
  array([79, 12, 91, 88, 61, 83, 23, 53,  5, 57, 20, 46, 38, 67, 66, 29,  8,
         49, 74, 80, 99, 28, 84, 87,  7, 19, 82, 89, 39, 33, 55, 16, 70, 68,
         81,  4, 31, 36,  9,  3, 73, 75, 22, 25, 32, 85, 95, 15, 59, 43,  6,
         63,  2, 44, 27, 65, 13, 41, 56, 60, 54, 42, 24, 26, 76, 35, 86,  1,
         92, 72, 98, 52, 78, 21, 51, 94, 71, 69, 77, 64, 93, 62, 50, 47, 11,
         40, 97, 10, 96, 30, 18, 58, 90, 37, 48, 45, 34, 14, 17])),
 (3, array([29, 12,  5, 88, 79, 68, 91, 98, 43, 49,  4, 31, 84, 89, 46,  7, 80,
         61, 59, 16, 42, 20, 38, 94, 44, 83, 76, 81, 99, 10, 62, 23,  6, 97,
         74, 22, 66, 92, 19, 70, 28, 13,  8, 53, 73, 24,  3, 85, 60, 77, 67,
         57,  9, 95, 96, 82, 69, 87, 40, 72, 50, 21, 75, 34, 63, 51, 35, 39,
         47, 36, 25, 78, 41, 64, 33, 90,  1, 18, 55, 48, 45,  2, 54, 17, 30,
         15, 27, 32, 65, 86, 11, 71, 58, 26, 52, 37, 14, 56, 93])),
 (11,
  array([ 5, 43,  7, 98, 24, 38, 42, 81, 20, 44, 46, 16, 49, 97, 11, 80,  4,
         91, 29, 88, 35,  3, 66, 95, 99, 77, 64, 89, 61, 82, 31, 22,  9, 28,
         14, 79, 37, 54, 12, 59, 93, 33,  8, 84, 56, 60, 85, 25, 50, 41,  6,
         18, 55, 15, 19, 10,  1, 65, 52, 73, 90, 92, 70, 21, 39, 72, 30, 47,
         76, 57, 23, 45, 87, 69, 86, 51,  2, 96, 13, 74, 34, 48, 26, 53, 17,
         32, 68, 27, 63, 40, 67, 58, 71, 78, 83, 94, 62, 75, 36])),
 (19,
  array([38, 88, 39, 79, 83, 36, 91, 68, 61, 28, 23, 56, 59, 33, 73, 12, 57,
         74, 82, 80, 71,  8, 99, 30,  5, 94, 22, 47, 53, 46, 65, 85, 66, 89,
         13, 14, 92, 16, 75, 51, 20, 67, 55, 93, 49, 78, 41,  7, 84, 70, 32,
         62, 44, 10, 43, 96, 81, 63,  9, 95, 42,  3, 87, 19, 77, 15, 72, 31,
         69,  6, 64, 40, 26, 52, 27, 34, 45, 25, 11, 24, 29, 86,  2,  1, 76,
         35, 60, 37, 48, 54, 50, 90, 98, 21,  4, 58, 17, 97, 18])),
 (27,
  array([ 5, 49, 95, 43, 50, 35, 84,  7, 98, 18, 27, 46, 79,  3, 88, 80, 20,
         53, 99, 97, 24, 41, 82, 32, 16, 66, 44, 25, 19,  9, 12, 68, 29, 74,
         11,  4, 91, 89, 38, 93, 75, 48, 56, 92, 31, 63, 45,  6, 64, 96, 70,
         28, 76, 94, 13, 23, 85, 67, 69, 65, 42, 17, 87, 60, 81, 78, 90, 51,
         21, 34, 26, 86, 72,  8,  1, 59, 47, 22, 36, 15, 83, 77,  2, 52, 37,
         61, 54, 10, 73, 58, 57, 55, 14, 62, 40, 33, 30, 39, 71])),
 (4, array([84, 68, 59, 85, 17, 27, 60, 34, 12, 62, 96, 48, 36, 71,  5, 75, 76,
          6, 45, 10, 78, 19, 46, 61, 49, 80, 20, 40, 64, 73, 25, 83, 89, 81,
         47, 29, 99, 67, 53, 13, 69, 50, 79, 97, 42, 32,  8, 88, 92, 24, 94,
         70,  4, 77, 91, 55, 98, 74,  7, 31, 72, 41, 58, 90, 15, 54, 52, 23,
         39, 63, 56,  2, 86,  3, 22, 38, 33, 57, 28, 26, 21,  1, 51, 18, 65,
         66, 44, 95, 93, 11, 87, 16, 37, 35, 82, 43, 30,  9, 14])),
 (12,
  array([43,  9, 16, 44, 38,  5,  3,  7, 88, 92, 14, 94, 91, 31, 20, 51, 98,
         35, 30, 82, 24, 95, 28, 66, 97,  4, 69, 22, 42, 45, 80, 37, 89, 79,
         39,  8, 41, 76, 33, 34, 32, 25, 13, 64, 29, 90, 56, 93, 57, 99, 23,
         84, 65, 11, 54, 27, 81, 61, 68,  1, 48, 78, 21, 55, 26, 15, 18, 77,
         46, 62, 83, 19, 75, 72,  6, 17, 36, 47, 10, 70, 53, 96, 50, 67, 49,
          2, 87, 63, 52, 86, 85, 59, 73, 58, 40, 74, 12, 71, 60])),
 (20,
  array([79, 49, 88, 74, 12, 80, 46, 38, 83, 73, 13, 61,  5, 59, 68, 91, 23,
         53, 29, 47, 36, 65, 10, 28, 84, 19, 70, 99, 39, 57, 89, 43, 82, 66,
         56, 95,  8, 67, 41, 50, 77, 63, 16, 87, 75, 35, 20, 15, 85,  7, 33,
         22, 78, 55,  9, 98, 62, 72, 93, 40, 11, 60, 64,  4, 71, 76,  6, 86,
          3, 30, 44, 21,  2, 31, 32, 14, 26, 52, 94, 25, 92,  1, 18, 24, 81,
         69, 51, 97, 54, 96, 58, 27, 42, 90, 37, 48, 45, 34, 17])),
 (28,
  array([64, 24, 54, 20, 25,  5,  7, 37, 43, 16, 69, 91, 84,  4, 76, 98, 97,
         93, 41, 42,  9, 66,  3, 56, 53, 11, 32, 92, 35, 27, 44, 81, 15, 38,
          6, 80, 59,  8, 85, 88, 65, 19, 99, 48, 68, 52, 21, 72, 70, 67, 46,
         62, 31, 33, 49, 55, 47, 83, 75, 89, 77, 18, 50, 78, 60, 14, 39, 13,
         86, 10, 57, 26,  2, 36, 22, 90, 87, 58, 40,  1, 45, 61, 96, 71, 63,
         73, 95, 29, 30, 34, 12, 28, 51, 74, 23, 79, 94, 17, 82])),
 (5, array([38,  5, 20, 61, 91, 80,  7, 57,  8, 55, 88, 15, 79, 33, 28, 66, 46,
         84, 82, 23, 12, 85, 59, 19, 64, 67, 71, 81, 95, 44, 99,  9, 16,  3,
         43, 51, 39, 60, 13, 29, 94, 73, 27, 31, 24, 98, 53, 83, 25, 49, 70,
         42, 74, 47, 65, 78, 75, 10, 89, 32, 87, 92, 77, 56, 36, 72, 68, 30,
          2, 97, 48, 52, 45,  1, 22, 14, 62, 26, 34, 40,  4,  6, 90, 69, 76,
         86, 35, 37, 21, 58, 96, 54, 11, 93, 63, 41, 50, 17, 18])),
 (13,
  array([68, 84, 49, 59, 27, 50, 36, 12, 85, 75, 53, 79,  5, 74,  6, 88, 96,
         76, 99, 19, 95, 32, 41, 48, 46, 80, 62, 25, 60, 89, 34, 94, 83, 64,
         17, 10, 47, 71, 13, 78, 29, 69, 70, 18, 63, 97, 98, 73, 31, 20, 92,
         82, 72, 38, 15, 35, 45, 67, 23, 24, 56, 61,  7, 65, 40,  3, 28, 66,
          4, 43, 93, 91, 39,  8, 86, 77, 21, 58, 22, 81, 52, 11,  2, 90, 51,
         55, 26, 87, 42, 54, 44,  1, 16, 57, 33,  9, 37, 30, 14])),
 (21,
  array([68, 59, 84, 36, 62, 94, 34, 85, 27, 13, 75, 10, 76, 48, 92, 78, 71,
         45, 17, 96, 47, 73, 69, 88, 40, 80, 39, 64, 83, 60,  6, 25, 32, 79,
         89, 38, 19,  5, 50, 51, 41, 56, 74, 49, 99, 31, 65, 22, 12, 63,  3,
         28, 72, 70, 30, 90, 95, 20, 14,  9, 46, 53, 77, 67, 26, 16, 44, 97,
          8, 58, 61, 54, 37, 15, 43, 86, 33,  4, 21, 24,  2, 29, 91, 82,  1,
         55, 52, 23, 93, 66, 35, 18, 42, 98, 57,  7, 81, 87, 11])),
 (29,
  array([ 5, 38, 91, 88,  7, 20, 43, 16, 61, 79, 44, 28, 80, 98, 42, 66,  9,
          3, 29, 81, 57, 12, 82, 31, 23, 46,  8, 33, 39, 24, 92, 89, 51, 22,
          4, 94, 83, 99, 84, 97, 95, 30, 55, 85, 14, 59, 45, 68, 67, 49, 35,
         34, 76, 69, 19, 53, 87, 71, 77, 13, 11, 56,  6, 60, 73, 78, 62, 36,
          1, 90, 10, 25, 70, 64, 15, 27, 37, 65, 96, 32, 26,  2, 47, 48, 54,
         41, 52, 74, 21, 17, 72, 75, 93, 40, 86, 63, 58, 50, 18])),
 (6, array([17, 34, 45, 68, 84, 27, 85, 96, 48, 59, 75, 94, 36, 92,  3, 62, 78,
         51, 60, 76,  5, 44, 90,  6, 97, 89, 31, 50, 69, 71, 22, 32, 10, 41,
         35, 40, 88, 42, 98, 95, 13, 16, 19, 29, 12, 18, 25, 47, 28, 20, 56,
         67,  4, 81, 26, 93, 83, 99, 14,  9, 80,  1, 82, 58, 49,  7,  8, 61,
         24, 79, 73, 77, 43, 30, 63, 46, 39,  2, 53, 52, 86, 21, 38, 33, 72,
         11, 23, 91, 55, 37, 65, 70, 64, 57, 66, 54, 87, 74, 15])),
 (14,
  array([59, 68, 62, 84, 10, 13, 71, 36, 85, 78, 94, 80, 34, 76, 48, 73, 40,
         92, 45, 60, 47, 27, 75,  5, 64, 17, 96, 69, 19, 25, 20, 89, 38, 46,
          6, 79, 39, 49, 32, 51, 99, 74, 12, 72, 70,  9, 83, 61, 88, 31, 77,
         65, 43, 28, 37, 26, 58,  3, 22, 29, 15, 90, 54, 14, 50,  4, 44, 55,
         63, 66, 95, 30, 16, 67,  8, 97, 21,  7, 91,  2, 33, 86, 42,  1, 57,
         56, 24, 98, 18, 41, 52, 23, 82, 81, 53, 35, 87, 93, 11])),
 (22,
  array([12, 80, 49, 79, 46, 59,  5, 84, 73, 61, 13, 68, 74, 10, 29, 88, 83,
         85, 19, 60, 38, 47, 91, 20, 62, 67, 23, 57, 89, 99, 53, 70, 40,  8,
         77, 78, 36, 71, 75, 66, 65, 28,  7, 43, 81, 55, 76, 50, 98,  4, 95,
         27, 48,  6, 64, 96, 56, 15, 87, 82, 63, 45, 34,  2, 33, 25, 16, 42,
         26, 97, 86, 18, 72,  3,  1, 21, 24, 17, 44, 52, 39, 58, 41, 35, 22,
         11, 90, 92, 31, 69, 54, 32,  9, 51, 94, 93, 14, 37, 30])),
 (7, array([68, 94, 84, 62, 76, 92, 36, 59, 75, 27, 31, 69, 64, 34, 32, 48, 96,
         25,  6, 10, 89, 78, 88, 85, 13, 50, 45,  9, 17, 51, 83, 39,  4, 40,
         72, 16, 19, 22, 99, 41, 47, 70, 63, 71,  3, 20, 43, 53, 44,  5, 54,
         97, 29, 12, 74, 15, 30, 60, 95, 37, 73, 90, 80, 91, 79, 67,  8, 21,
         28, 98, 58, 66,  7, 18, 24, 38, 65, 49, 86,  2, 42, 26, 23, 35, 14,
         77, 82,  1, 33, 55, 52, 87, 61, 57, 56, 46, 81, 93, 11])),
 (15,
  array([38, 14, 88, 39,  9, 30, 43, 16, 44, 56, 94, 51, 28, 79, 92, 36,  3,
         13, 82, 65, 83, 80, 91, 73, 22, 33, 47, 35, 93, 37,  8,  5,  7, 41,
         95, 45, 68, 66, 59, 20, 64, 24, 61, 57, 31, 10, 89, 75, 77, 90, 69,
         32, 78, 23, 34, 11, 15, 55, 26, 98, 42, 74,  1, 99, 46, 25, 72, 63,
         52, 70, 97, 85, 67, 96, 86,  2, 54,  4, 48, 62, 49, 40, 71, 19, 17,
         21, 81, 76, 87, 58, 84, 50, 53, 27, 29,  6, 18, 12, 60])),
 (23,
  array([ 5, 43,  7, 20, 38, 91,  9, 80, 66, 16, 44, 88, 98, 95,  3, 82, 24,
         64, 94, 99, 46, 28, 79, 92, 57,  8, 61, 14,  4, 15, 25, 42, 55, 84,
         37, 35, 51, 33, 32, 23, 49, 97, 19, 31, 81, 29, 39, 13, 89, 30, 22,
         62, 67, 78, 10, 27, 70, 65, 59, 87, 54, 72, 26, 69,  1, 45, 12, 18,
         74, 48, 73, 11, 77, 47, 76, 85, 90,  2, 53, 21, 50, 60, 56, 86, 75,
         68, 40, 52, 71, 83, 41, 63, 36, 58, 34,  6, 93, 96, 17]))]

In [ ]: