Bloquer la partition d'une chaîne

11

L'inspiration .

Prenons une liste lcomposée de chiffres. Définir une opération de bloc à l' index isur la liste là l'acte consistant à déplacer 3 éléments consécutifs à partir ide lla fin.

Exemple:

l, i (1-indexing) -> l (after applying block operation at index i)
[1,2,3,4,5], 1 -> [4,5,1,2,3]
[1,2,3,4,5,6,7], 3 -> [1,2,6,7,3,4,5]

Étant donné une liste composée uniquement de 0 et 1, votre défi est de la partitionner de sorte que les zéros soient à l'avant et ceux à l'arrière, en utilisant uniquement des opérations de bloc. La sortie doit être les indices dans l'ordre où ils sont appliqués sur la liste.

Parce que cela est impossible pour la liste [1,0,1,0], la longueur de la liste est garantie d'être au moins 5.

Cas de test (1-indexation)

(il existe d'autres sorties valides)

[1,1,1,0,0] -> [1]
[0,1,0,1,0] -> [1,2,1,1]
[0,0,0,1,1,1,0,0,0] -> [4]

Utilisez ce script pour générer plus de cas de test. (seule entrée. La rplc ' ';','partie est utilisée pour r e pl d' un c e espaces par des virgules dans la sortie)

Critères gagnants

est le principal critère gagnant, et est le bris d'égalité. En particulier:

  • La solution avec la longueur de sortie la plus courte (le moins d'opérations de bloc) avec le cas de test ( n_elem= 500, random_seed= {valeur secrète}) l'emporte. Vous devriez pouvoir exécuter votre solution jusqu'à son terme avec le scénario de test ( n_elem= 500, random_seed= 123456).
  • En cas d'égalité, la solution qui peut gérer la plus grande valeur de puissance de 2 de n_elemavec random_seed= {valeur secrète} dans les 10 secondes (pour moi) l'emporte.
  • En cas d'égalité, la solution qui prend moins de temps sur ce cas de test l'emporte.
user202729
la source
Poteau de bac à sable . (note) J'ai une solution d'espace linéaire en temps linéaire, mais elle a un facteur constant énorme, en plus ce n'est pas facile à mettre en œuvre. Il est possible de réduire le facteur constant mais c'est encore plus difficile à mettre en œuvre.
user202729
(Avertissement: j'ai résolu le défi lié)
user202729
Juste pour clarifier, la sortie n'a pas besoin d'être la sortie la plus courte possible?
JungHwan Min
@JungHwanMin Correct.
user202729

Réponses:

8

Python 3 , (0,397 n + 3,58) étapes

Régression polynomiale de 1000 points par numpy.polyfit.


  • Nombre d'étapes pour la version 1: 0,0546 n² + 2,80 n - 221
  • Nombre d'étapes pour la version 2: 0,0235 n² + 0,965 n - 74
  • Nombre d'étapes pour la version 3: 0,00965 n² + 2,35 n - 111
  • Nombre d'étapes pour la version 4: 1,08 n - 36,3
  • Nombre d'étapes pour la version 5: 0,397 n + 3,58

  • Score du scénario de test secret pour la version 1: 14468
  • Score du scénario de test secret pour la version 2: 5349
  • Score du scénario de test secret pour la version 3: 4143
  • Score du scénario de test secret pour la version 4: 450
  • Score du scénario de test secret pour la version 5: 205

def partite(L):
	endgame5 = [9,9,1,9,0,0,1,9,
		0,1,0,1,0,1,1,9,
		0,0,1,0,0,0,1,0,
		0,0,0,1,0,0,0,9]
	endgame6 = [9,9,2,9,1,1,2,9,0,2,0,0,1,2,2,9,
		0,1,2,1,0,1,2,1,0,1,0,2,1,1,0,9,
		0,0,1,0,1,0,1,1,0,0,0,0,1,1,1,1,
		0,0,2,2,0,0,2,2,0,0,0,0,0,0,0,9]
	endgame = [9,9,3,9,2,2,3,9,1,0,3,0,2,0,3,9,0,1,3,3,2,2,3,0,1,0,1,0,2,1,0,9,
		0,0,2,1,0,0,2,2,1,0,1,2,0,0,0,2,0,1,3,3,3,3,3,0,1,1,1,1,1,3,0,9,
		0,0,0,0,1,0,1,1,1,0,3,0,1,0,1,0,0,1,0,0,1,1,0,0,1,0,0,0,2,0,1,0,
		0,0,2,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0,3,0,3,0,3,0,3,0,2,3,3,0,0,9]
	offset = 1
	steps = []
	def update(L,steps,ind):
		steps.append(offset + ind)
		if 0 <= ind and ind+3 < len(L):
			return (steps,L[:ind]+L[ind+3:]+L[ind:ind+3])
		else:
			print(offset,ind,L)
			raise
	if len(L) == 5:
		while endgame5[L[0]*16+L[1]*8+L[2]*4+L[3]*2+L[4]] != 9:
			steps, L = update(L,steps,endgame5[L[0]*16+L[1]*8+L[2]*4+L[3]*2+L[4]])
		return steps
	if len(L) == 6:
		while endgame6[L[0]*32+L[1]*16+L[2]*8+L[3]*4+L[4]*2+L[5]] != 9:
			steps, L = update(L,steps,endgame6[L[0]*32+L[1]*16+L[2]*8+L[3]*4+L[4]*2+L[5]])
		return steps
	if 1 not in L:
		return []
	while len(L) > 7 and 0 in L:
		wf_check = len(L)
		while L[0] != 0:
			pos = [-1]
			wf_check2 = -1
			while True:
				i = pos[-1]+1
				while i < len(L):
					if L[i] == 0:
						pos.append(i)
						i += 1
					else:
						i += 3
				assert len(pos) > wf_check2
				wf_check2 = len(pos)
				space = (pos[-1]-len(L)+1)%3
				ind = -1
				tail = pos.pop()
				i = len(L)-1
				while i >= 0:
					if tail == i:
						while tail == i:
							i -= 1
							tail = pos.pop() if pos else -1
						i -= 2
					elif i < len(L)-3 and L[i+space] == 0:
						ind = i
						break
					else:
						i -= 1
				if ind == -1:
					break
				steps, L = update(L, steps, ind)
				pos = pos or [-1]
			if L[0] == 0:
				break
			pos = [-1]
			while L[0] != 0:
				pos = [-1]
				found = False
				for i in range(1,len(L)):
					if L[i] == 0:
						if i%3 == (pos[-1]+1)%3:
							pos.append(i)
						else:
							found = found or i
				if found > len(L)-4:
					found = False
				if not found:
					break
				triple = []
				for i in range(1,len(L)-1):
					if L[i-1] == 1 and L[i] == 1 and L[i+1] == 1:
						triple.append(i)
					if len(triple) > 3:
						break
				space = (pos[-1]-len(L)+1)%3
				if space == 0:
					if found >= 2 and found-2 not in pos and found-1 not in pos:
						# ... _ 1 _ [0] 0 ...
						if found-2 in triple:
							triple.remove(found-2)
						if found-3 in triple:
							triple.remove(found-3)
						if L[-1] == 1:
							steps, L = update(L, steps, found-2)
							steps, L = update(L, steps, len(L)-4)
							steps, L = update(L, steps, len(L)-4)
						elif triple:
							steps, L = update(L, steps, found-2)
							if found < triple[0]:
								triple[0] -= 3
							steps, L = update(L, steps, triple[0]-1)
							steps, L = update(L, steps, len(L)-4)
						else:
							break
						assert L[-3] == 0
					elif found >= 1 and found-1 not in pos and found+1 not in pos:
						# ... _ 1 [0] _ 0 ...
						if found-2 in triple:
							triple.remove(found-2)
						if L[-2] == 1 and L[-1] == 1:
							steps, L = update(L, steps, found-1)
							steps, L = update(L, steps, len(L)-5)
							steps, L = update(L, steps, len(L)-5)
						elif triple:
							steps, L = update(L, steps, found-1)
							if found < triple[0]:
								triple[0] -= 3
							steps, L = update(L, steps, triple[0]-1)
							steps, L = update(L, steps, len(L)-5)
						elif L[-1] == 1:
							steps, L = update(L, steps, found-1)
							steps, L = update(L, steps, len(L)-4)
							steps, L = update(L, steps, len(L)-4)
							steps, L = update(L, steps, len(L)-4)
						else:
							break
						assert L[-3] == 0
					else:
						break
				elif space == 1:
					# ... 1 1 [0] 0 ...
					if found >= 2 and found-2 not in pos and found-1 not in pos:
						if found-2 in triple:
							triple.remove(found-2)
						if found-3 in triple:
							triple.remove(found-3)
						if triple:
							steps, L = update(L, steps, found-2)
							if found < triple[0]:
								triple[0] -= 3
							steps, L = update(L, steps, triple[0]-1)
							steps, L = update(L, steps, len(L)-5)
						elif L[-2] == 1 and L[-1] == 1:
							steps, L = update(L, steps, found-2)
							steps, L = update(L, steps, len(L)-4)
							steps, L = update(L, steps, len(L)-5)
						else:
							break
						assert L[-2] == 0
					else:
						break
				else:
					if found+1 not in pos and found+2 not in pos:
						# ... 0 [0] _ 1 _ ...
						if found+2 in triple:
							triple.remove(found+2)
						if found+3 in triple:
							triple.remove(found+3)
						if L[-2] == 1 and L[-1] == 1:
							steps, L = update(L, steps, found)
							steps, L = update(L, steps, len(L)-5)
						elif L[-1] == 1:
							steps, L = update(L, steps, found)
							steps, L = update(L, steps, len(L)-4)
							steps, L = update(L, steps, len(L)-4)
						elif triple:
							steps, L = update(L, steps, triple[0]-1)
							if triple[0] < found:
								found -= 3
							steps, L = update(L, steps, found)
							steps, L = update(L, steps, len(L)-5)
						else:
							break
						assert L[-1] == 0
					elif found >= 1 and found-1 not in pos and found+1 not in pos:
						# ... 0 _ [0] 1 _ ...
						if found+2 in triple:
							triple.remove(found+2)
						if L[-1] == 1:
							steps, L = update(L, steps, found-1)
							steps, L = update(L, steps, len(L)-4)
						elif triple:
							steps, L = update(L, steps, triple[0]-1)
							if triple[0] < found:
								found -= 3
							steps, L = update(L, steps, found-1)
							steps, L = update(L, steps, len(L)-4)
						else:
							break
						assert L[-1] == 0
					else:
						break
			if L[0] == 0:
				break
			if 0 in L[::3]:
				assert L[::3].index(0) < wf_check
				wf_check = L[::3].index(0)
			steps, L = update(L, steps, 0)
		assert L[0] == 0
		offset += L.index(1)
		del L[:L.index(1)]
		continue
	if 0 in L:
		offset -= 7-len(L)
		L = [0]*(7-len(L))+L
		assert(len(L) == 7)
		while endgame[L[0]*64+L[1]*32+L[2]*16+L[3]*8+L[4]*4+L[5]*2+L[6]] != 9:
			steps, L = update(L,steps,endgame[L[0]*64+L[1]*32+L[2]*16+L[3]*8+L[4]*4+L[5]*2+L[6]])
	return steps

Essayez-le en ligne!

Leaky Nun
la source
3

Python 3, ~ 179 pas pour n = 500 (en moyenne)

Une approche heuristique gourmande. Un peu lent mais fonctionne toujours. Utilise un solveur optimal pour les petites tailles.

def incomplete_groups(l):
    r = 0
    ones = 0
    for x in l:
        if x == "1":
            ones += 1
        else:
            if ones % 3:
                r += 1
            ones = 0
    # Ones at the end don't count as an incomplete group.

    return r

def move(l, i):
    return l[:i] + l[i+3:] + l[i:i+3]

def best_pos(l, hist):
    r = []
    cleanup = incomplete_groups(l) == 0

    candidates = []
    for i in range(len(l) - 3):
        block = l[i:i+3]
        if block == "111" and cleanup:
            return i
        elif block == "111":
            continue

        new = move(l, i)
        bad_start = i < 3 and "10" in l[:3]
        candidates.append((new not in hist, -incomplete_groups(new), bad_start, block != "000", i))

    candidates.sort(reverse=True)
    return candidates[0][-1]

def done(l):
    return list(l) == sorted(l)



class IDAStar:
    def __init__(self, h, neighbours):
        """ Iterative-deepening A* search.

        h(n) is the heuristic that gives the cost between node n and the goal node. It must be admissable, meaning that h(n) MUST NEVER OVERSTIMATE the true cost. Underestimating is fine.

        neighbours(n) is an iterable giving a pair (cost, node, descr) for each node neighbouring n
        IN ASCENDING ORDER OF COST. descr is not used in the computation but can be used to
        efficiently store information about the path edges (e.g. up/left/right/down for grids).
        """

        self.h = h
        self.neighbours = neighbours
        self.FOUND = object()


    def solve(self, root, is_goal, max_cost=None):
        """ Returns the shortest path between the root and a given goal, as well as the total cost.
        If the cost exceeds a given max_cost, the function returns None. If you do not give a
        maximum cost the solver will never return for unsolvable instances."""

        self.is_goal = is_goal
        self.path = [root]
        self.is_in_path = {root}
        self.path_descrs = []
        self.nodes_evaluated = 0

        bound = self.h(root)

        while True:
            t = self._search(0, bound)
            if t is self.FOUND: return self.path, self.path_descrs, bound, self.nodes_evaluated
            if t is None: return None
            bound = t

    def _search(self, g, bound):
        self.nodes_evaluated += 1

        node = self.path[-1]
        f = g + self.h(node)
        if f > bound: return f
        if self.is_goal(node): return self.FOUND

        m = None # Lower bound on cost.
        for cost, n, descr in self.neighbours(node):
            if n in self.is_in_path: continue

            self.path.append(n)
            self.is_in_path.add(n)
            self.path_descrs.append(descr)
            t = self._search(g + cost, bound)

            if t == self.FOUND: return self.FOUND
            if m is None or (t is not None and t < m): m = t

            self.path.pop()
            self.path_descrs.pop()
            self.is_in_path.remove(n)

        return m

def h(l):
    """Number of groups of 1 with length <= 3 that come before a zero."""
    h = 0
    num_ones = 0
    complete_groups = 0
    incomplete_groups = 0
    for x in l:
        if x == "1":
            num_ones += 1
        else:
            while num_ones > 3:
                num_ones -= 3
                h += 1
                complete_groups += 1
            if num_ones > 0:
                h += 1
                incomplete_groups += 1
            num_ones = 0

    return complete_groups + incomplete_groups

def neighbours(l):
    inc_groups = incomplete_groups(l)
    final = inc_groups == 0

    candidates = []
    for i in range(len(l) - 3):
        left = l[:i]
        block = l[i:i+3]
        right = l[i+3:]
        cand = (1, left + right + block, i)

        # Optimal choice.
        if final and (block != "111" or i >= len(l.rstrip("1"))):
            continue

        candidates.append(cand)

    candidates.sort(key=lambda c: c[2], reverse=True)

    return candidates


def is_goal(l):
    return all(l[i] <= l[i+1] for i in range(len(l)-1))

opt_solver = IDAStar(h, neighbours)

def partite(l):
    if isinstance(l, list):
        l = "".join(map(str, l))
    if len(l) < 10:
        return [i + 1 for i in opt_solver.solve(l, is_goal)[1]]
    moves = []
    hist = [l]
    while not done(l):
        i = best_pos(l, hist)
        l = move(l, i)
        moves.append(i+1)
        hist.append(l)
    return moves
orlp
la source