Remova a costura de soma mínima de uma matriz

18

O algoritmo de escultura de costura, ou uma versão mais complexa, é usado para redimensionar imagens com reconhecimento de conteúdo em vários programas gráficos e bibliotecas. Vamos jogar golfe!

Sua entrada será uma matriz bidimensional retangular de números inteiros.

Sua saída será a mesma matriz, uma coluna mais estreita, com uma entrada removida de cada linha, aquelas entradas representando um caminho de cima para baixo com a menor soma de todos esses caminhos.

Ilustração de escultura de costura https://en.wikipedia.org/wiki/Seam_carving

Na ilustração acima, o valor de cada célula é mostrado em vermelho. Os números pretos são a soma do valor de uma célula e o menor número preto em uma das três células acima dela (apontada pelas setas verdes). Os caminhos destacados em branco são os dois caminhos de soma mais baixa, ambos com uma soma de 5 (1 + 2 + 2 e 2 + 2 + 1).

Em um caso em que há dois caminhos vinculados para a soma mais baixa, não importa qual você remove.

A entrada deve ser obtida do stdin ou como um parâmetro de função. Ele pode ser formatado de maneira conveniente para o idioma de sua escolha, incluindo colchetes e / ou delimitadores. Especifique na sua resposta como a entrada é esperada.

A saída deve ser stdout em um formato delimitado sem ambiguidade, ou como um valor de retorno de função no equivalente em seu idioma a uma matriz 2D (que pode incluir listas aninhadas, etc.).

Exemplos:

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: Os números serão todos não negativos, e cada costura possível terá uma soma que cabe em um número inteiro de 32 bits assinado.

Sparr
fonte
Nos exemplos, todos os valores das células são números de um dígito. Isso é garantido? Caso contrário, existem outras suposições que podem ser feitas sobre o tamanho / intervalo dos valores? Por exemplo, que a soma se encaixa em um valor de 16/32 bits? Ou pelo menos que todos os valores sejam positivos?
Reto Koradi
@RetoKoradi editado com detalhes sobre o alcance
Sparr

Respostas:

5

CJam, 51 44 bytes

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

Essa é uma função anônima que exibe uma matriz 2D da pilha e empurra uma em troca.

Experimente os casos de teste online no interpretador CJam . 1

Idéia

Essa abordagem itera todas as combinações possíveis de elementos de linha, filtra aquelas que não correspondem às costuras, classifica pela soma correspondente, seleciona o mínimo e remove os elementos correspondentes da matriz. 2

Código

_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 Observe que o CJam não pode distinguir entre matrizes vazias e cadeias vazias, pois cadeias são apenas matrizes cujos elementos são caracteres. Portanto, a representação de strings de matrizes vazias e de strings vazias é "".

2 Embora a complexidade de tempo do algoritmo mostrado na página da Wikipedia deva ser O (nm) para uma matriz n × m , essa é pelo menos O (m n ) .

Dennis
fonte
{2ew::m2f/0-!},
Optimizer
Infelizmente, isso não funcionará para o segundo caso de teste. Eu enviei um relatório de bug sobre isso há duas semanas.
Dennis
5

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

Exemplo de uso:

*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]]

Como funciona, versão curta: crie uma lista de todos os caminhos (1), por caminho: remova os elementos correspondentes (2) e some todos os elementos restantes (3). Pegue o retângulo com a maior soma (4).

Versão mais longa:

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.
nimi
fonte
3

IDL 8.3, 307 bytes

Meh, tenho certeza que isso não vai ganhar, porque é longo, mas aqui está uma solução direta:

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

Criamos iterativamente a matriz de energia e rastreamos em que direção a costura segue e, em seguida, construímos uma lista de remoção quando conhecemos a posição final. Remova a costura através da indexação 1D e depois volte para a matriz com as novas dimensões.

sirpercival
fonte
3
Oh Deus ... acho que vomitei um pouco vendo o IDL (de novo). Eu pensei que estava feito vendo que depois da formatura ...
Kyle Kanos
Dito isto, suspeito que isso também funcione para a GDL, para que as pessoas que não estão dispostas a pagar US $ 1 bilhão pela licença de usuário único possam testá-la?
Kyle Kanos #
Eu nunca usei GDL, então não posso dizer (honestamente, esqueci que existia). A única coisa que pode causar um problema é se o GDL não puder lidar com a criação de matriz da sintaxe [0:n]; se isso for verdade, é fácil substituir r+=[0:z[1]-1]*z[0]por r+=indgen(z[1]-1)*z[0].
Sirpercival
Além disso, embora eu prefira usar python para meus golfs, ninguém mais usa IDL, por isso me sinto obrigado a contribuir com XD. Além disso, faz algumas coisas muito bem.
Sirpercival
3
Eu me faz encolher / chorar muito bem;)
Kyle Kanos
3

JavaScript ( ES6 ) 197209 215

Implementação passo a passo do algoritmo da Wikipedia.

Provavelmente pode ser reduzido mais.

Teste a execução do snippet no 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>

edc65
fonte
1

Pip, 91 bytes

Isso não vai ganhar nenhum prêmio, mas eu me diverti trabalhando nisso. O espaço em branco é apenas para fins estéticos e não está incluído na contagem de bytes.

{
 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
}

Esse código define uma função anônima cujo argumento e valor de retorno são listas aninhadas. Ele implementa o algoritmo da página da Wikipedia: a(o argumento) são os números vermelhos e zsão os números pretos.

Aqui está uma versão com equipamento de teste:

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)

Resultados:

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

E aqui está o equivalente aproximado no Python 3. Se alguém quiser uma explicação melhor do código Pip, basta perguntar nos comentários.

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
DLosc
fonte