Usine d'ensachage de fruits

21

Votre mission est de construire un algorithme (programme ou fonction) qui puisse optimiser le conditionnement des fruits d'un tapis roulant dans des sacs à envoyer aux détaillants, optimisant pour un plus grand nombre de sacs.

Chaque sac doit peser au moins une certaine quantité, mais tout excédent est un manque à gagner, car ce poids pourrait être utilisé pour remplir un autre sac. Votre machine d'ensachage a toujours une longueur d'avance sur les nfruits de la file d'attente et peut uniquement choisir d'ajouter l'un de ces nfruits au sac (unique) en cours de traitement. Il ne peut pas regarder au-delà des npremiers éléments de la file d'attente. Le programme sait toujours exactement combien de poids il y a déjà dans le sac.

Une autre façon de visualiser cela est d'avoir un tapis roulant avec une zone de chargement de taille nà la fin, d'où un fruit doit être pris avant l'arrivée d'un nouveau fruit. Tout fruit restant et un sac non plein à la fin sont jetés.

Figure 1: Usine d'ensachage de fruits

Contributions

  • Liste / tableau des poids des fruits en file d'attente (entiers positifs)
  • Poids total minimum pour les sacs (entier positif)
  • Lookahead n(entier positif)

Production

Votre algorithme doit renvoyer pour tous les sacs le poids des fruits qu'ils contiennent, par tout moyen qui vous convient et votre langue, que ce soit stdin ou une valeur de retour ou autre chose. Vous devriez pouvoir exécuter le programme et calculer votre score en une minute sur votre ordinateur.

Exemple

Total weight 1000, lookahead of 3 and fruit queue: 
[171,163,172,196,156,175,162,176,155,182,189,142,161,160,152,162,174,172,191,185]

One possible output (indented to show how the lookahead affects the bagging):
[171,163,172,    156,175,    176]
                        [162,    155,182,189,    161,160]
                                                        [152,162,174,172,191,185]

Notation

Votre algorithme sera testé sur six passages sur un lot de 10000 oranges que j'ai préparé pour vous, sur des têtes de recherche allant de 2 à 7, inclus aux deux extrémités. Vous devez les emballer dans des sacs pesant au moins 1 000 unités. Les oranges sont normalement distribuées avec un poids moyen de 170 et un écart type de 13, si cela peut être utile.

Votre score sera la somme du nombre de sacs des six courses. Le score le plus élevé l'emporte. Les failles standard ne sont pas autorisées.

Exemple d'implémentation simple et passe-partout de suite de tests à Haskell

Angs
la source
7
Allez les gens, je pense qu'il y a encore quelques algorithmes de fruits à suspendre qui attendent d'être cueillis…
Angs
2
Les programmes peuvent-ils coder en dur le poids / la distribution moyens? (supposons que cela fonctionne aussi bien sur des lots similaires, bien sûr tout coder en dur est invalide car il détruit le but de l'anticipation limitée)
user202729
@ user202729: Oui, ils le peuvent.
Angs
Et tout coder en dur est de toute façon une faille standard interdite .
Angs
Je ne vois pas ce qu'est le lookhead
l4m2

Réponses:

8

Python 3, 9964 9981 sacs

L'idée de cette solution est similaire à celle de Jonathan, JayCe et fortraan, mais avec une fonction de notation =)

Cette solution ajoute les meilleurs sous-ensembles de la zone d'anticipation selon le score.

score fournit un ordre sur les sous-ensembles en utilisant le schéma suivant:

  • Un sous-ensemble remplissant un sac est meilleur qu'un autre
  • Un sous-ensemble remplissant un sac est meilleur qu'un autre s'il a moins d'excès de poids
  • Un sous-ensemble ne remplissant pas un sac est meilleur qu'un autre si sa moyenne est plus proche de ce qui devrait être dans un sac

expected_mean tente de prédire à quoi devraient ressembler les autres valeurs (en supposant que leur choix est optimal).

UPD :

Voici une autre observation: vous pouvez placer toutes les oranges du meilleur sous-ensemble dans le sac sans nuire aux performances de l'algorithme. En déplacer n'importe quelle partie permet toujours de déplacer le reste par la suite, et le reste devrait toujours être la meilleure option (sans nouvelles oranges) si la notation est correcte. De plus, de cette façon, il est possible d'améliorer dynamiquement l'ensemble des candidats à mettre dans le sac en voyant plus d'oranges avant de remplir le sac. Et vous voulez en savoir autant d'informations que possible, il est donc inutile de déplacer plus d'une orange dans le sac à un moment donné.

import itertools as it
import math
from functools import partial
from collections import Counter


mean, std = 170, 13


def powerset(list_, max_items):
    return it.chain.from_iterable(it.combinations(list_, r) for r in range(1, max_items + 1))


def expected_mean(w):
    spread = std * 1
    return max(mean - spread, min(mean + spread, w / max(1, round(w / mean))))


def score(weight_needed, candidate):
    c_sum = sum(candidate)
    c_mean = c_sum / len(candidate)
    if c_sum >= weight_needed:
        return int(-2e9) + c_sum - weight_needed
    return abs(expected_mean(weight_needed) - c_mean)


def f(oranges, min_bag_weight, lookahead):
    check = Counter(oranges)

    oranges = oranges.copy()
    result = []
    bag = []

    while oranges:
        weight_needed = min_bag_weight - sum(bag)

        lookahead_area = oranges[:lookahead]
        tail = oranges[lookahead:]

        to_add = min(powerset(lookahead_area, lookahead),
                     key=partial(score, weight_needed))
        to_add = min(powerset(to_add, 1),
                     key=partial(score, weight_needed))

        bag.extend(to_add)
        for x in to_add:
            lookahead_area.remove(x)
        oranges = lookahead_area + tail

        if sum(bag) >= min_bag_weight:
            result.append(bag)
            bag = []

    assert check == Counter(oranges) + Counter(bag) + sum(map(Counter, result), Counter())

    return result


if __name__ == '__main__':
    with open('oranges') as file:
        oranges = list(map(int, file))
    res = [f(oranges, 1000, l) for l in range(2, 7+1)]
    print(sum(map(len, res)))

Essayez!

Alex
la source
Très agréable! Il obtient 1672 avec un lookahead de 7, jamais vu un aussi haut.
Angs
(il semble que le deuxième argument de votre powersetfonction soit redondant dans ce cas car il est de len(list_)toute façon égal à ?)
user202729
@user J'ai expérimenté ce paramètre dans la version précédente. Le supprimera probablement plus tard
Alex
1
Félicitations d'avoir découvert la puissante combinaison du meilleur élément unique du meilleur sous-ensemble et d'avoir également le meilleur score! La prime vous appartient.
Angs
Un simple expected_mean(w)qui donne aussi de bons résultats:return (w+2) / max(1, round((w+2) / mean))
Angs
10

Python 3 , 9796 sacs

S'appuyant sur la réponse de Jonathan:

import itertools as it

def powerset(iterable):
    s = list(iterable)
    return it.chain.from_iterable(it.combinations(s, r) for r in range(len(s)+1))

def f(a,m,l):
 r=[];b=[]
 while a:
  c =  min(list(powerset(a[:l])),key=lambda w: abs(sum(b)+sum(w)-m))
  if sum(c)==0:
   c = a[:l]
  b+=[a.pop(a.index(min(c,key=lambda w: abs(sum(b)+w-m))))]
  if sum(b)>=m:r+=[b];b=[]
 return r

Cela repose sur le jeu de puissance du livre de cuisine d'itertool. Trouve d'abord le sous-ensemble optimal du tampon en fonction de la minimisation de la différence par rapport au poids cible pour tous les sous-ensembles, puis sélectionne un élément de ce sous-ensemble en fonction du même critère. Si aucun sous-ensemble optimal ne sélectionne l'ensemble du tampon.

JayCe
la source
Bienvenue chez PPCG!
Martin Ender
@MartinEnder Merci Martin pour le vote positif de bienvenue :)
JayCe
1
Ah oui j'ai raté un truc là-bas ... Je n'ai aucun problème avec ça comme autre réponse!
Jonathan Allan
1
@JonathanAllan Merci Jonathan J'ai raccourci ma réponse pour vous remercier sans toutes les excuses. Cela peut être amélioré en utilisant le fait qu'il s'agit d'une distribution normale (170,13) - je suis sûr que la probabilité d'obtenir un meilleur fruit dans les prochaines séries peut être utilisée.
JayCe
@JayCe semble dangereusement proche de l'erreur du joueur.
qwr
6

C ++ 17, 9961,58 (moyenne sur certaines graines aléatoires)

(faites défiler vers le bas pour l'explication si vous ne connaissez pas le C ++)

#include<iostream>

#include<vector>
#include<random>

std::mt19937 engine(279); // random engine
// random distribution of the oranges
std::normal_distribution dist (170.,13.);

int constexpr N_NEW_ORANGES=7;

/** Input format: Current remaining weight of the bag (remain) and 
the weight of the oranges (weights)
Output: the index of the orange to be pick.
*/
struct pick_orange{
    std::vector<int>weights,sum_postfix;int remain;

    /// returns {min excess, mask}, (index) is the LSB
    std::pair<int,int> backtrack(int index, int remain) {
        if(sum_postfix[index]<remain)return {1e9,0};
        int min_excess=1e9, good_mask=0;
        for(int next=index;next<N_NEW_ORANGES;++next){
            if(weights[next]==remain){
                return {0, 1<<(next-index)};
            }else if(weights[next]>remain){
                if(weights[next]-remain<min_excess){
                    min_excess=weights[next]-remain;
                    good_mask=1<<(next-index);
                }
            }else{
                auto[excess,mask]=backtrack(next+1,remain-weights[next]);
                if(excess<min_excess){
                    min_excess=excess;
                    good_mask=(mask<<1|1)<<(next-index);
                }
            }
        }
        return {min_excess,good_mask};
    } 

    int ans;

    pick_orange(std::vector<int> weights_,int remain_)
        :weights(std::move(weights_)),remain(remain_){

        int old_size=weights.size();

        std::vector<int> count (N_NEW_ORANGES, 0);
        weights.resize(N_NEW_ORANGES, 0);

        sum_postfix.resize(N_NEW_ORANGES+1);
        sum_postfix.back()=0;

        for(int _=0; _<500; ++_){

            for(int i=old_size;i<N_NEW_ORANGES;++i)
                weights[i] = dist(engine);

            // prepare sum postfix
            for(int i=N_NEW_ORANGES;i-->0;)
                sum_postfix[i]=weights[i]+sum_postfix[i+1];

            // auto[excess,mask]=backtrack(0,remain);
            int mask = backtrack(0,remain).second;

            for(int i=0; 

                mask
                // i < N_NEW_ORANGES

                ; mask>>=1, ++i){

                // if(mask&1)std::cout<<'(';
                // std::cout<<weights[i];
                // if(mask&1)std::cout<<')';
                // std::cout<<' ';

                count[i]+=mask&1;
            }

            // std::cout<<"| "<<remain<<" | "<<excess<<'\n';

        }

        std::vector<double> count_balanced(old_size, -1);
        for(int i=0;i<old_size;++i){
            if(count_balanced[i]>-1)continue;
            int sum=0,amount=0;
            for(int j=i;j<old_size;++j)
                if(weights[j]==weights[i]){sum+=count[j];++amount;}

            double avg=sum;avg/=amount;
            for(int j=i;j<old_size;++j)
                if(weights[j]==weights[i])count_balanced[j]=avg;
        }

        ans=old_size-1;
        for(int i=ans;i-->0;)
            if(count_balanced[i]>count_balanced[ans])ans=i;
        // Fun fact: originally I wrote `<` for `>` here and wonder
        // why the number of bags is even less than that of the
        // randomized algorithm
    }

    operator int()const{return ans;}
};


#include<iostream>
#include<fstream>
#include<algorithm>

int main(){
    // read input from the file "data"
    std::ifstream data ("data");
    std::vector<int> weights;
    int weight;while(data>>weight)weights.push_back(weight);

    int constexpr BAG_SIZE=1000;
    int total_n_bag=0;
    for(int lookahead=2;lookahead<=7;++lookahead){
        auto weights1=weights;
        std::reverse(weights1.begin(),weights1.end());

        int remain=BAG_SIZE,n_bag=0;
        std::vector<int> w;
        for(int _=lookahead;_--;){
            w.push_back(weights1.back());
            weights1.pop_back();
        }
        while(!weights1.empty()){
            int index=pick_orange(w,remain);

            remain-=w[index];
            if(remain<=0){
                ++n_bag;remain=BAG_SIZE;

                if(n_bag%100==0)
                    std::cout<<n_bag<<" bags so far..."<<std::endl;
            }
            w[index]=weights1.back();weights1.pop_back();
        }

        while(!w.empty()){
            int index=pick_orange(w,remain);
            remain-=w[index];
            if(remain<=0){++n_bag;remain=BAG_SIZE;}
            w.erase(w.begin()+index);
        }

        std::cout<<"lookahead = "<<lookahead<<", "
            "n_bag = "<<n_bag<<'\n';
        total_n_bag += n_bag;
    }

    std::cout<<"total_n_bag = "<<total_n_bag<<'\n';
}

// Fait amusant: à l'origine, j'ai écrit <pour >ici et je me demande
// pourquoi le nombre de sacs est encore inférieur à celui de
// l'algorithme randomisé

(si <est utilisé, l'algorithme essaie de minimiser le nombre de sacs)

Inspiré par cette réponse .

Lien TIO pour 250 répétitions: essayez-le en ligne!


Définit une fonction (en fait, elle ressemble à une fonction, c'est une structure) pick_orangequi, compte tenu vector<int> weightsdu poids des oranges et int remaindu poids restant du sac, renvoie l'index de l'orange à cueillir.

Algorithme:

les 500temps de répétition {
génèrent des oranges aléatoires (fausses) (distribution normale avec une moyenne de 170 et stddev 13) jusqu'à ce qu'il y ait des N_NEW_ORANGES=7oranges
choisissent un sous-ensemble dont la somme est la plus petite et non inférieure à remain(la fonction backtrackfait cela)
marque toutes les oranges de ce sous-ensemble comme bonnes
}

moyenne du nombre de fois où une orange est marquée comme bonne des (vraies) oranges de poids égal
renvoie la meilleure orange


Il y a 3 constantes codées en dur dans le programme qui ne peuvent pas être déduites du problème:

  • La graine aléatoire (ce n'est pas important)
  • N_NEW_ORANGES(la longueur de prédiction). L'augmentation de ce qui rend le programme s'exécute de manière exponentielle plus longue (car backtrack)
  • nombre de répétitions. Augmenter cela rend le programme plus linéaire.
user202729
la source
D'accord. Changer la graine par celle qui donne la meilleure réponse semble cependant être optimisé pour le cas de test, vous devriez donc prendre la moyenne de quelques, disons 10, graines différentes comme score. Pourriez-vous publier un lien TIO vers une version qui fait moins de répétitions pour réduire le temps d'exécution?
Angs
Je l'ai finalement compilé après avoir obtenu un nouveau gcc. Sur 50 pistes avec des graines aléatoires, il a obtenu une moyenne de 9961,58. Très impressionnant encore. Mais je me suis demandé - votre algorithme s'entraîne à nouveau sur chaque sac, y a-t-il un ensemble fixe de meilleures valeurs qui pourraient être mémorisées?
Angs
@Angs Je ne pense pas qu'il existe un moyen d'utiliser la mémorisation pour aider dans ce cas. Une idée?
user202729
Mon système d'exploitation est livré avec gcc 5.4.0, il a eu quelques problèmes avec invalid use of template-name ‘std::normal_distribution’. Aucun problème avec gcc 7.1.0.
Angs
4

Python 2 , 9756 sacs

Faisons rouler l'orange ...

def f(a,m,l):
 r=[];b=[]
 while a:
  b+=[a.pop(a.index(min(a[:l],key=lambda w:abs(sum(b)+w-m))))]
  if sum(b)>=m:r+=[b];b=[]
 return r

Essayez-le en ligne!

Cueille toujours les fruits du tampon, ce qui minimise la différence absolue entre le nouveau poids et le poids cible.

Jonathan Allan
la source
4

Python 3, 9806 sacs

S'appuyant sur les réponses de Jonathan et JayCe:

import itertools as it

def powerset(iterable):
    s = list(iterable)
    return it.chain.from_iterable(it.combinations(s, r) for r in range(len(s)+1))

def f(a,m,l):
 r=[];b=[]
 while a:
  c =  min(list(powerset(list(reversed(sorted(a[:l]))))),key=lambda w: abs((sum(b)+sum(w))-m))
  if sum(c)==0:
   c = a[:l]
  b+=[a.pop(a.index(min(c,key=lambda w: abs((sum(b)+w)-m))))]
  if sum(b)>=m:r+=[b];b=[]
 return r

Essayez-le en ligne!

Comment ça marche

Supposons que le sac contient 900 unités et qu'il y a 2 fruits disponibles: 99 unités et 101 unités. Si le fruit de 99 unités est plus proche du début de la liste d'anticipation, alorsmin le sélectionnera au lieu de 101. Si cela se produit, nous aurions maintenant besoin d'un autre fruit pour remplir 1 unité restante nécessaire. J'ai changé le programme pour privilégier les fruits de plus grande valeur dans ces cas.

Il le fait en triant puis en inversant la liste d'anticipation avant le paramétrage.

fortraan
la source
4

PHP, 9975 sacs

  • Si possible, optez pour 5 oranges
  • Lors du démarrage du sac, choisissez une valeur extrême, équilibrez plus tard
  • Si possible, remplissez le sac immédiatement
  • Essayez de garder le poids du sac proche de la courbe estimée (n * 200 pour 5 sacs, n * 167 pour 6 sacs, etc.)

la plus longue de toutes les soumissions mais devrait être lisible

class Belt
{
    private $file;
    private $windowSize;
    private $buffer = [];

    public function __construct($filename, $windowSize) {
        $this->file = new \SplFileObject($filename);
        $this->windowSize = $windowSize;
        $this->loadBuffer();
    }

    public function reset($windowSize) {
        $this->file->seek(0);
        $this->windowSize = $windowSize;
        $this->buffer = [];
        $this->loadBuffer();
    }

    public function peekBuffer() {
        return $this->buffer;
    }

    public function pick($index) {
        if (!array_key_exists($index, $this->buffer)) {
            return null;
        }
        $value = $this->buffer[$index];
        unset($this->buffer[$index]);
        $this->buffer = \array_values($this->buffer);
        $this->loadBuffer();
        return $value;
    }

    private function loadBuffer() {
        for ($c = count($this->buffer); $c < $this->windowSize; $c++) {
            if ($this->file->eof()) {
                return;
            }
            $line = $this->file->fgets();
            if (false !== $line && "" !== $line) {
                $this->buffer[] = trim($line);
            }
        }
    }
}

class Packer
{

    const BAG_TARGET_WEIGHT = 1000;
    const MEAN_WEIGHT = 170;
    const MEAN_COUNT = 6; //ceil(self::BAG_WEIGHT/self::MEAN_WEIGHT);
    const MEAN_TARGET_WEIGHT = 167; //ceil(self::BAG_WEIGHT/self::MEAN_COUNT);

    public static function pack(Belt $belt, Picker $picker) {
        $bag = ["oranges" => [], "buffers" => []];
        $bags = [];
        while ($oranges = $belt->peekBuffer()) {

            $index = $picker->pick($oranges, \array_sum($bag["oranges"]));
            $orange = $belt->pick($index);
            $bag["oranges"][] = $orange;
            $bag["buffers"][] = $oranges;

            if (\array_sum($bag["oranges"]) >= self::BAG_TARGET_WEIGHT) {
                $bags[] = $bag;
                $bag = ["oranges" => [], "buffers" => []];
            }
        }
        return $bags;
    }
}

class Base
{
    public static function bestPermutation($elements, $weight = 0) {
        if (\array_sum($elements) < Packer::BAG_TARGET_WEIGHT - $weight) {
            return null;
        }
        $permute = function ($weight, $elements) use (&$permute) {
            if ($weight >= Packer::BAG_TARGET_WEIGHT) {
                return [];
            }
            $best = \PHP_INT_MAX;
            $bestElements = [];
            foreach ($elements as $key => $value) {
                $sum = $weight + $value;
                $els = [$value];
                if ($sum < Packer::BAG_TARGET_WEIGHT) {
                    $subSet = $elements;
                    unset($subSet[$key]);
                    $els = $permute($weight + $value, $subSet);
                    $els[] = $value;
                    $sum = $weight + \array_sum($els);
                }
                if ($sum >= Packer::BAG_TARGET_WEIGHT && $sum < $best) {
                    $best = $sum;
                    $bestElements = $els;
                }
            }
            return $bestElements;
        };
        $best = $permute($weight, $elements);

        return $best;
    }

    public function pickLightestOutOfHeavierThan($buffer, $targetWeight) {
        $b = -1;
        $bW = PHP_INT_MAX;
        foreach ($buffer as $key => $value) {
            if ($targetWeight <= $value && $value < $bW) {
                $b = $key;
                $bW = $value;
            }
        }
        return $b;
    }

    public function pickClosestTo($buffer, $targetWeight) {
        $b = -1;
        $bW = PHP_INT_MAX;
        foreach ($buffer as $key => $value) {
            $diff = \abs($targetWeight - $value);
            if ($diff < $bW) {
                $b = $key;
                $bW = $diff;
            }
        }
        return $b;
    }

    public function pickFurthestFrom($buffer, $targetWeight) {
        $b = -1;
        $bW = \PHP_INT_MIN;
        foreach ($buffer as $key => $value) {
            $diff = \abs($targetWeight - $value);
            if ($diff > $bW) {
                $b = $key;
                $bW = $diff;
            }
        }
        return $b;
    }

    public function findMax($buffer) {
        $i = -1;
        $m = 0;
        foreach ($buffer as $k => $v) {
            if ($v > $m) {
                $m = $v;
                $i = $k;
            }
        }
        return $i;
    }

    public function findMin($buffer) {
        $i = -1;
        $m = \PHP_INT_MAX;
        foreach ($buffer as $k => $v) {
            if ($v < $m) {
                $m = $v;
                $i = $k;
            }
        }
        return $i;
    }

    public function minimalOrangeCount($buffer, $weight) {
        $elementsToAdd = ceil((Packer::BAG_TARGET_WEIGHT - $weight) / Packer::MEAN_WEIGHT);
        $buffer = \array_merge($buffer,
            \array_fill(0, \floor($elementsToAdd / 2), Packer::MEAN_WEIGHT - 7),
            \array_fill(0, \floor($elementsToAdd / 2), Packer::MEAN_WEIGHT + 7),
            \array_fill(0, $elementsToAdd - \floor($elementsToAdd / 2) * 2, Packer::MEAN_WEIGHT)
        );
        \rsort($buffer);
        $orangeCount = 0;
        foreach ($buffer as $w) {
            $weight += $w;
            $orangeCount++;
            if ($weight >= Packer::BAG_TARGET_WEIGHT) {
                return $orangeCount;
            }
        }
        return $orangeCount + (Packer::BAG_TARGET_WEIGHT - $weight) / Packer::MEAN_WEIGHT;
    }
}


class Picker extends Base
{
    public function pick($buffer, $weight) {
        $weightNeeded = Packer::BAG_TARGET_WEIGHT - $weight;

        $minimalOrangeCount = $this->minimalOrangeCount($buffer, $weight);
        $orangeTargetWeight = ceil($weightNeeded / $minimalOrangeCount);

        if (0 === $weight) {
            $mean = \array_sum($buffer) / count($buffer);
            if ($mean > $orangeTargetWeight) {
                return $this->findMin($buffer);
            } elseif ($mean < $orangeTargetWeight) {
                return $this->findMax($buffer);
            }
            return $this->pickFurthestFrom($buffer, $orangeTargetWeight);
        }

        $i = $this->pickLightestOutOfHeavierThan($buffer, $weightNeeded);
        if (-1 !== $i) {
            return $i;
        }
        $i = $this->pickClosestTo($buffer, $orangeTargetWeight);
        return -1 !== $i ? $i : 0;
    }
}

$bagCount = 0;
$belt = new Belt(__DIR__ . "/oranges.txt", 0);
for ($l = 2; $l <= 7; $l++) {
    $belt->reset($l);
    $bags = Packer::pack($belt, new Picker());
    $bagCount += count($bags);
    printf("%d -> %d\n", $l, count($bags));
}
echo "Total: $bagCount\n";

2 -> 1645 3 -> 1657 4 -> 1663 5 -> 1667 6 -> 1671 7 -> 1672 Total: 9975

Essayez-le

mleko
la source
Agréable! Ce qui est surprenant pour moi, c'est qu'il utilise le nombre actuel d'articles - je me demande si cela peut être pris en compte. Après tout, peu importe qu'il y ait 3 articles pesant 120 chacun ou 3 articles pesant 160 chacun.
Angs
@Angs c'est probablement possible. Le nombre actuel d'articles est apparu comme un raccourci simple pour l'idée "Hé, parfois il est possible de faire un sac de 5 articles" et j'ai essayé de faire fonctionner les sacs de 5 articles. Avec du temps libre, des améliorations viendront :)
mleko
3

Python 3, 9855 9928 9947 9956 9964 sacs

Basé sur le code de démarrage de Jonathan Allan, mais non golfé pour être lisible.

Idée: Depuis 1000/170 = 5,88, nous essayons de sélectionner des fruits proches de 1000/6 (j'ai tripoté les constantes magiques). Cependant, si le dernier fruit du sac peut minimiser les déchets, nous l'utilisons à la place.

Cette solution a des objectifs de somme de sac pour chaque fruit ajouté. Je vais probablement m'arrêter ici. J'ai utilisé Nelder-Mead pour trouver mon targetstableau:

[  165.79534144   343.58443287   522.58081597   680.76516204   845.93431713 1063.17204861]
def f(a, m, l, targets):
    bags = []
    bag = []
    bag_sum = 0
    while a:
        buffer = a[:l]
        finishers = tuple(filter(lambda w: bag_sum + w >= m, buffer))
        if finishers:
            next_fruits = [min(finishers)]

        else:
            ind = len(bag)
            next_fruits = [min(buffer, key=lambda w: abs(targets[ind]-bag_sum-w))]

        for next_fruit in next_fruits:
            bag.append(a.pop(a.index(next_fruit)))
            bag_sum += bag[-1]

        if sum(bag) >= m:
            bags.append(bag)
            bag = []  # Reset bag
            bag_sum = 0

    return bags

9956 sacs

from itertools import combinations

def f(a,m,l):
    bags = []
    bag = []
    while a:
        buffer = a[:l]
        next_fruit = None
        single_fruit = True

        finishers = [w for w in buffer if sum(bag) + w >= m ]
        if finishers: next_fruit = min(finishers)

        if not next_fruit:
            if len(buffer) >= 4 and sum(bag) < 600:
                next_fruits = min(combinations(buffer, 2), key=
                                  lambda ws: abs(2*169-sum(ws)))
                for fruit in next_fruits:
                    bag.append(a.pop(a.index(fruit)))

                single_fruit = False  # Skip adding single fruit

            else:
                next_fruit = min(buffer, key=lambda w: abs(171.5-w))

        if single_fruit:
            bag.append(a.pop(a.index(next_fruit)))

        if sum(bag)>=m:
            bags.append(bag)
            bag = []

    return bags


oranges = [int(x.strip()) for x in open("fruit.txt").readlines()]
bagLists = []
for lookahead in (2,3,4,5,6,7):
    bagLists.append(f(oranges[:], 1000, lookahead))


totalBagsOver1000 = sum(map(len, bagLists))
print('bags: ', (totalBagsOver1000))

Le programme de sacs 9947 est particulièrement simple:

def f(a,m,l):
    bags = []
    bag = []
    while a:
        buffer = a[:l]
        next_fruit = None

        finishers = [w for w in buffer if sum(bag) + w >= m ]
        if finishers: next_fruit = min(finishers)

        if not next_fruit:
            next_fruit = min(buffer, key=lambda w: abs(171.5-w))

        bag.append(a.pop(a.index(next_fruit)))

        if sum(bag)>=m:
            bags.append(bag)
            bag = []

    return bags
qwr
la source
1
Bien! Btw, juste ramasser le dernier article afin de minimiser les déchets est assez puissant en soi et donne 9862 sacs.
Angs
Comment en êtes-vous arrivé à cela targets? Formation sur les données aléatoires?
Alex
1
@Alex Je l'ai dit: méthode Nelder-Mead (avec des sacs négatifs comme fonction de perte)
qwr
2

Rubis , 9967 sacs

def pick a, n
  if a.sum < n
    #return a.max
    d = n % 170
    return a.min_by{|w|
      [(w - d).abs, (w - d - 170).abs].min
    }
  end
  
  subsets = (0..a.length).map do |i|
    a.combination(i).to_a
  end.flatten(1)
  
  subsets.select!{|s|s.sum >= n}
  least_overkill = subsets.min_by{|s|s.sum}
  #puts "best: #{least_overkill.sort}"
  return least_overkill.min
end

def run list, weight, n
  bags = 0
  in_bag = 0
  while list.size > 0
    x = pick(list[0...n], weight - in_bag)
    i = list.index(x)
    raise new Exeption("not a valid weight") if(!i || i >= n)
    list.delete_at i
    in_bag += x
    if in_bag >= weight
      #puts in_bag
      in_bag = 0
      bags += 1
    end
  end
  return bags
end

Essayez-le en ligne!

Si vous avez suffisamment de poids pour remplir le sac, trouvez le sous-ensemble le plus léger qui peut remplir le sac et utilisez l'orange le plus clair de ce sous-ensemble. Sinon, rapprochez le plus possible le poids restant d'un multiple de 170.

MegaTom
la source
2

Raquette / Scheme, 9880 sacs

Pour décider du fruit à ajouter au sac, comparez le poids optimal du sac au poids du sac avec le fruit supplémentaire. Si c'est le poids optimal, utilisez-le. Si elle est en surpoids, minimisez l'excédent. En cas d'insuffisance pondérale, minimisez l'excédent après avoir tenté de laisser un écart optimal.

;; types

(define-struct bagger (fruit look tray bag bags)) ; fruit bagger

;; constants

(define MBW 1000) ; minimum bag weight
(define AFW 170) ; average piece-of-fruit weight
(define GAP (- MBW AFW)) ; targeted gap
(define FRUIT (file->list "fruit-supply.txt")) ; supplied fruit

;; utility functions

(define (weigh-it ls)
  (if (empty? ls)
      0
      (+ (car ls) (weigh-it (cdr ls)))))

(define (ref-to-car ls ref)
  (if (zero? ref)
      ls
      (let ((elem (list-ref ls ref)))
        (cons elem (remove elem ls)))))

;; predicates

(define (bag-empty? bgr) (empty? (bagger-bag bgr)))
(define (bag-full? bgr) (>= (weigh-it (bagger-bag bgr)) MBW))
(define (fruit-empty? bgr) (empty? (bagger-fruit bgr)))
(define (tray-empty? bgr) (empty? (bagger-tray bgr)))
(define (tray-full? bgr) (= (length (bagger-tray bgr)) (bagger-look bgr)))
(define (target-not-set? target value) (and (empty? target) (empty? value)))

;; pick best piece of fruit

(define (pf-rec tray bag i target value diff)
  (if (or (target-not-set? target value) (< diff value))
      (pick-fruit (cdr tray) bag (add1 i) i diff)
      (pick-fruit (cdr tray) bag (add1 i) target value)))

(define (pick-fruit tray bag i target value)
  (if (empty? tray)
      target
      (let ((weight (weigh-it (cons (car tray) bag))))
        (cond
          ((= weight MBW) i)
          ((> weight MBW) (pf-rec tray bag i target value (- weight MBW)))
          ((< weight MBW)
           (if (> weight GAP)
               (pf-rec tray bag i target value (- weight GAP))
               (pf-rec tray bag i target value (modulo (- MBW weight) AFW))))))))

;; load tray, bag, bags, etc.

(define (load-bag bgr)
  (let* ((tray (bagger-tray bgr))
         (bag (bagger-bag bgr))
         (weight (+ (weigh-it tray) (weigh-it bag))))
    (if (= weight MBW)
        (struct-copy bagger bgr
                     (tray empty)
                     (bag (append tray bag)))
        (let ((new-tray (ref-to-car tray (pick-fruit tray bag 0 empty empty))))
          (struct-copy bagger bgr
                       (tray (cdr new-tray))
                       (bag (cons (car new-tray) bag)))))))

(define (load-bags bgr)
  (struct-copy bagger bgr
               (bag empty)
               (bags (cons (bagger-bag bgr) (bagger-bags bgr)))))

(define (load-tray bgr)
  (struct-copy bagger bgr
               (fruit (cdr (bagger-fruit bgr)))
               (tray (cons (car (bagger-fruit bgr)) (bagger-tray bgr)))))

;; run the bagger factory

(define (run-bagger-aux bgr)
  (cond
    ((bag-full? bgr) (run-bagger-aux (load-bags bgr)))
    ((bag-empty? bgr)
     (cond
       ((tray-full? bgr) (run-bagger-aux (load-bag bgr)))
       ((tray-empty? bgr)
        (if (fruit-empty? bgr)
            (length (bagger-bags bgr))
            (run-bagger-aux (load-tray bgr))))
       (else
        (if (fruit-empty? bgr)
            (run-bagger-aux (load-bag bgr))
            (run-bagger-aux (load-tray bgr))))))
    (else
     (cond
       ((tray-full? bgr) (run-bagger-aux (load-bag bgr)))
       ((tray-empty? bgr)
        (if (fruit-empty? bgr)
            (run-bagger-aux (load-bags bgr))
            (run-bagger-aux (load-tray bgr))))
       (else
        (if (fruit-empty? bgr)
            (run-bagger-aux (load-bag bgr))
            (run-bagger-aux (load-tray bgr))))))))

(define (run-bagger fruit look)
  (run-bagger-aux (make-bagger fruit look empty empty empty)))

;; stackexchange problem run

(define (run-problem fruit looks)
  (if (empty? looks)
      0
      (+ (run-bagger fruit (car looks)) (run-problem fruit (cdr looks)))))

(run-problem FRUIT '(2 3 4 5 6 7)) ; result = 9880
Kevin
la source
1

Haskell , 9777 sacs

Ce fut ma première tentative:

  • il remplit avidement un sac avec un lot quand il le pouvait,
  • ou rincé toutes les oranges dans le sac quand il ne le pouvait pas.
options[]=[(0,([],[]))]
options(first:rest)=[option|(sum,(now,later))<-options rest,
 option<-[(sum+first,(first:now,later)),(sum,(now,first:later))]]
bags _[_]_=[]
bags(w_sum,w_bag)(w_kilo:w_iyts)w_view=
 let(w_take,w_remd)=splitAt(w_view)w_iyts;
     w_fill=filter((>=(w_kilo-w_sum)).fst)(options w_take)
 in if null w_fill then bags(w_sum+sum w_take,w_bag++w_take)(w_kilo:w_remd)w_view
    else let(_,(w_now,w_later))=minimum w_fill in
         (w_bag++w_now):bags(0,[])(w_kilo:w_later++w_remd)w_view
main=print.sum$map(length.bags(0,[])(1000:batch))[2..7]

Essayez-le en ligne!

Roman Czyborra
la source
1

Haskell , 9981 sacs

Les AngsJonathan AllanJayCefortraanAlexRoman Czyborra codegolf python pourrait retourner à Haskell pour plus de pureté mathématique le long du même courant de pensée

  • une seule orange est renvoyée avant qu'une nouvelle orange ne soit ajoutée
  • le biais préfère des fruits suffisants ((<miss)==False<True )
  • le biais préfère les fruits proches du remplissage entier le plus probable
  • pour cet entier inversez le à
    (m-n)/sqrt(n)==(n+1-m)/sqrt(n+1) <==> n=sqrt(m^2-1/4)-1/2 partir d'un https://en.wikipedia.org/wiki/Sum_of_normally_distributed_random_variables

    https://m.wolframalpha.com/input/?i=plot+abs (1-x) * sqrt (1), abs (2-x) * sqrt (2), abs (3-x) * sqrt ( 3), abs (4-x) * sqrt (4)

assaisonné de quelques inutilités inutiles

subsets[]=[[]];subsets(f:t)=[r|s<-subsets t,r<-[s,f:s]]
mean=uncurry(div).(id***max 1).(sum&&&length)
bags[]_ _=[];bags batch(miss:have)n=let
 goal=div miss$ceiling(sqrt((fromIntegral miss/170)^2+1/4)-1/2)
 best=minimumBy.comparing.(((<miss)&&&(abs.(goal-))).); desk=take n batch
 pick=best id.best(if sum desk<miss then mean else sum).filter(>[]).subsets$desk
 in if pick < miss then bags(delete pick batch)(miss-pick:pick:have)n
       else (pick:have):bags(delete pick batch)[miss+sum have]n
main=print$id&&&sum$map(length.bags batch[1000])[2..7]

Essayez-le en ligne!

sans donner un gain numérique différent au-dessus des 9981 filets d'oranges récoltés auparavant tandis que mon emballeur de sacs 10k011 saisissant des oranges impropres de retour de sacs non fermés a été disqualifié par user69850 en persona user202729Jo Kingovs donc la générosité méritée est allée à Alex

GIMME BOUNTY!

Roman Czyborra
la source