web-dev-qa-db-ja.com

スピードバックアルゴリズムの実装に役立つ機能的な方法はどれですか。

スピードバックは、スピードデートとフィードバックの融合です:2分。人々のグループのすべてのメンバーと1対1で話します。

アルゴリズムについての手がかりを得るために、コンピューターサイエンス で同様の質問をしましたが、関数型プログラミングに変換するのは困難です。

入力を検討してください:

val team = setOf("alexandre", "sergiu", "joana", "tiago", "susana", "david")

チームメンバー全員が互いにフィードバックし合うラウンドロビンのようにしたいと思います。同時ペアの数が最大になるようにグループ化して、ラウンドの総数を減らしたいです。

必要な出力は次のようになります:

1st round:
"alexandre" to "sergiu"
"joana" to "tiago"
"susana" to "david"

2nd round:
"alexandre" to "david"
"sergiu" to "tiago"
"susana" to "joana"

3rd round:
...

みんなが「デート」するまで。

私はこれを持っています:

fun main() {
    val team = setOf("alexandre", "sergiu", "joana", "tiago", "susana", "david")

    println(team.combinations(2))
}

これまでの出力は15の可能なペア(組み合わせ)です。 次に、上記の出力を取得するために使用する関数メソッドがわかりません。私はgroupByassociateBypartitionを調べましたが、どれも適切ではないようです。または、FPの初心者です。

4
Luís Soares

answer Computer Science Stack ExchangeをKotlinに変えたところです。

fun <T> roundRobin(list: List<T>): List<List<Pair<T, T>>> {
    val n = list.size
    return (if (n % 2.0 == 0.0) roundRobinEven(n) else roundRobinOdd(n)).map { 
        round -> round.map {
            (first, second) -> Pair(list[first], list[second])
        }
    }
}

//For each round (i), (i, n-1) and then Pair((j+i)%(n-1), (n-1−j+i)%(n-1) for the rest
fun roundRobinEven(n: Int): List<List<Pair<Int, Int>>> = 
    List(n-1) { i -> (1..(n/2-1)).map{ j -> Pair((j+i)%(n-1), (n-1-j+i)%(n-1)) }.plusElement(Pair(i, n-1)) }
    
//For each round (i), Pair((j+i)%(n), (n−j+i)%(n)) where 1<=j<=(n-1)/2
fun roundRobinOdd(n: Int): List<List<Pair<Int, Int>>> =
    List(n) { i -> (1..(n-1)/2).map { j -> Pair((j+i)%n, (n-j+i)%n) } }

そして、あなたはこのようにそれを使うことができます。内部の各リストはラウンドを表します

val team = listOf("alexandre", "sergiu", "joana", "tiago", "susana", "david")
val rounds = roundRobin(team2)
for (round in rounds) {
    println(round)
}

出力:

[(sergiu, susana), (joana, tiago), (alexandre, david)]
[(joana, alexandre), (tiago, susana), (sergiu, david)]
[(tiago, sergiu), (susana, alexandre), (joana, david)]
[(susana, joana), (alexandre, sergiu), (tiago, david)]
[(alexandre, tiago), (sergiu, joana), (susana, david)]

Kotlinプレイグラウンドへのリンク

2
user

これがalgorithmで、関数型プログラミングライブラリで記述されています。ラウンドワイズは理想的ではありません(6人で7ラウンドかかる)が、15の組み合わせになります。正確に5ラウンドすることは不可能ではありません、受け入れられた答えを見てください。まず結果

people [ 'A', 'B', 'C', 'D', 'E', 'F' ]
round 0 [ [ 'A', 'B' ], [ 'C', 'D' ], [ 'E', 'F' ] ]
round 1 [ [ 'A', 'C' ], [ 'B', 'D' ] ]
round 2 [ [ 'A', 'D' ], [ 'B', 'C' ] ]
round 3 [ [ 'A', 'E' ], [ 'B', 'F' ] ]
round 4 [ [ 'A', 'F' ], [ 'B', 'E' ] ]
round 5 [ [ 'C', 'E' ], [ 'D', 'F' ] ]
round 6 [ [ 'C', 'F' ], [ 'D', 'E' ] ]

ここに実装があります

const {
  pipe, fork, tap, switchCase,
  map, filter, transform,
  and, not, eq, get,
} = require('rubico')

const identity = x => x

// [person] => Map { person => (Set { person }) }
const makeTracker = transform(
  map(fork([
    person => person,
    () => new Set(),
  ])),
  new Map(),
)

const db = new Map()

const saveTracker = async tracker => { db.set('tracker', tracker) }

const hasTracker = async () => db.has('tracker')

const getTracker = async tracker => db.get('tracker')

const isNotIn = set => x => !set.has(x)

// { people, tracker } => { people, tracker, pairs }
const algorithm = ({ people, tracker }) => {
  const matchedThisRound = new Set()
  return fork({
    people: identity,
    tracker: () => tracker,
    pairs: transform(
      pipe([
        filter(isNotIn(matchedThisRound)),
        map(person => {
          for (const other of people) {
            // console.log('person:other', `${person}:${other}`)
            if (other === person) continue
            if (tracker.get(person).has(other)) continue
            if (matchedThisRound.has(other)) continue
            tracker.get(person).add(other)
            tracker.get(other).add(person)
            matchedThisRound.add(person)
            matchedThisRound.add(other)
            return [person, other]
          }
        }),
        filter(Array.isArray),
      ]),
      [],
    )
  })(people)
}

// [person, person, person, person, ...] => [[person, person], [person, person], ...]
const matchmake = pipe([
  fork({
    people: identity,
    tracker: switchCase([
      hasTracker, getTracker,
      makeTracker,
    ]),
  }),
  algorithm,
  tap(pipe([get('tracker'), saveTracker])),
])

const didEveryoneMatch = (tracker, people) => {
  for (const [, set] of tracker) {
    if (set.size < people.length - 1) return false // 1 for yourself
  }
  return true
}

const main = async () => {
  const people = ['A', 'B', 'C', 'D', 'E', 'F']
  console.log('people', people)
  let i = 0
  while (true) {
    const round = await matchmake(people)
    const { tracker, pairs } = round
    console.log('round', i, pairs)
    if (didEveryoneMatch(tracker, people)) break
    i += 1
  }
}

main()

いくつかの楽しい統計

  • 100人で127回
  • 53人で63回
  • 1000人は1023ラウンドかかります

これをpeopleにドロップすると、自分の人数で遊ぶことができます

const people = Array.from((function*() {
  for (let i = 0; i < 53; i++) yield `${i + 1}`
})())
2
richytong