import collection.mutable._

object Qualifying {

  class Person (
    val name : String,
    val score : Int) extends Ordered[Person] {
    def compare(that : Person) = this.score compare that.score
    override def toString() = ("name: " + name + " score : " + score)
  }

  def main(args: Array[String]): Unit = {
    val Array(n, m) = io.StdIn.readLine.split(" ").map(_.toInt)
    var region = new Array[PriorityQueue[Person]](m+1)
    for (i<-1 to m) region(i) = new PriorityQueue[Person]()
    for (i<-0 until n) {
      val r = io.StdIn.readLine.split(" ")
      val p = new Person(r(0), r(2).toInt)
      region(r(1).toInt).enqueue(p)
    }
    for (i<-1 to m) {
      //print(region(i))
      if (region(i).size == 2) println(region(i).dequeue.name + " " + region(i).dequeue.name)
      else {
        val top = region(i).dequeue
        val sec = region(i).dequeue
        if (top.score == sec.score) {
          if (sec.score == region(i).head.score) println("?")
          else println(top.name + " " + sec.name)
        } else { // 1st is unique, checking for 2nd
          if (sec.score == region(i).head.score) println("?")
          else println(top.name + " " + sec.name)
        }
      }
    }
  }
}

import Utils._, annotation.tailrec, collection.mutable, util._, control._, math.Ordering.Implicits._

object _659B extends CodeForcesApp {
  override def apply(io: IO) = {
    val n, m = io[Int]
    val scores = Array.fill(m)(mutable.Map.empty[String, Int])

    repeat(n) {
      val (name, region, score) = io[(String, Int, Int)]
      scores(region - 1)(name) = score
    }

    io.separator = newLine

    scores foreach {score =>
      val top = score.toIndexedSeq.sortBy(_._2)(desc)
      if (top.length <= 2 || top(1)._2 != top(2)._2) {
        io += (top(0)._1 -> top(1)._1)
      } else {
        io += '?'
      }
    }

    io
  }
}
/****************************[Ignore Template Below]****************************************/
trait CodeForcesApp {
  lazy val isOnlineJudge: Boolean = sys.props.get("ONLINE_JUDGE").exists(_.toBoolean)
  def debug(x: Any): Unit = if (!isOnlineJudge) println(x)
  def apply(io: IO): io.type
  def main(args: Array[String]): Unit = this(new IO(System.in, System.out)).close()
}
object Utils {
  import scala.collection.mutable, mutable.{Map => Dict}
  implicit class GenericExtensions[A](val a: A) extends AnyVal {
    def partialMatch[B](f: PartialFunction[A, B]): Option[B] = f lift a
  }
  implicit class TraversableExtensions[A, C[X] <: Traversable[X]](val t: C[A]) extends AnyVal {
    def onlyElement: A = t.head.ensuring(t.size == 1)
    def groupByIdentifier[K](f: A => K): Map[K, A] = t.groupBy(f).mapValues(_.onlyElement)
    def duplicates(implicit b: C of A): C[A] = {
      val elems = mutable.Set.empty[A]
      (t collect {case i if !elems.add(i) => i}).to[C]
    }
    def sum[B](f: A => B)(implicit n: Numeric[B]): B = t.foldLeft(n.zero) {case (p, i) => n.plus(p, f(i))}
    def zipWith[B](f: A => B)(implicit b: C of (A, B)): C[(A, B)] = (t map {i => i -> f(i)}).to[C]
    def mapTo[B](f: A => B)(implicit b: C of (A, B)): Map[A, B] = zipWith(f).toUniqueMap
    def whenNonEmpty[B](f: t.type => B): Option[B] = when(t.nonEmpty)(f(t))  // e.g. grades.whenNonEmpty(_.max)
    def collectFirst[B](f: PartialFunction[A, Option[B]]): Option[B] = t.view collect f collectFirst {case Some(x) => x}
    def toMutableMultiSet: Dict[A, Int] = {
      val c = map[A] to 0
      t.foreach(i => c(i) += 1)
      c
    }
    def toMultiSet: Map[A, Int] = toMutableMultiSet.toMap
  }
  implicit class ArrayExtensions[A <: AnyRef](val a: Array[A]) extends AnyVal {
    def sort(from: Int, until: Int)(implicit cmp: Ordering[A]): Unit = java.util.Arrays.sort(a, from, until, cmp) //sort [from, until)
  }
  implicit class ArrayExtensions2[A](val a: Array[A]) extends AnyVal {
    def sort(from: Int, until: Int)(implicit cmp: Ordering[A]): Unit = a.view(from, until).sorted.copyToArray(a, from)
  }
  implicit class SeqExtensions[A, C[X] <: Seq[X]](val s: C[A]) extends AnyVal {
    def isRotationOf(t: Seq[A]): Boolean = s ++ s containsSlice t
  }
  implicit class PairsExtensions[A, B, C[X] <: Traversable[X]](t: C[(A, B)]) {
    def toUniqueMap: Map[A, B] = t.groupByIdentifier(_._1).mapValues(_._2)
    def toMultiMap(implicit b: C of B): Map[A, C[B]] = t.groupBy(_._1).mapValues(_.map(_._2).to[C])
    def swap(implicit b: C of (B, A)): C[(B, A)] = t.map(_.swap).to[C]
  }
  implicit class MapExtensions[K, V, C[X, Y] <: Map[X, Y]](val m: C[K, V]) extends AnyVal {
    def invert: Map[V, Set[K]] = m.toSet[(K, V)].swap.toMultiMap
    def mapKeys[K2](f: K => K2): Map[K2, V] = m map {case (k, v) => f(k) -> v}
    def toMutable: Dict[K, V] = Dict.empty[K, V] ++ m
  }
  implicit class SetExtensions[A, C[A] <: Set[A]](val s: C[A]) extends AnyVal {
    def notContains(x: A): Boolean = !(s contains x)
  }
  implicit class LongExtensions(val x: Long) extends AnyVal {
    def bitCount: Int = java.lang.Long.bitCount(x)
  }
  implicit class NumericExtensions[A](val x: A)(implicit n: Numeric[A]) {
    import Numeric.Implicits._
    def **(i: Int): A = if (i == 0) n.one else {
      val h = x ** (i/2)
      if (i%2 == 0) h * h else h * h * x
    }
    def nonNegative: Option[A] = when(n.gteq(x, n.zero))(x)
    def whenNegative(f: => A): A = nonNegative getOrElse f   //e.g. students indexOf "Greg" whenNegative Int.MaxValue
  }
  implicit class IntegralExtensions[A](val x: A)(implicit n: Integral[A]) {
    import scala.collection.immutable.NumericRange, Integral.Implicits._
    def mod(y: A): A = x - ((x / y) * y) //(x/y)*y + (x mod y) == x
    def -->(y: A): NumericRange.Inclusive[A] = NumericRange.inclusive(n.min(x, y), n.max(x, y), n.one)
  }
  def desc[A: Ordering]: Ordering[A] = asc[A].reverse  //e.g. students.sortBy(_.height)(desc)
  def  asc[A](implicit order: Ordering[A]): Ordering[A] = order
  def map[K] = new {
    def to[V](default: V): Dict[K, V] = Dict.empty[K, V] withDefaultValue default
  }
  def memoize[I] = new {
    def apply[O](f: I => O): I => O = new mutable.HashMap[I, O]() {
      override def apply(key: I) = getOrElseUpdate(key, f(key))
    }
  }
  @inline def when[A](check: Boolean)(f: => A): Option[A] = if (check) Some(f) else None
  def repeat[U](n: Int)(f: => U): Unit = (1 to n).foreach(_ => f)
  type of[C[_], A] = scala.collection.generic.CanBuild[A, C[A]]
  val mod: Int = (1e9 + 7).toInt
  val newLine: String = "\n"
}
/***********************************[Scala I/O]*********************************************/
import Utils._, java.io._, java.util.StringTokenizer

class IO(in: BufferedReader, out: BufferedWriter) extends PrintWriter(out, true) with Iterator[String] {
  def this(reader: Reader, writer: Writer) = this(new BufferedReader(reader), new BufferedWriter(writer))
  def this(in: InputStream, out: OutputStream) = this(new InputStreamReader(in), new OutputStreamWriter(out))

  private[this] val tokenizers = Iterator.continually(in.readLine()).takeWhile(_ != null).map(new StringTokenizer(_)).buffered

  @inline private[this] def tokenizer() = {
    while(tokenizers.nonEmpty && !tokenizers.head.hasMoreTokens) tokenizers.next()
    if (tokenizers.nonEmpty) Some(tokenizers.head) else None
  }

  def apply[A](implicit read: IO.Read[A]): A = read.apply(this)
  def apply[C[_], A: IO.Read](n: Int)(implicit b: C of A): C[A] = Iterator.fill(n)(apply).to[C]

  def till(delim: String): String = tokenizer().get.nextToken(delim)
  def tillEndOfLine(): String = till("\n\r")

  override def next() = tokenizer().get.nextToken()
  override def hasNext = tokenizer().nonEmpty

  var separator = " "
  private[this] var isNewLine = true

  def +=[A](x: A)(implicit write: IO.Write[A]): this.type = {
    write(this)(x)
    this
  }
  def appendNewLine(): this.type = {
    println()
    this
  }

  override def print(obj: String) = {
    if (isNewLine) isNewLine = false else super.print(separator)
    super.print(obj)
  }
  override def println() = if (!isNewLine) {
    super.println()
    isNewLine = true
  }
  override def close() = {
    flush()
    in.close()
    super.close()
  }
}
object IO {
  class Read[A](val apply: IO => A) {
    def map[B](f: A => B): Read[B] = new Read(apply andThen f)
  }
  object Read {
    implicit def collection[C[_], A: Read](implicit b: C of A): Read[C[A]]         = new Read(r => r[C, A](r[Int]))
    implicit val string                                       : Read[String]       = new Read(_.next())
    implicit val char                                         : Read[Char]         = string.map(_.toTraversable.onlyElement)
    implicit val boolean                                      : Read[Boolean]      = string.map(_.toBoolean)
    implicit val int                                          : Read[Int]          = string.map(_.toInt)
    implicit val long                                         : Read[Long]         = string.map(_.toLong)
    implicit val bigInt                                       : Read[BigInt]       = string.map(BigInt(_))
    implicit val double                                       : Read[Double]       = string.map(_.toDouble)
    implicit val bigDecimal                                   : Read[BigDecimal]   = string.map(BigDecimal(_))
    implicit def tuple2[A: Read, B: Read]                     : Read[(A, B)]       = new Read(r => (r[A], r[B]))
    implicit def tuple3[A: Read, B: Read, C: Read]            : Read[(A, B, C)]    = new Read(r => (r[A], r[B], r[C]))
    implicit def tuple4[A: Read, B: Read, C: Read, D: Read]   : Read[(A, B, C, D)] = new Read(r => (r[A], r[B], r[C], r[D]))
  }
  trait Write[-A] {
    def apply(out: PrintWriter)(x: A): Unit
  }
  trait DefaultWrite {
    implicit def any[A]: Write[A] = Write(_.toString)
  }
  object Write extends DefaultWrite {
    def apply[A](f: A => String) = new Write[A] {
      override def apply(out: PrintWriter)(x: A) = out.print(f(x))
    }
    implicit val boolean: Write[Boolean] = Write(b => if (b) "YES" else "NO")
    implicit def tuple2[A, B]: Write[(A, B)] = Write {case (a, b) => s"$a $b"}
    implicit def iterable[A](implicit write: Write[A]): Write[Traversable[A]] = new Write[Traversable[A]] {
      override def apply(out: PrintWriter)(xs: Traversable[A]) = {
        xs foreach write(out)
        out.println()
      }
    }
  }
}

import scala.io.Source

object Solution extends App {
  val in = Source.stdin.getLines()
  val Array(n, m) = in.next().split(' ').map(_.toInt)
  val res = (1 to n).map { _ =>
    val Array(name, region, score) = in.next().split(' ')
    (name, region.toInt, score.toInt)
  }.groupBy(_._2).mapValues(_.sortBy(-_._3)).mapValues{
    case(participans) if participans.length == 2 => participans.map(_._1).mkString(" ")
    case(participans) if participans(1)._3 == participans(2)._3 => "?"
    case(participans) => participans.take(2).map(_._1).mkString(" ")
  }
  println((1 to m).map(res).mkString("\n"))
}

import collection.mutable._

object Qualifying {

  class Person (
    val name : String,
    val score : Int) extends Ordered[Person] {
    def compare(that : Person) = this.score compare that.score
    override def toString() = ("name: " + name + " score : " + score)
  }

  def main(args: Array[String]): Unit = {
    val Array(n, m) = io.StdIn.readLine.split(" ").map(_.toInt)
    var region = new Array[PriorityQueue[Person]](m+1)
    for (i<-1 to m) region(i) = new PriorityQueue[Person]()
    for (i<-0 until n) {
      val r = io.StdIn.readLine.split(" ")
      val p = new Person(r(0), r(2).toInt)
      region(r(1).toInt).enqueue(p)
    }
    for (i<-1 to m) {
      //print(region(i))
      if (region(i).size == 2) println(region(i).dequeue.name + " " + region(i).dequeue.name)
      else {
        val top = region(i).dequeue
        var sec = region(i).dequeue
        while (!region(i).isEmpty && top.score == sec.score) {
          sec = region(i).dequeue
        }
        if (sec.score == top.score) { // all are the same
          println("?")
        } else if (!region(i).isEmpty && region(i).head.score == sec.score) {
          // tiebreak 2nd
          //println(region(i).head, sec)
          println("?")
        } else println(top.name + " " + sec.name)
      }
    }
  }
}


object B1256 {

  import IO._
  import collection.{mutable => cu}
  import scala.collection.mutable.ArrayBuffer

  def main(args: Array[String]): Unit = {
    var tests = readInt
    while (tests > 0) {
      val n = readInt
      val arr = readInts(n)
      val vis = Array.fill(n)(false)
      for (i <- 1 to n) {
        var idx = arr.indexWhere(_ == i)
        while (idx > 0 && arr(idx) < arr(idx - 1) && !vis(idx - 1)) {
          vis(idx - 1) = true
          val tmp = arr(idx - 1)
          arr(idx - 1) = arr(idx)
          arr(idx) = tmp

          idx -= 1
        }
      }
      out.println(arr.mkString(" "))
      tests -= 1
    }
    out.close()
  }

  object IO {
    private val input = new java.io.BufferedReader(
      new java.io.InputStreamReader(
        if (System.getProperty("ONLINE_JUDGE") != null) System.in
        else new java.io.FileInputStream("src/input")
      )
    )
    val out = new java.io.PrintWriter(System.out)

    @inline def read: String = input.readLine()

    @inline def tokenizeLine = new java.util.StringTokenizer(read)

    def readInt: Int = tokenizeLine.nextToken.toInt

    def readInts(n: Int): Array[Int] = {
      val tl = tokenizeLine
      Array.fill(n)(tl.nextToken.toInt)
    }

    def readLongs(n: Int): Array[Long] = {
      val tl = tokenizeLine
      Array.fill(n)(tl.nextToken.toLong)
    }

    def readBigs(n: Int): Array[BigInt] = {
      val tl = tokenizeLine
      Array.fill(n)(BigInt(tl.nextToken))
    }
  }

}

object SmallestPermutation {

  def main(args: Array[String]): Unit = {
    import InOut._
    val inputSequences = nextInt
    for {_ <- 0 until inputSequences} {
      val operations = (0 until nextInt).dropRight(1)
      val initialPermutation = nextLine.trim.split(" ").map(_.toInt).toSeq
      val finalPermutation = findSmallestPermutation(initialPermutation, operations)
      out.println(finalPermutation.mkString(" "))
    }
    out.flush()
  }

  @scala.annotation.tailrec
  def findSmallestPermutation(permutation: Seq[Int], possibleOperations: Seq[Int]): Seq[Int] = {
    val operation = nextOperation(permutation, possibleOperations)
    if (operation == -1) permutation else findSmallestPermutation(swapOperation(operation)(permutation), possibleOperations.filter(_ != operation))
  }

  private def nextOperation(permutation: Seq[Int], possibleOperations: Seq[Int]): Int = {
    val relevantOperation = possibleOperations.filter(op => permutation(op) > permutation(op + 1))
    if (relevantOperation.isEmpty) {
      -1
    } else {
      relevantOperation.reduce {
        (op1, op2) => if (permutation(op1 + 1) < permutation(op2 + 1)) op1 else op2
      }
    }
  }

  private def swapOperation[T](index: Int)(collection: Seq[T]): Seq[T] =
    collection.updated(index, collection(index + 1)).updated(index + 1, collection(index))

  final object InOut {
    val out = new java.io.PrintWriter(System.out, false)

    def nextInt: Int = Integer.parseInt(nextToken)

    def nextLine: String = in.readLine

    private val in = new java.io.BufferedReader(new java.io.InputStreamReader(System.in))
    private[this] var tokenizer: java.util.StringTokenizer = _

    private def nextToken: String = {
      while (tokenizer == null || !tokenizer.hasMoreTokens)
        tokenizer = new java.util.StringTokenizer(in.readLine)
      tokenizer.nextToken
    }
  }

}

import java.util.Scanner

import scala.language.postfixOps

object TaskB extends App {
  val sc = new Scanner(System.in)
  val q = sc.nextInt

  val out = 1 to q map { _ =>
    val n = sc.nextInt
    val arr = (1 to n map (_ => sc.nextInt()) toArray)
    minimize(arr, 0, n)
    arr.mkString(" ")
  } mkString "\n"

  println(out)

  def minimize(arr: Array[Int], l: Int, r: Int): Unit = {
    if (l < r - 1) {
      val minInd = arr.zipWithIndex.slice(l, r).minBy(_._1)._2
      if (minInd == l) {
        minimize(arr, l + 1, r)
      } else {
        move(arr, minInd, l)
        minimize(arr, minInd, r)
      }
    }
  }

  def move(arr: Array[Int], from: Int, to: Int) = {
    val temp = arr(from)
    from until to by -1 foreach (i => arr(i) = arr(i - 1))
    arr(to) = temp
  }
}

//package codeforces.contest1256

object MinimizeThePermutation {
  def main(args: Array[String]): Unit = {
    for (_ <- 1 to io.StdIn.readInt()) {
      val n = io.StdIn.readInt()

      val ls = io.StdIn.readLine().split(" ").map(_.toInt).toList

      def loop(given: List[Int], acc: List[Int] = Nil): List[Int] = {
        if (given.isEmpty) acc
        else {
          val min = given.min

          val (left, right) = given.span(_ != min)

          if (left.isEmpty)
            loop(given.tail, acc :+ given.head)
          else
            loop(left.last +: right.tail, acc ++ (right.head +: left.dropRight(1)))
        }
      }

      println {
        loop(ls).mkString(" ")
      }

    }

  }

}

object Main {
  def main(args: Array[String]): Unit = {
    val q = readInt
    (0 until q).foreach((x) => f1)
  }

  def f1: Unit = {
    val n = readInt
    var a = readLine.split(" ").map(_.toInt).toList
    println(f2(a).mkString(" "))
  }

  def f2(a: List[Int]): List[Int] = a match {
    case Nil => Nil
    case hd :: tl  => {
      val (_, _, i) = tl.foldLeft((hd, 1, 0)){
        case ((mv, i, mi), h) => if (h < mv) (h, i + 1, i) else (mv, i + 1, mi)
      }
      val (at, bt) = a splitAt i
      (at, bt) match {
        case (_, Nil) => throw new Exception("Not occur")
        case (Nil, x :: y) => x :: f2(y)
        case (l, x :: y) => (x :: l.init) ++ f2(l.last :: y)
      }
    }
  }
}

object B1256 {

  import IO._
  import collection.{mutable => cu}
  import scala.collection.mutable.ArrayBuffer

  def main(args: Array[String]): Unit = {
    var tests = readInt
    while (tests > 0) {
      val n = readInt
      val arr = readInts(n)
      val vis = Array.fill(n)(false)
      for (i <- 1 to n) {
        var idx = arr.indexWhere(_ == i)
        while (i != idx + 1 && !vis(idx - 1) && vis.count(!_) > 1) {
          vis(idx - 1) = true
          val tmp = arr(idx - 1)
          arr(idx - 1) = arr(idx)
          arr(idx) = tmp

          idx -= 1
        }
      }
      out.println(arr.mkString(" "))
      tests -= 1
    }
    out.close()
  }

  object IO {
    private val input = new java.io.BufferedReader(
      new java.io.InputStreamReader(
        if (System.getProperty("ONLINE_JUDGE") != null) System.in
        else new java.io.FileInputStream("src/input")
      )
    )
    val out = new java.io.PrintWriter(System.out)

    @inline def read: String = input.readLine()

    @inline def tokenizeLine = new java.util.StringTokenizer(read)

    def readInt: Int = tokenizeLine.nextToken.toInt

    def readInts(n: Int): Array[Int] = {
      val tl = tokenizeLine
      Array.fill(n)(tl.nextToken.toInt)
    }

    def readLongs(n: Int): Array[Long] = {
      val tl = tokenizeLine
      Array.fill(n)(tl.nextToken.toLong)
    }

    def readBigs(n: Int): Array[BigInt] = {
      val tl = tokenizeLine
      Array.fill(n)(BigInt(tl.nextToken))
    }
  }

}

object B1256 {

  import IO._
  import collection.{mutable => cu}
  import scala.collection.mutable.ArrayBuffer

  def main(args: Array[String]): Unit = {
    var tests = readInt
    while (tests > 0) {
      val n = readInt
      val arr = readInts(n)
      val vis = Array.fill(n)(false)
      for (i <- 1 to n) {
        var idx = arr.indexWhere(_ == i)
        while (i != idx + 1 && !vis(idx - 1)) {
          vis(idx - 1) = true
          val tmp = arr(idx - 1)
          arr(idx - 1) = arr(idx)
          arr(idx) = tmp

          idx -= 1
        }
      }
      out.println(arr.mkString(" "))
      tests -= 1
    }
    out.close()
  }

  object IO {
    private val input = new java.io.BufferedReader(
      new java.io.InputStreamReader(
        if (System.getProperty("ONLINE_JUDGE") != null) System.in
        else new java.io.FileInputStream("src/input")
      )
    )
    val out = new java.io.PrintWriter(System.out)

    @inline def read: String = input.readLine()

    @inline def tokenizeLine = new java.util.StringTokenizer(read)

    def readInt: Int = tokenizeLine.nextToken.toInt

    def readInts(n: Int): Array[Int] = {
      val tl = tokenizeLine
      Array.fill(n)(tl.nextToken.toInt)
    }

    def readLongs(n: Int): Array[Long] = {
      val tl = tokenizeLine
      Array.fill(n)(tl.nextToken.toLong)
    }

    def readBigs(n: Int): Array[BigInt] = {
      val tl = tokenizeLine
      Array.fill(n)(BigInt(tl.nextToken))
    }
  }

}

object SmallestNumber {
  import InOut._
  def main(args: Array[String]): Unit = {}

  @scala.annotation.tailrec
  def smallestPermutation(permutation: Seq[Int], possibleOperations: Seq[Int]): Seq[Int] = {
    val operation = nextOperation(permutation, possibleOperations)
    if (operation == -1) permutation else smallestPermutation(swapOperation(operation)(permutation), possibleOperations.filter(_ != operation))
  }

  private def nextOperation(permutation: Seq[Int], possibleOperations: Seq[Int]): Int = {
    val relevantOperation = possibleOperations.filter(op => permutation(op) > permutation(op + 1))
    if (relevantOperation.isEmpty) {
      -1
    } else {
      possibleOperations.reduce {
        (acc, value) => if (permutation(acc + 1) < permutation(value + 1)) acc else value
      }
    }
  }

  private def swapOperation[T](index: Int)(collection: Seq[T]): Seq[T] =
    collection.updated(index, collection(index + 1)).updated(index + 1, collection(index))

  val inputSequences = nextInt
  for {_ <- 0 until inputSequences} {
    val operations = (0 until nextInt).dropRight(1)
    val initialPermutation = nextLine.trim.split(" ").map(_.toInt).toSeq
    val finalPermutation = smallestPermutation(initialPermutation, operations)
    out.println(finalPermutation.mkString(" "))
  }
  out.flush()

  final object InOut {
    val out = new java.io.PrintWriter(System.out, false)

    def nextInt: Int = Integer.parseInt(nextToken)

    def nextLine: String = in.readLine

    private val in = new java.io.BufferedReader(new java.io.InputStreamReader(System.in))
    private[this] var tokenizer: java.util.StringTokenizer = _

    private def nextToken: String = {
      while (tokenizer == null || !tokenizer.hasMoreTokens)
        tokenizer = new java.util.StringTokenizer(in.readLine)
      tokenizer.nextToken
    }
  }

}