Michael von Aichberger <[EMAIL PROTECTED]> wrote:

> Imagine you had a given length. Like:
> 
> length = 315
> 
> And you needed to cover that length with smaller pieces. You had a set of
> pieces of a given length, like for instance:
> 
> piecesL = [13, 27, 35, 48, 90, 111, ...]
> 
> 
> Now the algorithm I am looking for should chose any number of the pieces in
> piecesL and add them up the the given length. The pieces can be used several
> times. If the full length cannot be achieved because of the given values,
> the one solution should be found that comes closest.
> 
> So I need a solution in the style of
> 
> solutionL = [13, 13, 13, 27, 90, 90, ...]


Hi Michael,

Here are a couple of solutions to the particular problem that you give:

[27, 27, 27, 27, 27, 90, 90]
[27, 27, 27, 48, 48, 48, 90]


Your problem lends itself nicely to a genetic algorihtm, where a number of
near solutions are created then combined to produce even nearer solutions.
Below you will find my full genetic algorithm.  Here is the output from my
message window for my tests, where the solution should add up to 16 and the
list of lengths are the first nine prime numbers:

genePool = script("Genetics").new()
put genePool.mFindSolution(16, [2, 3, 5, 7, 11, 13, 17, 19, 23])
-- [[2, 2, 2, 5, 5], [2, 7, 7], [3, 13], [2, 3, 3, 3, 5]]
-- [[3, 13], [3, 3, 3, 7], [2, 7, 7]]
-- [[3, 13], [2, 7, 7], [5, 11], [2, 3, 11]]
-- [[3, 13], [2, 3, 11]]

Note that running the algorithm four times gave four different sets of
correct answers.  Genetic algorithms do not guarantee a correct answer.
What they do provide is a shortcut to an answer that is close enough.

My script is very arbitrary in places, and there are many ways in which it
could be optimized.  You may want to include other criteria for fitness,
such as how many numbers need to be summed together.

Have fun,

James



----------------------------------------------------------------------------
-- PARENT SCRIPT "Genetics
----------------------------------------------------------------------------



-- GENETICS --
--
-- February 2002, James Newton, [EMAIL PROTECTED]
--
-- An example of a genetic algorithm which can be used to find which
-- numbers in a given list can be added together to create a given
-- number.  Numbers in the list may be used more than once.
-- 
-- Examples:
--   genePool = script("Genetics").new()
--   put genePool.mFindSolution(16, [2, 3, 5, 7, 11, 13, 17, 19, 23])
--   -- [[5, 11]]
--   -- [[3, 13], [5, 11]]
--   -- [[3, 13], [2, 7, 7], [2, 2, 5, 7]]
--   -- [[3, 13], [3, 3, 3, 7], [5, 11]]



-- PROPERTY DECLARATIONS --

property pList   -- list of numbers which can be used for the sum
property pCount  -- number of items in pList
property pNumber -- number which must be created from a sum of the
--                  numbers in pList



-- PUBLIC METHODS --

on new(me, aList)
  -- Ensure that there is a list of items to choose from (the list may
  -- be empty)
  pList = []
  me.mMerge(aList, pList)
  
  return me
end new



on mFindSolution(me, aNumber, aNumberSource) -------------------------
  -- SENT from a different script
  -- INPUT:   <aNumber> should be a floating point or integer number
  --          <aNumberSource> should a list of numbers, in which case
  --           it will replace the current pList
  -- RETURNS: a list with the combination of numbers in pList which,
  --          when added, reach the closest to <aNumber>
  --------------------------------------------------------------------
  
  if listp(aNumberSource) then
    -- Use the numbers in aNumberSource instead of pList
    tList = []
    me.mMerge(aNumberSource, tList)
    if tList.count then
      pList = tList
    end if
  end if
  
  -- Look for an exact match
  if pList.getPos(aNumber) then
    -- Only one number is required
    return [aNumber]
    
  else
    -- Generate a gene pool of possible solutions then pick the best.
    -- The number 100 is hard coded.  The actual values you use will
    -- depend on the number of elements in pList, and how large
    -- pNumber is
    
    pNumber   = aNumber
    pCount    = pList.count
    tGenePool = me.mGenerateGenePool(100)
    
    repeat with i = 1 to 100
      me.mStir(tGenePool)
    end repeat
    
    return me.mPickBestFit(tGenePool)
  end if
end mFindSolution



on mMerge(me, aNumberSource, aList) ----------------------------------
  -- SENT BY: new(), mFindSolution() or from a different script
  -- INPUT:   <aNumberSource> should be a number or a list of numbers
  --          <aList> is a list of numbers
  -- ACTION:  Adds any float or integer numbers in aNumberSource to
  --          pList
  --------------------------------------------------------------------
  
  case ilk(aNumberSource) of
    #integer, #float:
      aList.add(aNumberSource)
      
    #list, #propList:
      i = aNumberSource.count
      repeat while i
        tItem = aNumberSource[i]
        case ilk(tItem) of
          #float, #integer:
            if not aList.getPos(tItem) then
              aList.add(tItem)
            end if
        end case
        i = i - 1 
      end repeat
  end case
end mMerge




-- PRIVATE METHODS --

on mGenerateGenePool(me, aCount) -------------------------------------
  -- CALLED BY mFindSolution()
  -- INPUT:    <aCount> is a positive number of genes to be created
  -- RETURNS:  a list of lists, each of which contains a random
  --           number of randomly chosen items from pList.  Certain
  --           items may appear twice in any given sublist
  --------------------------------------------------------------------
  
  tGenePool = [:]
  tGenePool.sort()
  
  if not pList.count then
    -- We can't create any genes
    return tGenePool
  end if
  
  repeat while aCount
    tGene  = me.mRandomGene()
    tError = me.mError(tGene)
    tGenePool.addProp(tError, tGene)
    aCount = aCount - 1
  end repeat
  
  return tGenePool
end mGenerateGenePool



on mRandomGene(me, aCount) -------------------------------------------
  -- CALLED BY mGenerateGenePool()
  -- RETURNS:  a list which contains a random number of randomly
  --           chosen items from pList.  Certain items may appear
  --           twice
  --------------------------------------------------------------------
  
  tGene  = []
  tError = pNumber
  
  i = random(pNumber / pList.min())
  repeat while i
    tGene.append(pList[random(pCount)])
    
    -- Ensure that genes don't get too fat
    temp = me.mError(tGene)
    if temp < tError then
      tError = temp
    else
      exit repeat
    end if
    i = i - 1
  end repeat
  
  return tGene
end mRandomGene



on mError(me, aGene) -------------------------------------------------
  -- CALLED BY mRandomGene(), mGenerateGenePool(), mStirGenePool()
  -- INPUT:    <aGene> is a list of numbers
  -- RETURNS:  a number indicating how close a fit aGene has to the
  --           target number
  --------------------------------------------------------------------
  
  tSum = 0
  
  i = aGene.count
  repeat while i
    tSum = tSum + aGene[i]
    i = i - 1
  end repeat
  
  return abs(tSum - pNumber)
end mError



on mStir(me, aGenePool) ----------------------------------------------
  -- CALLED BY mFindSolution()
  -- INPUT:    <aGenePool> is a list of lists of numbers
  -- ACTION:   Deletes the worst fitting genes somewhat randomly, then
  --           crosses existing genes randomly to rebuild the
  --           population
  --------------------------------------------------------------------
  
  tCount = aGenePool.count
  -- Delete some (most?) of the less fit
  tLowCount = sqrt(tCount)
  repeat with i = tCount down to tLowCount
    tFit = aGenePool.getPropAt(i)
    if random(tFit) > sqrt(tFit) then
      aGenePool.deleteAt(i)
    end if
  end repeat
  
  -- Rebuild the population by crossing existing genes
  repeat with i = aGenePool.count to tCount
    tGene1 = aGenePool[random(i)].duplicate() -- copy a gene
    tGene2 = aGenePool[random(i)]             -- may be the same gene
    
    tGenes = tGene2.count
    j = min(tGene1.count, tGenes)
    repeat while j
      if random(2) - 1 then
        -- Replace a number in tGene1 with a random number from tGene2
        tGene1[j] = tGene2[random(tGenes)]
      end if
      j = j - 1
    end repeat
    
    tError = me.mError(tGene1)
    aGenePool.addProp(tError, tGene1)
  end repeat
end mStirGenePool



on mPickBestFit(me, aGenePool) ---------------------------------------
  -- CALLED BY mFindSolution()
  -- INPUT:    <aGenePool> is a list of lists of numbers
  -- RETURNS:  a list of those unique combinations which have the
  --           best fit
  --------------------------------------------------------------------
  
  tBestFit  = []
   tTightFit = aGenePool.getPropAt(1) -- lowest error

  tCount = aGenePool.count
  repeat with i = 1 to tCount
    tFit = aGenePool.getPropAt(i)
    if tFit > tTightFit then
      -- This solution and those following are less accurate
      exit repeat
    end if
    
    -- Ensure that this solution is unique
    tGene = aGenePool[i]
    tGene.sort()
    if not tBestFit.getPos(tGene) then
      tBestFit.append(tGene)
    end if
  end repeat
  
  return tBestFit
end mPickBestFit

[To remove yourself from this list, or to change to digest mode, go to 
http://www.penworks.com/lingo-l.cgi  To post messages to the list, email 
[EMAIL PROTECTED]  (Problems, email [EMAIL PROTECTED]). Lingo-L is for 
learning and helping with programming Lingo.  Thanks!]

Reply via email to