How to dynamically generate possible encounters between teams?

Viewed 119

I appreciate your help in advance in this exercise in which the truth I have not discovered how to solve it

How to dynamically generate possible encounters between teams?

Having the following input fields

  • start date
  • teams
  • fields
  • days to play

With for example the following data

const startDate = "03-08-2020";
const teams = ["A", "B", "C", "D", "E", "F"];
const fields = ["Field1", "Field2"];
const daysToPlay = ["Monday", "Wednesday"];

Now and from this data I need to dynamically generate the game days with their game matching, the playing fields must be interchanged like the days from date to date. Starting from startDate value

An example of output is as follows

const output = [
    {
        number: 1,
        date: "03-08-2020", // Monday
        field: "Field1",
        matches: [
            ["A", "B"],
            ["C", "D"],
            ["E", "F"],
        ],
    },
    {
        number: 2,
        date: "05-08-2020", // Wednesday
        field: "Field2",
        matches: [
            ["A", "C"],
            ["B", "E"],
            ["C", "F"],
        ],
    },
];

In this way, according to the number of unique possible encounters between teams.

Update 0

  • All teams must play on each date
  • The number of teams is always even
  • The tournament ends when each team has played against all the others,

Update 1

I am following Oliver suggestion but in the last set of matches I am getting a single match, here I am expecting two matches like the previous ones

const teams = ["A", "B", "C", "D"];

const getCombinations = (data) => {
  let output = [];

  for (let i = 0; i < teams.length - 1; i++) {
    for (let j = i + 1; j < teams.length; j++) {
      output = [...output, [data[i], data[j]]];
    }
  }

  return output;
};

const getMatches = (data) => {
  let matches = [];
  let i = 0;

  while (data.length) {
    for (const [index, entry] of data.entries()) {
      if (index === 0) {
        matches.push([entry]);

        data.splice(index, 1);

        continue;
      }

      const [team1, team2] = entry;
      const idx = matches[i].findIndex(
        (value) => value.includes(team1) || value.includes(team2)
      );

      if (idx !== -1) {
        continue;
      }

      matches[i].push(entry);
      data.splice(index, 1);
    }

    i++;
  }

  return matches;
};

const combinations = getCombinations(teams);
const matches = getMatches(combinations);

console.log(matches);

Update 2

Fix to the previous update

const teams = ["A", "B", "C", "D"];

const getCombinations = (data) => {
  let output = [];

  for (let i = 0; i < teams.length - 1; i++) {
    for (let j = i + 1; j < teams.length; j++) {
      output = [...output, [data[i], data[j]]];
    }
  }

  return output;
};

const getMatches = (data) => {
  let matches = [];
  let i = 0;

  while (data.length) {
    for (const [index, entry] of data.entries()) {
      if (data.length === 1) {
        matches[i - 1].push(entry);
        data.splice(index, 1);
        break;
      }

      if (index === 0) {
        matches.push([entry]);

        data.splice(index, 1);
        continue;
      }

      const [team1, team2] = entry;
      const idx = matches[i].findIndex(
        (value) => value.includes(team1) || value.includes(team2)
      );

      if (idx !== -1) {
        continue;
      }

      matches[i].push(entry);
      data.splice(index, 1);
    }

    i++;
  }

  return matches;
};

const combinations = getCombinations(teams);

console.log(combinations);

const matches = getMatches(combinations);

console.log(matches);

Update 3

I am almost there

I am having problems to implement the task related to date, how do I get correct date. In order to make the solution easier, I have managed to change the input of game days by the number of the day of the week instead of the name of the day, in this way

Sunday 0
...
Saturday 6

In the example, the days correspond to Monday (1) and Wednesday (3)

I appreciate your comment

const startDate = "2020-08-03";
const matchDays = [1, 3];
const fields = ["Field 1", "Field 2"];
const teams = ["A", "B", "C", "D"];

const getCombinations = (data) => {
  let output = [];

  for (let i = 0; i < teams.length - 1; i++) {
    for (let j = i + 1; j < teams.length; j++) {
      output = [...output, [data[i], data[j]]];
    }
  }

  return output;
};

const getMatches = (data) => {
  let matches = [];
  let i = 0;

  while (data.length) {
    for (const [index, entry] of data.entries()) {
      if (data.length === 1) {
        matches[i - 1].push(entry);
        data.splice(index, 1);
        break;
      }

      if (index === 0) {
        matches.push([entry]);

        data.splice(index, 1);
        continue;
      }

      const [team1, team2] = entry;
      const idx = matches[i].findIndex(
        (value) => value.includes(team1) || value.includes(team2)
      );

      if (idx !== -1) {
        continue;
      }

      matches[i].push(entry);
      data.splice(index, 1);
    }

    i++;
  }

  return matches;
};

const getGameDays = (data) => {
  const options = {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  };

  return data.map((entry, index) => {
    return {
      number: index + 1,
      date:
        index === 0
          ? new Date(startDate).toLocaleDateString("es-ES", options)
          : new Date(startDate).toLocaleDateString("es-ES", options), // Here I need to move on every target day of week in matchDays
      field:
        fields.length === 1
          ? fields[0]
          : index === 0
          ? fields[0]
          : index % 2 === 0
          ? fields[0]
          : fields[1],
      matches: [...entry],
    };
  });
};

const combinations = getCombinations(teams);

console.log(combinations);

const matches = getMatches(combinations);

console.log(matches);

const gameDays = getGameDays(matches);

console.dir(gameDays, { depth: null, color: true });

At this time set the starting date on each day

Thank you

2 Answers

Seems to be a job for https://github.com/dankogai/js-combinatorics.

Here you have to create all combinations with size 2 (new Combination(teams, 2);). Now you got all combinations (if you have rematches, you can create them with new Combination(teams.reverse(), 2);.

Now hold an (empty) array of teamsPlayedToday, pick the first entry out of the combinations, add it to the day matches, the teams to the above mentioned array and remove it from the combinations list.

In the next step pick the next entry from the combinations, check if any team is mentioned in the teamsPlayedToday list and if yes skip over to the next combination, till you find one where no team is in the today list.

When you reach the end you have the plan for day 1. Repeat the steps above for every day till your list of combinations is empty.

From the given start date, put into new Date() and the method getDay() you should be able to find out the value for the startDate.

If anything of this sketch doesn't work, please ask a new question with your concrete code and what you expect and what you got.

Update for getting valid dates

To get from the given start date and allowed weekdays the list of days to play you could use something like this:

// Taken from https://stackoverflow.com/a/19691491/1838048
function addDays(date, days) {
  var result = new Date(date);
  result.setDate(result.getDate() + days);
  return result;
}

// Proof-of-concept without argument checks (could lead to endless loops!)
function getMatchingDays(startDate, allowedDays, maxCount) {
  const matchingDays = [];
  let current = startDate;

  while (matchingDays.length < maxCount) {
    if(allowedDays.find(day => day === current.getUTCDay())){
      matchingDays.push(current);
    }

    current = addDays(current, 1);
  }

  return matchingDays;
}

// Given start values
const startDate = "2020-08-03";
const daysToPlay = [1, 3];
// Could come from your matches.length array
const numberOfMatches = 13;

const result = getMatchingDays(new Date(startDate), daysToPlay, numberOfMatches);
console.log(result);

const startDate = "03-08-2020";
const teams = ["A", "B", "C", "D", "E", "F"];
const fields = ["Field1", "Field2"];
const daysToPlay = ["03-08-2020", "05-08-2020"]; // use raw value, and format when displaying
let output = []


function matchmaking() {
  
  const games = []
  // 15 games in total, (n^2 + n) / 2
  for (let i = 0; i < teams.length - 1; i++) {
    for (let k = 0; k < teams.length - i - 1; k++) {
      const left = teams[k]
      const right = teams[k + i + 1]
      games.push([left, right])
    }
  }
  
  const availableFields = fields.length
  const fieldGames = Math.floor(games.length / availableFields)
  
  // 15 / 2 !== 7
  const leftOvers = games.length % availableFields

  for (let i = 0; i < availableFields; i++) {
    output.push({
      number: i + 1,
      date: daysToPlay[i],
      field: fields[i],
      matches: games.slice(i * fieldGames, (i + 1) * fieldGames),
    })
  }
  // add leftOvers to latest date, or earliest
  const leftOverGames = games.slice(availableFields * fieldGames, availableFields * fieldGames + leftOvers)
  output[output.length - 1].matches = output[output.length - 1].matches.concat(leftOverGames)
  
  console.log(output)
  return output
}

matchmaking()

Consider data structure for future: fields and daysToPlay can be combined to single entity - fieldSlot, which can contain time slots and field ids. For the sake of example daysToPlay has no data structural impact on above demo code.

Related