Obstverpackungsfabrik

21

Ihre Mission ist es, einen Algorithmus (Programm oder Funktion) zu entwickeln, der das Verpacken von Obst von einem Förderband in Säcke, die an Einzelhändler versandt werden sollen, optimiert und für die meisten Säcke optimiert.

Jeder Beutel muss mindestens eine bestimmte Menge wiegen, aber jeder Überschuss geht als Gewinn verloren, da dieses Gewicht zum Befüllen eines anderen Beutels verwendet werden könnte. Ihre Verpackungsmaschine hat immer eine Vorausschau nach nFrüchten aus der Warteschlange und kann diese nFrüchte möglicherweise nur der (einzelnen) Tasche hinzufügen , die gerade verarbeitet wird. Es kann nicht über die nersten Elemente in der Warteschlange hinaussehen. Das Programm weiß immer genau, wie viel Gewicht sich bereits in der Tasche befindet.

Ein anderer Weg, dies zu visualisieren, ist ein Förderband mit einer Ladefläche nam Ende, von dem eine Frucht entnommen werden muss, bevor eine neue Frucht ankommt. Eventuell übrig gebliebene Früchte und ein nicht voller Beutel am Ende werden verworfen.

Abbildung 1: Obstverpackungsfabrik

Eingänge

  • Liste / Anordnung der Fruchtgewichte in der Warteschlange (positive ganze Zahlen)
  • Mindestgesamtgewicht für Beutel (positive ganze Zahl)
  • Lookahead n(positive ganze Zahl)

Ausgabe

Ihr Algorithmus sollte für alle Beutel die Gewichte der Früchte in ihnen zurückgeben, mit welchen Mitteln auch immer dies für Sie und Ihre Sprache zweckmäßig ist, sei es der Standardwert oder ein Rückgabewert oder etwas anderes. Sie sollten in der Lage sein, das Programm auszuführen und Ihre Punktzahl in einer Minute auf Ihrem Computer zu berechnen.

Beispiel

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]

Wertung

Ihr Algorithmus wird in sechs Läufen mit einer Charge von 10000 Orangen, die ich für Sie vorbereitet habe, auf Lookaheads von 2 bis 7 einschließlich an beiden Enden getestet . Sie sind in Säcke mit einem Gewicht von mindestens 1000 Stück zu verpacken. Die Orangen werden normalerweise mit einem Durchschnittsgewicht von 170 und einer Standardabweichung von 13 verteilt, wenn dies hilfreich ist.

Ihre Punktzahl ist die Summe der Taschen aus den sechs Läufen. Die höchste Punktzahl gewinnt. Standardlücken sind nicht zulässig.

Einfache Beispielimplementierung und Test-Suite-Boilerplate in Haskell

Angs
quelle
7
Komm schon Leute, ich denke, es gibt immer noch einige niedrig hängende Obstalgorithmen, die noch ausgewählt werden müssen ...
Angs
2
Können Programme das mittlere Gewicht / die Verteilung fest codieren? (Nehmen wir an, dass es auf ähnlichen Stapeln gleich gut funktioniert, natürlich ist alles durch Hardcodierung ungültig, da es den Zweck des eingeschränkten Lookaheads zerstört.)
user202729
@ user202729: Ja, das können sie.
Angs
Und das Hardcodieren von allem ist sowieso eine verbotene Standardlücke .
Angs
Ich kann nicht sehen, was Lookhead ist
l4m2

Antworten:

8

Python 3, 9964 9981 Taschen

Die Idee dieser Lösung ähnelt der von Jonathan, JayCe und fortraan, jedoch mit einer Bewertungsfunktion =)

Diese Lösung fügt die besten Teilmengen des Lookahead-Bereichs gemäß dem an score.

score Liefert eine Reihenfolge über die Teilmengen nach folgendem Schema:

  • Eine Untergruppe, die eine Tasche vervollständigt, ist besser als eine, die es nicht ist
  • Eine Untergruppe, die eine Tasche vervollständigt, ist besser als eine andere, wenn sie weniger Übergewicht hat
  • Eine Untergruppe, die eine Tasche nicht vervollständigt, ist besser als eine andere, wenn ihr Mittelwert näher an dem liegt, was in einer Tasche erwartet wird

expected_mean versucht vorherzusagen, wie der Rest der Werte aussehen soll (vorausgesetzt, ihre Auswahl ist optimal).

UPD :

Hier ist eine weitere Beobachtung: Sie können alle Orangen aus der besten Teilmenge in den Beutel legen, ohne die Leistung des Algorithmus zu beeinträchtigen. Wenn Sie einen Teil davon verschieben, können Sie den Rest danach verschieben, und der Rest sollte immer noch die beste Option sein (ohne neue Orangen), wenn die Wertung stimmt. Darüber hinaus besteht auf diese Weise die Möglichkeit, die Menge der Kandidaten, die in den Beutel gegeben werden sollen, dynamisch zu verbessern, indem vor dem Befüllen des Beutels mehr Orangen zu sehen sind. Und Sie möchten so viele Informationen wie möglich haben. Es macht also keinen Sinn, mehr als eine Orange gleichzeitig in die Tasche zu legen.

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)))

Versuch es!

Alex
quelle
Sehr schön! Es wird 1672 mit einem Lookahead von 7, noch nie so hoch gesehen.
Angs
(Es sieht so aus, als wäre das zweite Argument für Ihre powersetFunktion in diesem Fall überflüssig, weil es len(list_)sowieso gleich ist?)
user202729
@user Ich habe mit diesem Parameter in der vorherigen Version experimentiert. Wird wahrscheinlich später wieder entfernt
Alex
1
Herzlichen Glückwunsch zum Entdecken der leistungsstarken Kombination aus bestem Einzelelement aus der besten Teilmenge und der besten Punktzahl! Das Kopfgeld liegt bei Ihnen.
Angs
Eine einfachere expected_mean(w), die als gute Ergebnisse gibt:return (w+2) / max(1, round((w+2) / mean))
Angs
10

Python 3 , 9796 Taschen

Aufbauend auf Jonathans Antwort:

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

Dies basiert auf Powerset aus dem Kochbuch von itertool. Findet zuerst die optimale Teilmenge des Puffers basierend auf der Minimierung der Differenz zum Zielgewicht für alle Teilmengen und wählt dann ein Element aus dieser Teilmenge basierend auf demselben Kriterium aus. Wenn keine optimale Teilmenge aus dem gesamten Puffer auswählt.

JayCe
quelle
Willkommen bei PPCG!
Martin Ender
@ MartinEnder Vielen Dank, Martin, für die einladende positive Bewertung :)
JayCe
1
Ah ja da habe ich einen Trick verpasst ... ich habe kein Problem damit als andere Antwort!
Jonathan Allan
1
@ JonathanAllan Danke Jonathan Ich habe meine Antwort verkürzt, um dir ohne all die Entschuldigungen die Ehre zu erweisen. Dies kann durch die Tatsache verbessert werden, dass es sich um eine normale (170,13) Verteilung handelt. Ich bin sicher, dass die Wahrscheinlichkeit, in den nächsten Läufen eine bessere Frucht zu erhalten, genutzt werden kann.
JayCe
@ JayCe klingt gefährlich ähnlich wie der Irrtum eines Spielers.
Qwr
6

C ++ 17, 9961.58 (Durchschnitt über einige zufällige Samen)

(Scrollen Sie zur Erklärung nach unten, wenn Sie C ++ nicht kennen.)

#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';
}

// Fun fact: Ursprünglich schrieb ich <für >hier und frage mich,
// warum die Anzahl der Taschen noch geringer ist als die des
// randomisierten Algorithmus

(wenn <im Grunde genommen verwendet wird, versucht der Algorithmus die Anzahl der Taschen zu minimieren )

Inspiriert von dieser Antwort .

TIO-Link für 250 Wiederholungen: Probieren Sie es online!


Definiert eine Funktion (eigentlich sieht sie nur aus wie eine Funktion, es ist eine Struktur) pick_orange, die unter Berücksichtigung vector<int> weightsdes Gewichts der Orangen und int remaindes verbleibenden Gewichts des Beutels den Index der Orange zurückgibt, die gepflückt werden soll.

Algorithmus:

500Wiederholungszeiten {
erzeugen zufällige (fake) Orangen (Normalverteilung mit Mittelwert 170 und stddev 13) , bis es gibt N_NEW_ORANGES=7Orangen
jede Teilmenge Summe , dessen Pick am kleinsten ist und nicht kleiner als remain(die Funktion backtrackist , dass)
in dieser Teilmenge als alle Orangen markiert gut
}

Der Durchschnitt der Häufigkeit, mit der eine Orange als gut der (echten) Orangen mit gleichem Gewicht bewertet wird,
ergibt die beste Orange


Das Programm enthält 3 fest codierte Konstanten, die nicht aus dem Problem abgeleitet werden können:

  • Der zufällige Samen (das ist nicht wichtig)
  • N_NEW_ORANGES(die Vorhersagelänge). Wenn Sie dies erhöhen, wird das Programm exponentiell länger ausgeführt (da der Backtrack).
  • Anzahl der Wiederholungen. Wenn Sie dies erhöhen, läuft das Programm linear länger.
user202729
quelle
Okay. Das Ändern des Startwerts auf denjenigen, der die beste Antwort liefert, scheint jedoch für den Testfall zu optimieren. Daher sollten Sie den Durchschnitt von einigen, z. B. 10, verschiedenen Startwerten als Ihre Punktzahl verwenden. Könnten Sie einen TIO-Link zu einer Version veröffentlichen, die weniger Wiederholungen durchführt, um die Laufzeit zu verringern?
Angs
Endlich muss es kompiliert werden, nachdem ein neuer gcc installiert wurde. Bei 50 Läufen mit zufälligen Samen wurde ein Durchschnitt von 9961,58 erreicht. Immer noch sehr beeindruckend. Ich habe mich allerdings gewundert - Ihr Algorithmus trainiert sich im Grunde genommen bei jeder Tasche von neuem. Gibt es einen festen Satz von besten Werten, die man sich merken könnte?
Angs
@Angs Ich glaube nicht, dass es in diesem Fall eine Möglichkeit gibt, Memorization zu verwenden. Irgendeine Idee?
user202729
Mein Betriebssystem kommt mit gcc 5.4.0, es hatte einige Probleme mit invalid use of template-name ‘std::normal_distribution’. Keine Probleme mit gcc 7.1.0.
Angs
4

Python 2 , 9756 Taschen

Lassen Sie uns die Orange ins Rollen bringen ...

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

Probieren Sie es online!

Pflückt die Früchte immer aus dem Puffer, wodurch die absolute Differenz zwischen dem neuen Gewicht und dem Zielgewicht minimiert wird.

Jonathan Allan
quelle
4

Python 3, 9806 Taschen

Aufbauend auf den Antworten von Jonathan und 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

Probieren Sie es online!

Wie es funktioniert

Angenommen, die Tüte enthält 900 Einheiten und es stehen 2 Früchte zur Verfügung: eine Frucht mit 99 Einheiten und eine Frucht mit 101 Einheiten. Befindet sich die Frucht mit 99 Einheiten näher am Anfang der Lookahead-Liste, minwird sie anstelle von 101 ausgewählt. In diesem Fall benötigen wir jetzt eine weitere Frucht, um die verbleibende benötigte Einheit zu erfüllen. Ich habe das Programm geändert, um in diesen Fällen die höherwertigen Früchte zu bevorzugen.

Dies geschieht, indem die Lookahead-Liste vor dem Powersetting sortiert und dann umgekehrt wird.

fortraan
quelle
4

PHP, 9975 Beutel

  • Wenn möglich für 5 Orangen gehen
  • Wenn Sie mit dem Packen beginnen, wählen Sie einen extremen Wert und gleichen Sie ihn später aus
  • Wenn möglich Beutel sofort füllen
  • Versuchen Sie, das Gewicht des Beutels in der Nähe der geschätzten Kurve zu halten (n * 200 für 5 Beutel, n * 167 für 6 Beutel usw.).

am längsten von allen Einsendungen, sollte aber lesbar sein

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 Gesamt: 9975

Versuch es

mleko
quelle
Nett! Für mich ist es überraschend, dass es die aktuelle Anzahl von Gegenständen verwendet - ich frage mich, ob dies herausgerechnet werden kann. Immerhin spielt es keine Rolle, ob es 3 Artikel mit einem Gewicht von jeweils 120 oder 3 Artikel mit einem Gewicht von jeweils 160 gibt.
Angs
@Angs wahrscheinlich ist es möglich. Die aktuelle Anzahl der Gegenstände stellte sich als einfache Abkürzung für die Idee "Hey, manchmal ist es möglich, Beutel mit 5 Gegenständen zu erstellen" heraus. Mit der Freizeit werden Verbesserungen kommen :)
mleko
3

Python 3, 9855 9928 9947 9956 9964 Taschen

Basiert auf Jonathan Allans Startercode, ist aber ungolfed, um lesbar zu sein.

Idee: Seit 1000/170 = 5.88 versuchen wir, Früchte in der Nähe von 1000/6 auszuwählen (ich habe mit den magischen Konstanten herumgespielt). Wenn die letzte Frucht in der Tüte den Abfall minimieren kann, verwenden wir stattdessen diese.

Diese Lösung enthält Beutelsummenziele für jede hinzugefügte Frucht. Ich werde wahrscheinlich hier aufhören. Ich habe Nelder-Mead verwendet, um mein targetsArray zu finden :

[  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 Taschen

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))

Das 9947 bags- Programm ist besonders einfach:

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
quelle
1
Gut! Übrigens ist das einfache Auswählen des letzten Artikels, um den Abfall so gering wie möglich zu halten, von sich aus sehr leistungsfähig und ergibt 9862 Säcke.
Angs
Wie bist du auf die gekommen targets? Training auf zufälligen Daten?
Alex
1
@ Alex Ich erklärte so: Nelder-Mead-Methode (mit negativen Taschen als Verlustfunktion)
qwr
2

Rubin , 9967 Beutel

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

Probieren Sie es online!

Wenn Sie genug Gewicht haben, um den Beutel zu füllen, suchen Sie die leichteste Teilmenge, die den Beutel füllen kann, und verwenden Sie die hellste Orange dieser Teilmenge. Andernfalls bringen Sie das verbleibende Gewicht so nahe wie möglich an ein Vielfaches von 170.

MegaTom
quelle
2

Schläger / Schema, 9880 Taschen

Um zu entscheiden, welches Stück Obst in den Beutel gegeben werden soll, vergleichen Sie das optimale Beutelgewicht mit dem Gewicht des Beutels mit dem zusätzlichen Stück Obst. Wenn es das optimale Gewicht ist, verwenden Sie es. Wenn es übergewichtig ist, minimieren Sie die überschüssige Menge. Wenn es untergewichtig ist, minimieren Sie die überschüssige Menge, nachdem Sie versucht haben, eine optimale Lücke zu lassen.

;; 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
quelle
1

Haskell , 9777 Taschen

Dies war mein erster Versuch:

  • es füllte gierig einen Beutel mit einer Charge, wenn es konnte,
  • oder spülte alle Orangen in den Beutel, wenn es nicht konnte.
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]

Probieren Sie es online!

Roman Czyborra
quelle
1

Haskell , 9981 Beutel

Die Codegolf-Python von AngsJonathan AllanJayCeFortraanAlexRoman Czyborra könnte für eine zusätzliche mathematische Reinheit entlang des gleichen Hauptgedankens zu Haskell zurückkehren

  • Nur eine Orange wird geplündert, bevor eine neue Orange in Betracht gezogen wird
  • Voreingenommenheit bevorzugt genügende Früchte ( (<miss)==False<True)
  • Bias bevorzugt Früchte nahe der wahrscheinlichsten Ganzzahlfüllung
  • für die ganze Zahl umgekehrt die
    (m-n)/sqrt(n)==(n+1-m)/sqrt(n+1) <==> n=sqrt(m^2-1/4)-1/2 von einem 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)

gewürzt mit einigen unnötigen Sinnlosigkeit

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]

Probieren Sie es online!

ohne einen anderen numerischen Gewinn auf den zuvor geernteten 9981 Orangennetzen zu erzielen, während mein 10k011-Beutelpacker, der ungeeignete Orangen aus nicht verschlossenen Beuteln zurückholte, von user69850 in persona user202729 disqualifiziert wurde → Jo Kingovs, daher ging die verdiente Prämie an Alex

GIMME BOUNTY!

Roman Czyborra
quelle