18
\$\begingroup\$

The seam carving algorithm, or a more complex version of it, is used for content-aware image resizing in various graphics programs and libraries. Let's golf it!

Your input will be a rectangular two dimensional array of integers.

Your output will be the same array, one column narrower, with one entry removed from each row, those entries representing a path from top to bottom with the lowest sum of all such paths.

Seam carving illustration https://en.wikipedia.org/wiki/Seam_carving

In the above illustration, each cell's value is shown in red. The black numbers are the sum of a cell's value and the lowest black number in one of the three cells above it (pointed to by the green arrows). The white highlighted paths are the two lowest sum paths, both with a sum of 5 (1+2+2 and 2+2+1).

In a case where there are two paths tied for the lowest sum, it does not matter which you remove.

Input should be taken from stdin or as a function parameter. It can be formatted in a manner convenient to your language of choice, including brackets and/or delimiters. Please specify in your answer how the input is expected.

Output should be to stdout in an unambiguously delimited format, or as a function return value in your language's equivalent to a 2d array (which might include nested lists, etc).

Examples:

Input:
1 4 3 5 2
3 2 5 2 3
5 2 4 2 1
Output:
4 3 5 2      1 4 3 5
3 5 2 3  or  3 2 5 3
5 4 2 1      5 2 4 2

Input:
1 2 3 4 5
Output:
2 3 4 5

Input:
1
2
3
Output:
(empty, null, a sentinel non-array value, a 0x3 array, or similar)

EDIT: The numbers will all be non-negative, and every possible seam will have a sum that fits in a signed 32 bit integer.

\$\endgroup\$
  • \$\begingroup\$ In the examples, all cell values are single digit numbers. Is that guaranteed? If not, are there other assumptions that can be made about the size/range of the values? For example that the sum fits in a 16/32-bit value? Or at least that all the values are positive? \$\endgroup\$ – Reto Koradi May 21 '15 at 0:07
  • \$\begingroup\$ @RetoKoradi edited with details on range \$\endgroup\$ – Sparr May 21 '15 at 3:08
5
+100
\$\begingroup\$

CJam, 51 44 bytes

{_z,1$,m*{_1>.-W<2f/0-!},{1$.=:+}$0=.{WtW-}}

This is an anonymous function that pops a 2D array from the stack and pushes one in return.

Try the test cases online in the CJam interpreter.1

Idea

This approach iterates over all possible combinations of row elements, filters out those that do not correspond to seams, sorts by the corresponding sum, select the minimum and removes the corresponding elements from the array.2

Code

_z,   e# Get the length of the transposed array. Pushes the number of columns (m).
1$,   e# Get the length of the array itself. Pushes the number of rows (n).
m*    e# Cartesian power. Pushes the array of all n-tuples with elements in [0 ... m-1].
{     e# Filter:
  _1> e#     Push a copy of the tuple with first element removed.
  .-  e#     Vectorized difference.
  W<  e#     Discard last element.
  2f/ e#     Divide all by 2.
  0-  e#     Remove 0 from the results.
  !   e#     Push 1 if the remainder is empty and 0 otherwise.
},    e#     Keep only tuples which pushed a 1.

      e# The filtered array now contains only tuples that encode valid paths of indexes.

{     e# Sort by:
  1$  e#     Copy the input array.
  .=  e#     Retrieve the element of each row that corresponds to the index in the tuple.
  :+  e#     Add all elements.
}$    e#
0=    e# Retrieve the tuple of indexes with minimum sum.
.{    e# For each row in the array and the corresponding index in the tuple:
  Wt  e#     Replace the element at that index with -1.
  W-  e#     Remove -1 from the row.
}

1 Note that CJam cannot distinguish between empty arrays and empty strings, since strings are just arrays whose elements are characters. Thus, the string representation of both empty arrays and empty strings is "".

2 While the time complexity of the algorithm shown on the Wikipedia page should be of O(nm) for an n×m matrix, this one's is at least of O(mn).

\$\endgroup\$
  • \$\begingroup\$ {2ew::m2f/0-!}, \$\endgroup\$ – Optimizer May 30 '15 at 9:24
  • \$\begingroup\$ Sadly, that won't work for the second test case. I've filed a bug report about this two weeks ago. \$\endgroup\$ – Dennis May 30 '15 at 13:21
5
\$\begingroup\$

Haskell, 187 bytes

l=length
f a@(b:c)=snd$maximum$(zip=<<map(sum.concat))$map(zipWith((uncurry((.drop 1).(++)).).flip splitAt)a)$iterate((\e@(f:_)->[f-1:e,f:e,min(f+1)(l b-1):e])=<<)[[y]|y<-[0..l b-1]]!!l c

Usage example:

*Main> f [[1,4,3,5,2],[3,2,5,2,3],[5,2,4,2,1]]
[[4,3,5,2],[3,5,2,3],[5,4,2,1]]

*Main> f [[1],[2],[3]]
[[],[],[]]

*Main> f [[1,2,3,4,5]]
[[2,3,4,5]]

How it works, short version: build a list of all paths (1), per path: remove corresponding elements (2) and sum all remaining elements (3). Take the rectangle with the largest sum (4).

Longer version:

Input parameters, assigned via pattern matching:
a = whole input, e.g. [[1,2,4],[2,5,6],[3,1,6]]
b = first line, e.g. [1,2,4]
c = all lines, except first, e.g. [[2,5,6],[3,1,6]]

Step (1), build all paths:

iterate((\e@(f:_)->[f-1:e,f:e,min(f+1)(l b-1):e])=<<)[[y]|y<-[0..l b-1]]!!l c

     [[y]|y<-[0..l b-1]]           # build a list of single element lists
                                   # for all numbers from 0 to length b - 1
                                   # e.g. [[0],[1],[2]] for a 3 column input.
                                   # These are all possible start points

     \e@(f:_)->[f-1:e,f:e,min(f+1)(l b-1):e]
                                   # expand a list of paths by replacing each
                                   # path with 3 new paths (up-left, up, up-right)

     (...)=<<                      # flatten the list of 3-new-path lists into
                                   # a single list

     iterate (...) [...] !! l c    # repeatedly apply the expand function to
                                   # the start list, all in all (length c) times.


Step (2), remove elements

map(zipWith((uncurry((.drop 1).(++)).).flip splitAt)a)

     (uncurry((.drop 1).(++)).).flip splitAt
                                   # point-free version of a function that removes
                                   # an element at index i from a list by
                                   # splitting it at index i, and joining the
                                   # first part with the tail of the second part

      map (zipWith (...) a) $ ...  # per path: zip the input list and the path with
                                   # the remove-at-index function. Now we have a list
                                   # of rectangles, each with a path removed

Step (3), sum remaining elements

zip=<<map(sum.concat)             # per rectangle: build a pair (s, rectangle)
                                  # where s is the sum of all elements


Step (4), take maximum

snd$maximum                      # find maximum and remove the sum part from the
                                 # pair, again.
\$\endgroup\$
3
\$\begingroup\$

IDL 8.3, 307 bytes

Meh, I'm sure this won't win because it's long, but here's a straightforward solution:

pro s,a
z=size(a,/d)
if z[0]lt 2then return
e=a
d=a*0
u=max(a)+1
for i=0,z[1]-2 do begin
e[*,i+1]+=min([[u,e[0:-2,i]],[e[*,i]],[e[1:*,i],u]],l,d=2)
d[*,i]=l/z[0]-1
endfor
v=min(e[*,-1],l)
r=intarr(z[1])+l
for i=z[1]-2,0,-1 do r[0:i]+=d[r[i+1],i]
r+=[0:z[1]-1]*z[0]
remove,r,a
print,reform(a,z[0]-1,z[1])
end

Ungolfed:

pro seam, array
  z=size(array, /dimensions)
  if z[0] lt 2 then return
  energy = array
  ind = array * 0
  null = max(array) + 1
  for i=0, z[1]-2 do begin
    energy[*, i+1] += min([[null, energy[0:-2,i]], [energy[*,i]], [energy[1:*,i], null]], loc ,dimension=2)
    ind[*, i] = loc / z[0] - 1
  endfor
  void = min(energy[*,-1], loc)
  rem = intarr(z[1]) + loc
  for i=z[1]-2, 0, -1 do rem[0:i] += ind[rem[i+1], i]
  rem += [0:z[1]-1]*z[0]
  remove, rem, array
  print, reform(array, z[0]-1, z[1])
end

We iteratively create the energy array and track which direction the seam goes, then construct a removal list once we know the final position. Remove the seam via 1D indexing, then reform back into the array with the new dimensions.

\$\endgroup\$
  • 3
    \$\begingroup\$ Oh god...I think I just threw up a little seeing IDL (again). I thought I was done seeing that after graduation... \$\endgroup\$ – Kyle Kanos May 19 '15 at 20:26
  • \$\begingroup\$ That said, I suspect this also works for GDL, so that the people not willing to pay $1 billion for the single-user license can test it? \$\endgroup\$ – Kyle Kanos May 19 '15 at 20:27
  • \$\begingroup\$ I've never used GDL, so I can't say (honestly I forgot it existed). The only thing which might cause a problem is if GDL can't handle array creation of the syntax [0:n]; if that's true, then it's easy to replace r+=[0:z[1]-1]*z[0] with r+=indgen(z[1]-1)*z[0]. \$\endgroup\$ – sirpercival May 19 '15 at 20:38
  • \$\begingroup\$ Also, while I would rather use python for my golfs, no one else does IDL so I feel obliged to contribute XD. Plus, it does some things very well. \$\endgroup\$ – sirpercival May 19 '15 at 20:38
  • 3
    \$\begingroup\$ I does make me cringe/cry very well ;) \$\endgroup\$ – Kyle Kanos May 19 '15 at 20:39
3
\$\begingroup\$

JavaScript (ES6) 197 209 215

Step by step implementation of the wikipedia algorithm.

Probably can be shortened more.

Test running the snippet in Firefox.

// Golfed

F=a=>(u=>{for(r=[i=p.indexOf(Math.min(...p))];l--;i=u[l][i])(r[l]=[...a[l]]).splice(i,1)})
(a.map(r=>[r.map((v,i)=>(q[i]=v+~~p[j=p[i+1]<p[j=p[i-1]<p[i]?i-1:i]?i+1:j],j),q=[++l]),p=q][0],p=[l=0]))||r

// LESS GOLFED

U=a=>{
  p = []; // prev row
  u = a.map( r => { // in u the elaboration result, row by row
      q=[];
      t = r.map((v,i) => { // build a row for u from a row in a
        j = p[i-1] < p[i] ? i-1 : i; // find position of min in previous row
        j = p[i+1] < p[j] ? i+1 : j;
        q[i] = v + ~~p[j]; // values for current row
        // ~~ convert to number, as at first row all element in p are 'undefined'
        return j;//  position in u, row by row
      });
      p = q; // current row becomes previous row 
      return t;
  });
  n = Math.min(...p) // minimum value in the last row
  i = p.indexOf(n); // position of minimum (first if there are more than one present)
  r = []; // result      
  // scan u bottom to up to find the element to remove in the output row
  for(j = u.length; j--;)
  {
    r[j] = a[j].slice(); // copy row to output
    r[j].splice(i,1); // remove element
    i = u[j][i]; // position for next row
  }
  return r;
}

// TEST        
out=x=>O.innerHTML += x + '\n';        

test=[
  [[1,4,3,5,2],[3,2,5,2,3],[5,2,4,2,1]],
  [[1,2,3,4,5]],
  [[1],[2],[3],[4]]
];  

test.forEach(t=>{
  out('Test data:\n' + t.map(v=>'['+v+']').join('\n'));
  r=F(t);
  out('Golfed version:\n' + r.map(v=>'['+v+']').join('\n'))      
  r=U(t);
  out('Ungolfed version:\n' + r.map(v=>'['+v+']').join('\n'))
})  
<pre id=O></pre>

\$\endgroup\$
1
\$\begingroup\$

Pip, 91 bytes

This won't win any prizes, but I had fun working on it. Whitespace is for cosmetic reasons only and is not included in the byte count.

{
 p:{(zaj-1+,3RMv)}
 z:a
 w:,#(a0)
 Fi,#a
  Fjw
   Ii
    z@i@j+:MN(pi-1)
 s:z@i
 Ti<0{
  j:s@?MNs
  a@i@:wRMj
  s:(p--i)
 }
 a
}

This code defines an anonymous function whose argument and return value are nested lists. It implements the algorithm from the Wikipedia page: a (the argument) is the red numbers, and z is the black numbers.

Here's a version with test harness:

f:{p:{(zaj-1+,3RMv)}z:aw:,#(a0)Fi,#aFjwIiz@i@j+:MN(pi-1)s:z@iTi<0{j:s@?MNsa@i@:wRMjs:(p--i)}a}
d:[
 [[1 4 3 5 2]
  [3 2 5 2 3]
  [5 2 4 2 1]]
 [[1 2 3 4 5]]
 [[1]
  [2]
  [3]]
 ]
Fld
 P(fl)

Results:

C:\> pip.py minSumSeam.pip -p
[[4;3;5;2];[3;5;2;3];[5;4;2;1]]
[[2;3;4;5]]
[[];[];[]]

And here's the rough equivalent in Python 3. If anyone wants a better explanation of the Pip code, just ask in the comments.

def f(a):
    z = [row.copy() for row in a]
    w = range(len(a[0]))

    for i in range(len(a)):
        for j in w:
            if i:
                z[i][j] += min(z[i-1][max(j-1,0):j+2])
    s = z[i]
    while i >= 0:
        j = s.index(min(s))
        del a[i][j]
        i -= 1
        s = z[i][max(j-1,0):j+2]
    return a
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.