Magnetic pull in an array

Pyth, 28 20

[email protected]?bhaDk_x1QkQ~hZQ

Thanks @ThomasKwa for golfing 6 bytes!

This abuses stable sorting by assigning to all the values 1 or greater in the list the index of the nearest 1 (ties broken to the rightmost 1) and then sorting the list by these values. Zeroes are given their own index as their sort value.

Test Suite

Verification Suite

Explanation:

[email protected]?bhaDk_x1QkQ~hZQ  ##  implicit: Q = eval(input())
o                  Q  ##  Sort Q using the values of the lambda below
 @              ~hZ   ##  select the value from the matching index of the enumerate
  .e           Q      ##  enumerate with b = value, k = index
    ?b                ##  ternary on b
      haDk_x1Q        ##  if true, this thing
              k       ##  otherwise use the index as the sort weight
          _x1Q        ##  the indices of 1 in Q, given in reverse order 
                      ##  (the reverse makes it sort to the right because of stable sorts)
       aDk            ##  sort those indices by |index - k|
      h               ##  take the first value

Example:

Take the test case [1,0,3,0,1,0,3,0,1], for example. When we apply the enumeration, the zeros will all get their own index as a sort value, so I'll skip those, and do a one and a three.

For the first one, we get the indices of ones: [0, 4, 8]. Then reverse it, and sort by the absolute value of the indices minus the index of the one, which happens to be zero here. So we get [0, 4, 8] back again. The first value is 0 so we use that.

For the three, we get the reversed indices and do the same sorting but using two as the index of the three, so both the 0 and the 4 give the same value for the absolute difference, so we get: [4, 0, 8] and we take the 4.

Then the final "sorting values" array will be [0, 1, 4, 3, 4, 5, 8, 7, 8]. Thanks to the stable sort, the ties are broken by the order that the values originally appeared, so we get the final array we want.


Retina, 97 72 bytes

+`(?<=\b1(,1*)*?)(\B,(11+)|,(11+))\b(?!(?<-1>,1*)*,1\b)|(11+),\B
$3,$4$5

Input is expected to be a comma-separated list of unary integers (leading and trailing delimiters like [...] work just fine).

Run all test cases here. (For convenience, this takes care of the conversion from and to decimal automatically.)

Here is a completely different idea that avoids the expensive balancing groups by using multiple stage. It's currently 6 bytes longer, but might be more golfable:

,1\b
>1
\b1,
1<
(T`,`<`<1*,
)T`,`>`,1*>
+`(1+>)>
>$1
+`<(<1+\b)(?!>)
$1<
<|>
,

JavaScript (ES6), 108 bytes

a=>a.map(_=>a.map((x,i)=>x>1?a[j=(n=a.indexOf(1,i))<0|n-i>i-p?i-1:i+1]?0:a[a[i]=0,j]=x:x<1?0:p=i,p=-1/0))&&a

Explanation

Iterates over each cell and if it contains metal, checks if the next cell in the direction of the closest magnet is empty, and if it is, moves it there. This process is repeated many times until all metal has moved as far as it can go.

var solution =

a=>
  a.map(_=>                  // loop a.length times to ensure completeness
    a.map((x,i)=>            // for each cell item x at index i
      x>1?                   // if the cell contains metal
        a[j=                 // j = index of cell to move to
          (n=a.indexOf(1,i)) // n = index of next magnet
          <0|n-i>i-p?i-1:i+1 // set j to previous or next cell based on closest magnet
        ]?0:                 // if cell j is empty
          a[a[i]=0,j]=x      // set it to x and set cell i to 0
      :x<1?0:                // else if the cell contains a magnet
        p=i,                 // set p to the index of this magnet
      p=-1/0                 // p = index of previous magnet, initialise to -Infinity
    )
  )
  &&a                        // return a
<input type="text" id="input" value="1,2,3,4,5,6,7,8,9,10,11,0,0,0,1" />
<button onclick="result.textContent=solution(input.value.split(',').map(n=>+n))">Go</button>
<pre id="result"></pre>