public IDMultisetItemList PotentialFriendsParallel(SubscriberID id, int maxCandidates)
        {
            object     locker     = new object();
            IDMultiset candidates = new IDMultiset();

            Parallel.ForEach(subscribers[id].Friends,                             // Map over friends
                             () => Multiset.Create(new HashSet <SubscriberID>()), // init thread-local state localFoafs with empty Multiset
                             (friend, loopState, localFoafs) =>
            {
                var foafs = subscribers[friend].FriendsCopy();
                foafs.RemoveWhere(foaf => foaf == id || subscribers[id].Friends.Contains(foaf));                    // remove self, own friends
                return(Multiset.Union(localFoafs, Multiset.Create(foafs)));                                         // Reduce, thread-local
            },
                             localFoafs => { lock (locker) candidates = Multiset.Union(localFoafs, candidates); }); // Reduce, among threads
            return(Multiset.MostNumerous(candidates, maxCandidates));                                               // postprocess results of Reduce
        }
        public IDMultisetItemList PotentialFriendsSequential(SubscriberID id, int maxCandidates)
        {
            // Map
            var foafsList = new List <IDMultiset>();

            foreach (SubscriberID friend in subscribers[id].Friends)
            {
                var foafs = subscribers[friend].FriendsCopy();
                foafs.RemoveWhere(foaf => foaf == id || subscribers[id].Friends.Contains(foaf)); // remove self, own friends
                foafsList.Add(Multiset.Create(foafs));
            }

            // Reduce
            IDMultiset candidates = new IDMultiset();

            foreach (IDMultiset foafs in foafsList)
            {
                candidates = Multiset.Union(foafs, candidates);
            }

            // Postprocess
            return(Multiset.MostNumerous(candidates, maxCandidates));
        }