Beispiel #1
0
        // The version of the search loop for Sets with no Relations.
        internal bool TryCombinationsWithoutRelations(GroupSearchData searchData, PreviousSetMatches previousMatches)
        {
            var done           = false;
            var duplicateCount = 0;

            if (previousMatches == null || previousMatches.Count == 0)
            {
                do
                {
                    var hypothesis = m_MetaEnumerator.Current;
                    // make sure we don't have any contexts that are matches for more than one query that claims data
                    if (AnyDuplicateOrInvalid(hypothesis))
                    {
                        duplicateCount++;
                        continue;
                    }

                    // Since we have no Relations, our Set rating is derived entirely from member ratings
                    searchData.MatchBuffer.Add(hypothesis, AverageMemberRating(hypothesis));

                    // If our very first attempt has no duplicates, it's *guaranteed* to be the best possible match.
                    // This is because we are iterating through member ratings in order of descending rating.
                    if (duplicateCount == 0)
                    {
                        done = true;
                    }
                    // If we had any duplicates before our first valid match, we don't have that guarantee,
                    // because some other alternate combination may be better, so we should continue iterating
                }while (m_MetaEnumerator.MoveNext() && !done);
            }
            else
            {
                do
                {
                    var hypothesis = m_MetaEnumerator.Current;
                    // the first hypothesis being used by a previous match has the same effect on whether
                    // it is guaranteed to be our best possible match as if it had duplicates within itself
                    if (AnyDuplicateOrInvalid(hypothesis) || previousMatches.AssignmentPreviouslyUsed(hypothesis))
                    {
                        duplicateCount++;
                        continue;
                    }

                    searchData.MatchBuffer.Add(hypothesis, AverageMemberRating(hypothesis));
                    if (duplicateCount == 0)
                    {
                        done = true;
                    }
                }while (m_MetaEnumerator.MoveNext() && !done);
            }

            return(searchData.MatchBuffer.Count > 0);
        }
Beispiel #2
0
        /// <summary>
        /// Choose the best match we found while searching that hasn't been previously used by this set.
        /// </summary>
        /// <param name="buffer">The buffer containing every valid match found during the search</param>
        /// <param name="previous">A record of what previous matches for this set have used</param>
        /// <param name="hasRelations">Whether this Set has any Relations or not</param>
        /// <returns>The index into the match buffer where our chosen match is</returns>
        int BestAvailableMatch(SetMatchesBuffer buffer, PreviousSetMatches previous, bool hasRelations)
        {
            var chosenIndex = 0;

            if (hasRelations)
            {
                var tiesLeft = 0;
                var tieCount = 0;
                var tieIndex = 0;
                // for sets with Relations, we filter out duplicates and unavailable data
                // only for the highest-rated combinations we want to use, instead of for every combination
                do
                {
                    if (tiesLeft == 0)
                    {
                        // start by finding all matches that tie for best, then try choosing between them.
                        // if we run out of ties for the current best, we evaluate all the ties for the next best
                        var ratingToCount = buffer.FindHighestRated(ref m_RatingTieIndices);
                        // if we've looped until we discover all matches are unavailable, return -1 to indicate failure
                        if (ratingToCount.Key == 0f)
                        {
                            return(-1);
                        }

                        tieCount = ratingToCount.Value;
                        tiesLeft = ratingToCount.Value;
                        tieIndex = ChoiceBehavior == TieChoiceBehavior.First ? 0
                            : UnityEngine.Random.Range(0, tiesLeft - 1);
                    }
                    else
                    {
                        tiesLeft--;
                        tieIndex = tieIndex == tieCount - 1 ? 0 : tieIndex + 1;
                    }

                    chosenIndex = m_RatingTieIndices[tieIndex];
                }while (!previous.IsCombinationAvailable(buffer, chosenIndex));
            }
            else
            {
                // matches for sets without relations have already been filtered for availability and duplicates
                var chosenKvp = buffer.ChooseHighestRated(m_RatingTieIndices, ChoiceBehavior);
                chosenIndex = chosenKvp.Key;
            }

            return(chosenIndex);
        }
Beispiel #3
0
        void ProcessStage(List <int> workingIndices, List <int> outputIndices,
                          int[][] allSetMemberIndices,
                          RelationDataPair[][] allLocalRelationIndexPairs,
                          RelationRatingsData[] allRelationRatings,
                          float[] allSetOrderWeights,
                          GroupSearchData[] allGroupSearchData,
                          RelationMembership[][] allRelationMemberships,
                          Exclusivity[] allMemberExclusivities,
                          Dictionary <int, float>[] allMemberRatings,
                          ref int[] allMemberAssignments)
        {
            if (workingIndices.Count == 0)
            {
                IsComplete = true;
                return;
            }

            if (m_TickCount == 0)
            {
                outputIndices.Clear();
                m_MemberAssignments.Clear();
                // Determine the order we are going to answer Groups.
                CalculateSolveOrder(allSetOrderWeights, AllGroupPriorities, workingIndices, m_AllOrderPairs);
                var setCount = m_AllOrderPairs.Count;
                m_SetsPerTick = Mathf.Clamp(Mathf.CeilToInt(setCount / (float)FrameBudget), 1, setCount);
            }

            // if we need to force completion, setting the end index to the last one will force all groups to solve
            var endIndex = NeedsCompletion ? m_AllOrderPairs.Count
                : Mathf.Clamp(m_SolveIndex + m_SetsPerTick, m_SolveIndex, m_AllOrderPairs.Count);

            for (var solveIndex = m_SolveIndex; solveIndex < endIndex; solveIndex++)
            {
                var kvp             = m_AllOrderPairs[solveIndex];
                var setIndex        = kvp.Key;
                var queryId         = SetMatchIds[setIndex].queryID;
                var memberIndices   = allSetMemberIndices[setIndex];
                var localPairs      = allLocalRelationIndexPairs[setIndex];
                var relationRatings = allRelationRatings[setIndex];
                var searchData      = allGroupSearchData[setIndex];
                var matchBuffer     = searchData.MatchBuffer;
                matchBuffer.Reset();

                // determine how many options we should try for each member before stopping
                var hasRelations    = localPairs.Length > 0;
                var searchSpaceSize = GetGroupSearchSpaceSize(memberIndices, allMemberRatings);
                // there's no options available for this group, skip it.
                if (searchSpaceSize == 0)
                {
                    continue;
                }

                var portion = GetSearchPortion(searchSpaceSize, SearchSpacePortionCurve, hasRelations);

                GetIterationTargets(portion, searchData.MemberRatings, searchData.MetaEnumerator.TargetDepths);

                m_MetaEnumerator = searchData.MetaEnumerator;
                m_MetaEnumerator.RefreshEnumerators();
                m_MetaEnumerator.Reset();
                m_MetaEnumerator.MoveNext(); // get enumerator into initial valid state

                if (!PreviousMatches.TryGetValue(queryId, out var record))
                {
                    record = new PreviousSetMatches(memberIndices.Length);
                    PreviousMatches.Add(queryId, record);
                }

                /*  The critical loop where we search the set's combination space happens here, in either function.
                 *  There are 2 different versions of our inner combination loop.
                 *
                 *  1) for Sets with at least one Relation, we filter during the loop only based on whether
                 *     the combination satisfies all Relations. We only do duplicate assignment &
                 *     data availability checks later, when picking the best match.
                 *
                 *  2) for Sets with no Relations, we check duplicate assignment & previous use during the loop,
                 *     and anything that passes those checks is valid.
                 */
                var matchFound = hasRelations ? TryCombinations(searchData.MatchBuffer, relationRatings, localPairs)
                                              : TryCombinationsWithoutRelations(searchData, record);

                // if we didn't find any matches for this set during our search, give up and move on.
                if (!matchFound)
                {
                    continue;
                }

                // if we did find matches, find the highest rated one and do any picking between ties necessary
                var chosenIndex = BestAvailableMatch(matchBuffer, record, hasRelations);
                if (chosenIndex < 0)
                {
                    continue;
                }

                // take our chosen match and assign its keys to set members in a staging collection -
                // this isn't final assignment, since we might have to roll back later.
                var begin = chosenIndex * matchBuffer.SetSize;
                var end   = begin + matchBuffer.SetSize;
                for (var i = begin; i < end; i++)
                {
                    var assignedDataId    = matchBuffer.DataIds[i];
                    var globalMemberIndex = memberIndices[i - begin];
                    m_MemberAssignments[globalMemberIndex] = assignedDataId;
                }

                matchBuffer.CombinationAtRatingIndex(chosenIndex, searchData.ClaimedCombination);

                // the following stages - marking data used & filling results - will use these as their working indices
                if (!outputIndices.Contains(setIndex))
                {
                    outputIndices.Add(setIndex);
                }

                // if this is anything except the last Set to be solved in the cycle,
                // we need to remove the appropriate possibilities from the remaining sets
                if (solveIndex >= m_AllOrderPairs.Count - 1)
                {
                    continue;
                }

                for (var i = solveIndex + 1; i < m_AllOrderPairs.Count; i++)
                {
                    var otherSetIndex = m_AllOrderPairs[i].Key;
                    RemoveClaimedData(searchData.ClaimedCombination, memberIndices,
                                      allSetMemberIndices[otherSetIndex], allMemberExclusivities, allMemberRatings);
                }
            }

            m_SolveIndex = endIndex;
            m_TickCount++;

            if (endIndex == m_AllOrderPairs.Count)
            {
                // finalize our assignments
                foreach (var kvp in m_MemberAssignments)
                {
                    allMemberAssignments[kvp.Key] = kvp.Value;
                }

                IsComplete = true;
            }
        }