<commit_before>import ObjectiveAppraiser from 'src/server/services/projectFormationService/ObjectiveAppraiser'
import UnpopularGoalsNotConsideredAppraiser from 'src/server/services/projectFormationService/ObjectiveAppraiser/UnpopularGoalsNotConsideredAppraiser'

import {
  getGoalsWithVotes,
  getTeamSizesByGoal,
  getPoolSize,
  getAdvancedPlayerCount,
  getMinTeamSize,
} from '../../pool'

import {range, unique} from '../../util'

export default function * ennumerateGoalChoices(pool, teamFormationPlan = {}, shouldPrune, appraiser = new ObjectiveAppraiser(pool)) {
  const goalAndSizeCombinations = getGoalAndSizeCombinations(pool)
  const poolSize = getPoolSize(pool)
  const advancedPlayerCount = getAdvancedPlayerCount(pool)
  const extraSeatScenarios = getValidExtraSeatCountScenarios(pool)

  const teamOptions = goalAndSizeCombinations.map(option => ({playerIds: [], ...option}))
  const nodeStack = teamOptions.map(option => ({
    ...teamFormationPlan,
    seatCount: poolSize,
    teams: [option]
  }))
  sortByScore(nodeStack, appraiser)

  /* eslint-disable no-labels */
  OUTER: for (;;) {
    const currentNode = nodeStack.pop()

    if (!currentNode) {
      return
    }

    if (shouldPrune && shouldPrune(currentNode)) {
      continue
    }
    delete currentNode._score

    const currentTeams = currentNode.teams
    const currentSeatCount = currentTeams.reduce((sum, team) => sum + team.teamSize, 0)
    const targetSeatCount = currentNode.seatCount
    for (const extraSeatCount of extraSeatScenarios) {
      const seatCountAppropriateForExtraSeats = currentSeatCount === (targetSeatCount + extraSeatCount)
      const teamCountAppropriateForExtraSeats = currentTeams.length === (advancedPlayerCount + extraSeatCount)
      if (seatCountAppropriateForExtraSeats && teamCountAppropriateForExtraSeats) {
        yield {...currentNode, seatCount: currentSeatCount}
        continue OUTER
      }
    }

    const teamOptionsNotLargerThanRemainingSpace = teamOptions
      .filter(option =>
        extraSeatScenarios.some(extraSeatCount => {
          const newTeamCapacity = currentSeatCount + option.teamSize
          return poolSize + extraSeatCount >= newTeamCapacity
        })
      )

    const newNodes = teamOptionsNotLargerThanRemainingSpace
      // Skip all nodes that are not sorted to ensure no duplicates
      .filter(option => compareGoals(option, currentTeams[currentTeams.length - 1]) >= 0)
      .map(option => ({
        ...currentNode,
        teams: currentTeams.concat(option)
      }))

    // Sort by score so we visit the most promising nodes first.
    nodeStack.push(...sortByScore(newNodes, appraiser))
  }
}

function sortByScore(teamFormationPlans, appraiser) {
  return teamFormationPlans
    .map(plan => ({...plan, _score: appraiser.score(plan)}))
    .sort((a, b) => a._score - b._score)
}

function getGoalAndSizeCombinations(pool) {
  const teamSizesByGoal = getTeamSizesByGoal(pool)
  const goals = getGoalsWithVotes(pool)
  const minTeamSize = getMinTeamSize(pool)

  const unpopularGoalAppraiser = new UnpopularGoalsNotConsideredAppraiser(pool)
  const popularGoalDescriptors = unpopularGoalAppraiser.popularGoals()

  return goals.reduce((result, goalDescriptor) => {
    if (!popularGoalDescriptors.has(goalDescriptor)) {
      return result
    }

    const options = [
      {goalDescriptor, teamSize: teamSizesByGoal[goalDescriptor], matchesTeamSizeRecommendation: true},
      {goalDescriptor, teamSize: teamSizesByGoal[goalDescriptor] + 1},
    ]
    if (teamSizesByGoal[goalDescriptor] > minTeamSize) {
      options.push({goalDescriptor, teamSize: teamSizesByGoal[goalDescriptor] - 1})
    }

    return result.concat(options)
  }, [])
}

function getValidExtraSeatCountScenarios(pool) {
  // When advanced players are on multiple teams it creates
  // a scenario where we need extra seats on the teams since
  // one player is using multiple seats.
  //
  // Given a certain pool size and smallest/largest possible project sizes
  // there are a limited muber of valid extra seat configurations.
  // This method returns a list of those configurations represented by the
  // number of additional seats.
  const poolSize = getPoolSize(pool)
  const goalAndSizeCombinations = getGoalAndSizeCombinations(pool)
  const advancedPlayerCount = getAdvancedPlayerCount(pool)
  const nonAdvancedPlayerCount = poolSize - advancedPlayerCount

  const sizeOptions = unique(goalAndSizeCombinations.map(_ => _.teamSize)).sort()
  const smallestGoalSizeOption = sizeOptions[0]
  const largestGoalSizeOption = sizeOptions[sizeOptions.length - 1]

  const nonAdvancedSeatsOnLargestGoalSizeOption = largestGoalSizeOption - 1
  const minTeams = Math.floor(nonAdvancedPlayerCount / nonAdvancedSeatsOnLargestGoalSizeOption)
  const minExtraSeats = Math.max(0, minTeams - advancedPlayerCount)

  const nonAdvancedSeatsOnSmallestGoalSizeOption = smallestGoalSizeOption - 1
  const maxTeams = Math.floor(nonAdvancedPlayerCount / nonAdvancedSeatsOnSmallestGoalSizeOption)
  const maxExtraSeats = Math.max(0, maxTeams - advancedPlayerCount)

  return range(minExtraSeats, maxExtraSeats + 1)
}

function compareGoals(a, b) {
  if (a && !b) {
    return 1
  }
  if (b && !a) {
    return -1
  }
  if (a.goalDescriptor > b.goalDescriptor) {<commit_msg>rolling back refactoring that had negative performance implications<commit_after>import ObjectiveAppraiser from 'src/server/services/projectFormationService/ObjectiveAppraiser'
import UnpopularGoalsNotConsideredAppraiser from 'src/server/services/projectFormationService/ObjectiveAppraiser/UnpopularGoalsNotConsideredAppraiser'

import {
  getGoalsWithVotes,
  getTeamSizesByGoal,
  getPoolSize,
  getAdvancedPlayerCount,
  getMinTeamSize,
} from 'src/server/services/projectFormationService/pool'

import {range} from 'src/server/services/projectFormationService/util'

export default function ennumerateGoalChoices(pool, teamFormationPlan = {}, shouldPrune) {
  const teamSizesByGoal = getTeamSizesByGoal(pool)
  const goals = getGoalsWithVotes(pool)
  const minTeamSize = getMinTeamSize(pool)

  const x = new UnpopularGoalsNotConsideredAppraiser(pool)
  const popularGoalDescriptors = x.popularGoals()

  const goalAndSizeOptions = goals.reduce((result, goalDescriptor) => {
    if (!popularGoalDescriptors.has(goalDescriptor)) {
      return result
    }

    const options = [
      {goalDescriptor, teamSize: teamSizesByGoal[goalDescriptor], matchesTeamSizeRecommendation: true},
      {goalDescriptor, teamSize: teamSizesByGoal[goalDescriptor] + 1},
    ]
    if (teamSizesByGoal[goalDescriptor] > minTeamSize) {
      options.push({goalDescriptor, teamSize: teamSizesByGoal[goalDescriptor] - 1})
    }

    return result.concat(options)
  }, [])

  const optionSizes = goalAndSizeOptions.map(_ => _.teamSize).sort()
  const smallestGoalSizeOption = optionSizes[0]
  const largestGoalSizeOption = optionSizes[optionSizes.length - 1]

  const poolSize = getPoolSize(pool)
  const advancedPlayerCount = getAdvancedPlayerCount(pool)

  const extraSeatScenarios = getValidExtraSeatCountScenarios({
    poolSize,
    smallestGoalSizeOption,
    largestGoalSizeOption,
    advancedPlayerCount,
  })

  const appraiser = new ObjectiveAppraiser(pool)

  return goalChoiceGenerator(teamFormationPlan, {
    goalAndSizeOptions,
    poolSize,
    advancedPlayerCount,
    extraSeatScenarios,
    shouldPrune,
    appraiser,
  })
}

function getValidExtraSeatCountScenarios({poolSize, smallestGoalSizeOption, largestGoalSizeOption, advancedPlayerCount}) {
  // When advanced players are on multiple teams it creates
  // a scenario where we need extra seats on the teams since
  // one player is using multiple seats.
  //
  // Given a certain pool size and smallest/largest possible project sizes
  // there are a limited muber of valid extra seat configurations.
  // This method returns a list of those configurations represented by the
  // number of additional seats.

  const nonAdvancedPlayerCount = poolSize - advancedPlayerCount

  const nonAdvancedSeatsOnLargestGoalSizeOption = largestGoalSizeOption - 1
  const minTeams = Math.floor(nonAdvancedPlayerCount / nonAdvancedSeatsOnLargestGoalSizeOption)
  const minExtraSeats = Math.max(0, minTeams - advancedPlayerCount)

  const nonAdvancedSeatsOnSmallestGoalSizeOption = smallestGoalSizeOption - 1
  const maxTeams = Math.floor(nonAdvancedPlayerCount / nonAdvancedSeatsOnSmallestGoalSizeOption)
  const maxExtraSeats = Math.max(0, maxTeams - advancedPlayerCount)

  return range(minExtraSeats, maxExtraSeats + 1)
}

function * goalChoiceGenerator(teamFormationPlan, {goalAndSizeOptions, poolSize, advancedPlayerCount, extraSeatScenarios, shouldPrune, appraiser}) {
  const teamOptions = goalAndSizeOptions.map(option => ({playerIds: [], ...option}))
  const nodeStack = teamOptions.map(option => ({
    ...teamFormationPlan,
    seatCount: poolSize,
    teams: [option]
  }))
  .map(teamFormationPlan => ({
    ...teamFormationPlan,
    _score: appraiser.score({...teamFormationPlan})
  }))
  .sort(({_score: a}, {_score: b}) => a - b)

  /* eslint-disable no-labels */
  OUTER: for (;;) {
    const currentNode = nodeStack.pop()

    if (!currentNode) {
      return
    }

    if (shouldPrune && shouldPrune(currentNode)) {
      continue
    }
    delete currentNode._score

    const currentTeams = currentNode.teams
    const currentSeatCount = currentTeams.reduce((sum, team) => sum + team.teamSize, 0)
    const targetSeatCount = currentNode.seatCount
    for (const extraSeatCount of extraSeatScenarios) {
      if ((currentSeatCount === (targetSeatCount + extraSeatCount)) && (currentTeams.length === (advancedPlayerCount + extraSeatCount))) {
        yield {...currentNode, seatCount: currentSeatCount}
        continue OUTER
      }
    }

    const newNodes = teamOptions
      .filter(option =>
        extraSeatScenarios.some(extraSeatCount => {
          const newTeamCapacity = currentSeatCount + option.teamSize
          return poolSize + extraSeatCount >= newTeamCapacity
        })
      )
      // Skipping all nodes that are not sorted to ensure that we won't
      // add children that will be duplicates of nodes further left in the tree
      .filter(option => compareGoals(option, currentTeams[currentTeams.length - 1]) >= 0)
      .map(option => ({
        ...currentNode,
        teams: currentTeams.concat(option)
      }))

    // Sort by score so we visit the most promising nodes first.
    const sortedNodes = newNodes
      .map(teamFormationPlan => ({
        ...teamFormationPlan,
        _score: appraiser.score({...teamFormationPlan})
      }))
      .sort(({_score: a}, {_score: b}) => a - b)

    nodeStack.push(...sortedNodes)
  }
}

function compareGoals(a, b) {
  if (a && !b) {
    return 1
  }
  if (b && !a) {
    return -1
  }
  if (a.goalDescriptor > b.goalDescriptor) {
{'fraction_non_alphanumeric': 0.09203751379183524, 'fraction_numerical': 0.0025744759102611253, 'mean_word_length': 4.120998116760829, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1798460', 'n_tokens_mistral': 3335, 'n_tokens_neox': 3192, 'n_words': 729}