2

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

Mario
  • 4,784
  • 3
  • 34
  • 50
  • please post what you have tried so far to achieve this. – Asutosh Aug 03 '20 at 05:53
  • In this particular task I have not managed to know how to start. I think that I could know the dates from day to day if I knew the number of possible combinations of the teams. But I don't know how to get this information – Mario Aug 03 '20 at 05:54
  • To start where you need combinations of items inside an array, please follow :https://stackoverflow.com/questions/43241174/javascript-generating-all-combinations-of-elements-in-a-single-array-in-pairs – Asutosh Aug 03 '20 at 05:55
  • Here are several tasks to be done and it feels like a lot of constraints are missing. Some of the questions that come to my mind are: "How many games can be played per day on one field?", "How will the matches be arranged (tournament, round-robin, with rematch)?", "How many matches a single team can play on one day?". So seems for me quite incomplete to provide a real answer to this question.I think you won't get any more than a few comments about combinatorics and that's it. – Oliver Aug 03 '20 at 06:01
  • You are right, I will update the question – Mario Aug 03 '20 at 06:02
  • I got to fix the problem described in update 1 by checking when the combinations length is equal to one, I push to the last set of matches and remove the combination from combinations – Mario Aug 04 '20 at 04:14
  • I'm almost there!, could you give me a hand in relation to the date, take a look at update 3 – Mario Aug 04 '20 at 05:02

2 Answers2

2

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);
Oliver
  • 43,366
  • 8
  • 94
  • 151
  • Thanks for answering Oliver, I am trying to implement the logic you share until the related to date, could you take a look to the updated I am sharing. I am getting an unexpected behavior in the final set of matches – Mario Aug 04 '20 at 03:08
  • Thanks again Oliver. I have been testing and the logic is going well, only that in my time zone instead of counting from August 03, the example counts from 04 of the same month. Any ideas how I could handle this? And thanks – Mario Aug 04 '20 at 17:00
  • This is the current output I get https://gist.github.com/ilmoralito/7bbecd3c2c2d59fde5dbd13d1ec6d1f4 – Mario Aug 04 '20 at 17:01
  • Its working now, I changed `current.getDay()` for `current.getUTCDay()` in my local the call to `current.getDay()` return `0` even when 03-08-2020 is monday, and the call to `current.getUTCDay()` return `1` as expected. I Will update the answer with the final result. – Mario Aug 04 '20 at 17:45
  • Nice to see, that my sketch could help. Using UTC is really better to avoid time zone shifts (especially if the time is midnight and causes day shifts). Also beware of missing checks of the incoming parameters. If `allowedDays` is an empty array or contains only things that are not the numbers from zero to six it will run endless. – Oliver Aug 05 '20 at 08:08
  • Thanks for the help, I take into consideration the observation. – Mario Aug 05 '20 at 12:26
1

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.

Medet Tleukabiluly
  • 11,662
  • 3
  • 34
  • 69