Podziel mapę przepływów wody

17

To wyzwanie w Internecie zadane przez Palantir Technologies w wywiadach .

Grupa rolników ma pewne dane dotyczące wysokości, a my pomożemy im zrozumieć, w jaki sposób opady deszczu przepływają przez ich pola uprawne. Będziemy reprezentować ziemię jako dwuwymiarowy układ wysokości i zastosujemy następujący model, oparty na pomyśle, że woda spływa w dół:

Jeśli wszystkie cztery sąsiadujące komórki komórki mają wyższe wysokości, nazywamy tę komórkę zlewem; woda zbiera się w zlewach. W przeciwnym razie woda przepłynie do sąsiedniej komórki o najniższej wysokości. Jeśli komórka nie jest zlewem, możesz założyć, że ma unikalny najniższy sąsiad i że ten sąsiad będzie niższy niż komórka.

Mówi się, że komórki, które spływają do tego samego zlewu - bezpośrednio lub pośrednio - są częścią tego samego basenu.

Twoim wyzwaniem jest podzielenie mapy na baseny. W szczególności, biorąc pod uwagę mapę rzędnych, twój kod powinien podzielić mapę na baseny i wyświetlać rozmiary basenów, w kolejności malejącej.

Załóżmy, że mapy wysokości są kwadratowe. Wprowadzanie rozpocznie się od linii z jedną liczbą całkowitą, S, wysokością (i szerokością) mapy. Każda kolejna linia S będzie zawierała wiersz mapy, każda z liczbami całkowitymi S - rzędnymi komórek S w rzędzie. Niektórzy rolnicy mają małe działki, takie jak poniższe przykłady, a niektórzy mają większe działki. Jednak w żadnym przypadku rolnik nie będzie posiadał działki większej niż S = 5000.

Twój kod powinien wyświetlać rozdzieloną spacjami listę rozmiarów umywalek, w kolejności malejącej. (Końcowe spacje są ignorowane).

Kilka przykładów znajduje się poniżej.

Wejście:

3
1 5 2
2 4 7
3 6 9 

Wynik: 7 2

Umywalki oznaczone jako A i B to:

A A B
A A B
A A A 

Wejście:

1
10

Wynik: 1

W tym przypadku jest tylko jeden basen.

Wejście:

5
1 0 2 5 8
2 3 4 7 9
3 5 7 8 9
1 2 5 4 2
3 3 5 2 1 

Wynik: 11 7 7

Umywalki oznaczone jako A, B i C to:

A A A A A
A A A A A
B B A C C
B B B C C
B B C C C 

Wejście:

4
0 2 1 3
2 1 0 4
3 3 3 3
5 5 2 1 

Wynik: 7 5 4

Umywalki oznaczone jako A, B i C to:

A A B B
A B B B
A B B C
A C C C
AnkitSablok
źródło
1
Zredagowałem twoje pytanie, aby było bardziej odpowiednie dla tej witryny. Wcześniej było to pytanie dotyczące programowania / przeglądu kodu. Teraz jest to wyzwanie. Ta strona służy do udostępniania użytkownikom wyzwań / problemów związanych z kodem. Uwaga: nadal potrzebujesz kryteriów wygranej: zalecany jest najkrótszy kod ( code-golf ).
Justin
2
@OP Jeśli chcesz uzyskać odpowiedź na swoje oryginalne pytanie zamiast szeregu alternatywnych rozwiązań golfowych, sugeruję, aby zadać je ponownie w Stack Overflow (a może Code Review?)
Gareth
1
@JanDvorak Myślę, że oryginalne pytanie przed edycją może być w porządku w Code Review (na początku nie było żadnego golfa)? Prawdopodobnie masz rację co do SO.
Gareth
1
@JanDvorak Wydaje mi się, że po prostu edytuj i uczyń z niego poprawnego golfa
Justin
1
Problem dotyczący przeglądu kodu opublikowałem
codereview.stackexchange.com/questions/39895/...

Odpowiedzi:

8

Matematyka

Listę rozmiarów umywalek można zdobyć

WatershedComponents[
 Image[Rest@ImportString[m,"Table"]] // ImageAdjust,
 CornerNeighbors -> False,
 Method -> "Basins"
 ] // Reverse@Sort@Part[Tally[Flatten@#], All, 2] &

gdzie msą podane dane wejściowe. Aby wyświetlić macierz, jak te w pytaniu może zastąpić jeden // Reverse@Sort@Part[Tally[Flatten@#], All, 2] &z /. {1 -> "A", 2 -> "B", 3 -> "C"} // MatrixFormlub można wyświetlić go jako obraz zamiast korzystania //ImageAdjust//Image.


źródło
Nie zostawiaj nas wisi! Posortowana lista rozmiarów umywalek używa BinCounts [] i Sortuj [], prawda?
Scott Leadley
@ ScottLeadley Nie zdawałem sobie sprawy, że poproszono o listę rozmiarów umywalek, dziękuję za zwrócenie na to uwagi. Naprawiłem odpowiedź (chociaż ostatnia część może być prawdopodobnie znacznie krótsza).
2

JavaScript - 673 707 730 751

e=[],g=[],h=[],m=[],q=[];function r(){a=s,b=t;function d(d,A){n=a+d,p=b+A;c>e[n][p]&&(u=!1,v>e[n][p]&&(v=e[n][p],w=n,k=p))}c=e[a][b],u=!0,v=c,w=a,k=b;0!=a&&d(-1,0);a!=l&&d(1,0);0!=b&&d(0,-1);b!=l&&d(0,1);g[a][b]=w;h[a][b]=k;return u}function x(a,b,d){function c(a,b,c,k){g[a+b][c+k]==a&&h[a+b][c+k]==c&&(d=x(a+b,c+k,d))}d++;0!=a&&c(a,-1,b,0);a!=l&&c(a,1,b,0);0!=b&&c(a,0,b,-1);b!=l&&c(a,0,b,1);return d}y=$EXEC('cat "'+$ARG[0]+'"').split("\n");l=y[0]-1;for(z=-1;z++<l;)e[z]=y[z+1].split(" "),g[z]=[],h[z]=[];for(s=-1;s++<l;)for(t=-1;t++<l;)r()&&m.push([s,t]);for(z=m.length-1;0<=z;--z)s=m[z][0],t=m[z][1],q.push(x(s,t,0));print(q.sort(function(a,b){return b-a}).join(" "));

Wyniki testu (przy użyciu Nashorn):

$ for i in A B C D; do jjs -scripting minlm.js -- "test$i"; done
7 2
1
11 7 7
7 5 4
$

Prawdopodobnie wystąpiłyby problemy ze stosem dla map wielkości 5000 (ale to szczegół implementacji :).

Niezminimalizowane źródło w całej swej kruchości:

// lm.js - find the local minima


//  Globalization of variables.

/*
    The map is a 2 dimensional array. Indices for the elements map as:

    [0,0] ... [0,n]
    ...
    [n,0] ... [n,n]

Each element of the array is a structure. The structure for each element is:

Item    Purpose         Range       Comment
----    -------         -----       -------
h   Height of cell      integers
s   Is it a sink?       boolean
x   X of downhill cell  (0..maxIndex)   if s is true, x&y point to self
y   Y of downhill cell  (0..maxIndex)

Debugging only:
b   Basin name      ('A'..'A'+# of basins)

Use a separate array-of-arrays for each structure item. The index range is
0..maxIndex.
*/
var height = [];
var sink = [];
var downhillX = [];
var downhillY = [];
//var basin = [];
var maxIndex;

//  A list of sinks in the map. Each element is an array of [ x, y ], where
// both x & y are in the range 0..maxIndex.
var basinList = [];

//  An unordered list of basin sizes.
var basinSize = [];


//  Functions.

function isSink(x,y) {
    var myHeight = height[x][y];
    var imaSink = true;
    var bestDownhillHeight = myHeight;
    var bestDownhillX = x;
    var bestDownhillY = y;

    /*
        Visit the neighbors. If this cell is the lowest, then it's the
    sink. If not, find the steepest downhill direction.

        This would be the place to test the assumption that "If a cell
    is not a sink, you may assume it has a unique lowest neighbor and
    that this neighbor will be lower than the cell." But right now, we'll
    take that on faith.
    */
    function visit(deltaX,deltaY) {
        var neighborX = x+deltaX;
        var neighborY = y+deltaY;
        if (myHeight > height[neighborX][neighborY]) {
            imaSink = false;
            if (bestDownhillHeight > height[neighborX][neighborY]) {
                bestDownhillHeight = height[neighborX][neighborY];
                bestDownhillX = neighborX;
                bestDownhillY = neighborY;
            }
        }
    }
    if (x !== 0) {
        // upwards neighbor exists
        visit(-1,0);
    }
    if (x !== maxIndex) {
        // downwards neighbor exists
    visit(1,0);
    }
    if (y !== 0) {
        // left-hand neighbor exists
        visit(0,-1);
    }
    if (y !== maxIndex) {
        // right-hand neighbor exists
        visit(0,1);
    }

    downhillX[x][y] = bestDownhillX;
    downhillY[x][y] = bestDownhillY;
    return imaSink;
}

function exploreBasin(x,y,currentSize) {//,basinName) {
    //  This cell is in the basin.
    //basin[x][y] = basinName;
    currentSize++;

    /*
        Visit all neighbors that have this cell as the best downhill
    path and add them to the basin.
    */
    function visit(x,deltaX,y,deltaY) {
        if ((downhillX[x+deltaX][y+deltaY] === x) && (downhillY[x+deltaX][y+deltaY] === y)) {
            currentSize = exploreBasin(x+deltaX,y+deltaY,currentSize); //,basinName);
        }
        return 0;
    }
    if (x !== 0) {
        // upwards neighbor exists
        visit(x,-1,y,0);
    }
    if (x !== maxIndex) {
        // downwards neighbor exists
        visit(x,1,y,0);
    }
    if (y !== 0) {
        // left-hand neighbor exists
        visit(x,0,y,-1);
    }
    if (y !== maxIndex) {
        // right-hand neighbor exists
        visit(x,0,y,1);
    }

    return currentSize;
}

//  Read map from file (1st argument).
var lines = $EXEC('cat "' + $ARG[0] + '"').split('\n');
maxIndex = lines.shift() - 1;
for (var i = 0; i<=maxIndex; i++) {
    height[i] = lines.shift().split(' ');
    //  Create all other 2D arrays.
    sink[i] = [];
    downhillX[i] = [];
    downhillY[i] = [];
    //basin[i] = [];
}

//  Everyone decides if they are a sink. Create list of sinks (i.e. roots).
for (var x=0; x<=maxIndex; x++) {
    for (var y=0; y<=maxIndex; y++) {
        if (sink[x][y] = isSink(x,y)) {
            //  This node is a root (AKA sink).
            basinList.push([x,y]);
        }
    }
}
//for (var i = 0; i<=maxIndex; i++) { print(sink[i]); }

//  Each root explores it's basin.
//var basinName = 'A';
for (var i=basinList.length-1; i>=0; --i) { // i-- makes Closure Compiler sad
    var x = basinList[i][0];
    var y = basinList[i][1];
    basinSize.push(exploreBasin(x,y,0)); //,basinName));
    //basinName = String.fromCharCode(basinName.charCodeAt() + 1);
}
//for (var i = 0; i<=maxIndex; i++) { print(basin[i]); }

//  Done.
print(basinSize.sort(function(a, b){return b-a}).join(' '));

Lepsze wyniki minimalizacji uzyskałem, dzieląc obiekty elementów na osobne tablice, globalizując wszędzie, gdzie to możliwe, i obejmując skutki uboczne. NSFW.

Skutki minimalizacji kodu:

  • 4537 bajtów, nieupoważniona
  • 1180 bajtów, pakujący
  • 855 bajtów, optymalizacja pakera + dłoni (globalne nazwy 1 znaków)
  • 751 bajtów, kompilator zamknięcia Google z ADVANCED_OPTIMIZATIONS (NB, uniknął szczątkowego „return 0” jako martwego kodu)
  • 730 bajtów, lekkomyślna optymalizacja ręki (nie zmieniam niezminimalizowanego źródła, więc NSFW)
  • 707 bajtów, bardziej nierozważna optymalizacja ręki (usuń wszystkie odniesienia do sink []);
  • 673 bajty, usuń wszystkie „var”, upuść flagę Nashorn -strict

Mógłbym osiągnąć blisko 700 bajtów bez edycji zminimalizowanego kodu, gdybym chciał zmodyfikować oryginalne źródło. Ale nie zrobiłem tego, ponieważ myślę, że pozostawienie go takim, jakim jest, daje ciekawy widok od samego początku.

Scott Leadley
źródło
Możesz skrócić var e=[],g=[],h=[],l,m=[],q=[]do e=g=h=l=m=q=[]. Prawdopodobnie możesz również pozbyć się innych zastosowań tego varsłowa kluczowego, jeśli nie ukrywasz żadnych zmiennych globalnych.
nyuszika7h
@ nyuszika7h Nie da się. e = g = h = l = m = q = [] sprawi, że wszystkie użyją wskaźnika do tej samej tablicy. A Nashorn wymaga var.
Scott Leadley
@ nyuszika7h Wykopałeś mnie z mojej koleiny. Upuściłem Nashorn-surowe i usunąłem wszystkie „var”.
Scott Leadley,
1

Python: 276 306 365 bajtów

To moja pierwsza próba golfa. Sugestie są mile widziane!

edycja: importowanie i zamykanie plików zajmuje zbyt wiele znaków! Podobnie jest z przechowywaniem plików w zmiennych i interpretacją listy zagnieżdżonej.

t=map(int,open('a').read().split());n=t.pop(0);q=n*n;r,b,u=range(q),[1]*q,1
while u!=0:
    u=0
    for j in r:
        d=min((t[x],x)for x in [j,j-1,j+1,j-n,j+n]if int(abs(j/n-x/n))+abs(j%n-x%n)<=1 and x in r)[1]
        if j-d:u|=b[j];b[d]+=b[j];b[j]=0
for x in sorted(b)[::-1]:print x or '',

w pełni skomentowane (2130 bajtów ...)

from math import floor
with open('a') as f:
    l = f.read()
    terrain = map(int,l.split()) # read in all the numbers into an array (treating the 2D array as flattened 1D)
    n = terrain.pop(0) # pop the first value: the size of the input
    valid_indices = range(n*n) # 0..(n*n)-1 are the valid indices of this grid
    water=[1]*(n*n) # start with 1 unit of water at each grid space. it will trickle down and sum in the basins.
    updates=1 # keep track of whether each iteration included an update

    # helper functions
    def dist(i,j):
        # returns the manhattan (L1) distance between two indices
        row_dist = abs(floor(j/n) - floor(i/n))
        col_dist = abs(j % n - i % n)
        return row_dist + col_dist

    def neighbors(j):
        # returns j plus up to 4 valid neighbor indices
        possible = [j,j-1,j+1,j-n,j+n]
        # validity criteria: neighbor must be in valid_indices, and it must be one space away from j
        return [x for x in possible if dist(x,j)<=1 and x in valid_indices]

    def down(j):
        # returns j iff j is a sink, otherwise the minimum neighbor of j
        # (works by constructing tuples of (value, index) which are min'd
        # by their value, then the [1] at the end returns its index)
        return min((terrain[i],i) for i in neighbors(j))[1]

    while updates!=0: # break when there are no further updates
        updates=0 # reset the update count for this iteration
        for j in valid_indices: # for each grid space, shift its water 
            d =down(j)
            if j!=d: # only do flow if j is not a sink
                updates += water[j] # count update (water[j] is zero for all non-sinks when the sinks are full!)
                water[d] += water[j] # move all of j's water into the next lowest spot
                water[j] = 0 # indicate that all water has flown out of j
    # at this point, `water` is zeros everywhere but the sinks.
    # the sinks have a value equal to the size of their watershed.
    # so, sorting `water` and printing nonzero answers gives us the result we want!
    water = sorted(water)[::-1] # [::-1] reverses the array (high to low)
    nonzero_water = [w for w in water if w] # 0 evaulates to false.
    print " ".join([str(w) for w in nonzero_water]) # format as a space-separated list
wrongu
źródło
Proszę nie grać w golfa przez rok. 365 znaków jest zbyt ładne. : P
tomsmeding
1
Zredukowałem to do 306! Potrzebuję tych dodatkowych 59 dni urlopu.
wrongu
open('a').read()Myślę, że powinieneś być w stanie to zrobić .
MrLemon
1

JavaScript (ECMAScript 6) - 226 znaków

s=S.split(/\s/);n=s.shift(k=[]);u=k.a;t=s.map((v,i)=>[v,i,1]);t.slice().sort(X=(a,b)=>a[0]-b[0]).reverse().map(v=>{i=v[1];p=[v,i%n?t[i-1]:u,t[i-n],(i+1)%n?t[i+1]:u,t[+n+i]].sort(X)[0];p==v?k.push(v[2]):p[2]+=v[2]});k.join(' ')

Wyjaśnienie

s=S.split(/\s/);                  // split S into an array using whitespace as the boundary.
n=s.shift();                      // remove the grid size from s and put it into n.
k=[];                             // an empty array to hold the position of the sinks.
u=k.a;                            // An undefined variable
t=s.map((v,i)=>[v,i,1]);          // map s to an array of:
                                  // - the elevation
                                  // - the position of this grid square
                                  // - the number of grid squares which have flowed into
                                  //      this grid square (initially 1).
X=(a,b)=>a[0]-b[0];               // A comparator function for sorting.
t.slice()                         // Take a copy of t
 .sort(X)                         // Then sort it by ascending elevation
 .reverse()                       // Reverse it to be sorted in descending order
 .map(v=>{                        // For each grid square (starting with highest elevation)
   i=v[1];                        // Get the position within the grid
   p=[v,i%n?t[i-1]:u,t[i-n],(i+1)%n?t[i+1]:u,t[+n+i]]
                                  // Create an array of the grid square and 4 adjacent
                                  //   squares (or undefined if off the edge of the grid)
     .sort(X)                     // Then sort by ascending elevation
     [0];                         // Then get the square with the lowest elevation.
   p==v                           // If the current grid square has the lowest elevation
     ?k.push(v[2])                // Then add the number of grid square which have
                                  //   flowed into it to k
     :p[2]+=v[2]});               // Else flow the current grid square into its lowest
                                  //   neighbour.
k.join(' ')                       // Output the sizes of the block with  space separation.

Poprzednia wersja - 286 znaków

s=S.split(/\s/);n=s.shift()*1;k=[];u=k[1];t=s.map((v,i)=>({v:v,p:i,o:[]}));for(i in t){t[p=[t[i],i%n?t[i-1]:u,t[i-n],(+i+1)%n?t[+i+1]:u,t[+i+n]].sort((a,b)=>(a.v-b.v))[0].p].o.push([i]);p==i&&k.push([i])}k.map(x=>{while(x[L="length"]<(x=[].concat(...x.map(y=>t[y].o)))[L]);return x[L]})

Zakłada, że ​​dane wejściowe są w zmiennej S;

Wyjaśnienie

s=S.split(/\s/);                  // split S into an array using whitespace as the boundary.
n=s.shift()*1;                    // remove the grid size from s and put it into n.
k=[];                             // an empty array to hold the position of the sinks.
u=k[1];                           // Undefined
t=s.map((v,i)=>({v:v,p:i,o:[]})); // map s to an Object with attributes:
                                  // - v: the elevation
                                  // - p: the position of this grid square
                                  // - o: an array of positions of neighbours which
                                  //      flow into this grid square.
for(i in t){                      // for each grid square
  p=[t[i],i%n?t[i-1]:u,t[i-n],(+i+1)%n?t[+i+1]:u,t[+i+n]]
                                  // start with an array containing the objects 
                                  //   representing that grid square and its 4 neighbours
                                  //   (or undefined for those neighbours which are
                                  //   outside the grid)
      .sort((a,b)=>(a.v-b.v))     // then sort that array in ascending order of elevation
      [0].p                       // then get the first array element (with lowest
                                  //   elevation) and get the position of that grid square.
  t[p].o.push([i]);               // Add the position of the current grid square to the
                                  //   array of neighbours which flow into the grid square
                                  //   we've just found.
  p==i&&k.push([i])               // Finally, if the two positions are identical then
                                  //   we've found a sink so add it to the array of sinks (k)
}
k.map(x=>{                        // For each sink start with an array, x, containing the
                                  //   position of the sink.
  while(x.length<(x=[].concat(...x.map(y=>t[y].o))).length);
                                  // Compare x to the concatenation of x with all the
                                  //   positions of grid squares which flow into squares
                                  //   in x and loop until it stops growing.
  return x.length                 // Then return the number of grid squares.
})

Test

S="3\n1 5 2\n2 4 7\n3 6 9";
s=S.split(/\s/);n=s.shift()*1;k=[];u=k[1];t=s.map((v,i)=>({v:v,p:i,o:[]}));for(i in t){t[p=[t[i],i%n?t[i-1]:u,t[i-n],(+i+1)%n?t[+i+1]:u,t[+i+n]].sort((a,b)=>(a.v-b.v))[0].p].o.push([i]);p==i&&k.push([i])}k.map(x=>{while(x[L="length"]<(x=[].concat(...x.map(y=>t[y].o)))[L]);return x[L]})

Wyjścia: [7, 2]

S="5\n1 0 2 5 8\n2 3 4 7 9\n3 5 7 8 9\n1 2 5 4 2\n3 3 5 2 1"
s=S.split(/\s/);n=s.shift()*1;k=[];u=k[1];t=s.map((v,i)=>({v:v,p:i,o:[]}));for(i in t){t[p=[t[i],i%n?t[i-1]:u,t[i-n],(+i+1)%n?t[+i+1]:u,t[+i+n]].sort((a,b)=>(a.v-b.v))[0].p].o.push([i]);p==i&&k.push([i])}k.map(x=>{while(x[L="length"]<(x=[].concat(...x.map(y=>t[y].o)))[L]);return x[L]})

Wyjścia: [11, 7, 7]

S="4\n0 2 1 3\n2 1 0 4\n3 3 3 3\n5 5 2 1"
s=S.split(/\s/);n=s.shift()*1;k=[];u=k[1];t=s.map((v,i)=>({v:v,p:i,o:[]}));for(i in t){t[p=[t[i],i%n?t[i-1]:u,t[i-n],(+i+1)%n?t[+i+1]:u,t[+i+n]].sort((a,b)=>(a.v-b.v))[0].p].o.push([i]);p==i&&k.push([i])}k.map(x=>{while(x[L="length"]<(x=[].concat(...x.map(y=>t[y].o)))[L]);return x[L]})

Wyjścia: [5, 7, 4]

MT0
źródło
1
Moim zdaniem definicje funkcji strzałek (=>) są znacznie wyraźniejsze.
Scott Leadley,
1

Julia, 315

function f(a,i,j)
    z=size(a,1)
    n=filter((x)->0<x[1]<=z&&0<x[2]<=z,[(i+1,j),(i-1,j),(i,j-1),(i,j+1)])
    v=[a[b...] for b in n]
    all(v.>a[i,j]) && (return i,j)
    f(a,n[indmin(v)]...)
end
p(a)=prod(["$n " for n=(b=[f(a,i,j) for i=1:size(a,1),j=1:size(a,2)];sort([sum(b.==s) for s=unique(b)],rev=true))])

Wystarczy funkcja rekurencyjna, która określa, że ​​bieżąca komórka jest zlewem lub znajduje odpływ, a następnie wywołuje ją na każdym zestawie wskaźników. Nie zawracałem sobie głowy wykonaniem części wejściowej, ponieważ i tak nie zamierzałem wygrać, a ta część nie jest fajna.

gggg
źródło
1

Haskell, 271 286

import Data.List
m=map
q[i,j]=[-1..1]>>= \d->[[i+d,j],[i,j+d]]
x%z=m(\i->snd.fst.minimum.filter((`elem`q i).snd)$zip(zip z[0..])x)x
g(n:z)=iterate(\v->m(v!!)v)(sequence[[1..n],[1..n]]%z)!!(n*n)
main=interact$unwords.m show.reverse.sort.m length.group.sort.g.m read.words

Być może nadal będzie jakiś kod do gry w golfa.

& runhaskell 19188-Partition.hs <<INPUT
> 5
> 1 0 2 5 8
> 2 3 4 7 9
> 3 5 7 8 9
> 1 2 5 4 2
> 3 3 5 2 1
INPUT
11 7 7

Wyjaśnienie

Podstawowy pomysł: dla każdej komórki (i, j) znajdź najniższą komórkę w „sąsiedztwie”. Daje to wykres [ (i, j)(mi, mj) ]. Jeśli komórka jest samą najniższą komórką, to (i, j) == (mi, mj) .

Ten wykres można iterować: Dla każdego a → b na wykresie zastąp go a → c, gdzie b → c jest na wykresie. Gdy ta iteracja nie przyniesie więcej zmian, wówczas każda komórka na wykresie wskazuje najniższą komórkę, do której przepłynie.

Aby to zrobić, wprowadzono kilka zmian: Po pierwsze, współrzędne są reprezentowane jako lista długości 2, a nie pary. Po drugie, po znalezieniu sąsiadów komórki są reprezentowane przez ich indeks w liniowym układzie komórek, a nie we współrzędnych 2D. Po trzecie, ponieważ istnieje n * n komórek, po n * n iteracjach wykres musi być stabilny.

Ungolf'd

type Altitude = Int     -- altitude of a cell

type Coord = Int        -- single axis coordinate: 1..n
type Coords = [Coord]   -- 2D location, a pair of Coord
    -- (Int,Int) would be much more natural, but Coords are syntehsized
    -- later using sequence, which produces lists

type Index = Int        -- cell index
type Graph = [Index]    -- for each cell, the index of a lower cell it flows to


neighborhood :: Coords -> [Coords]                              -- golf'd as q
neighborhood [i,j] = concatMap (\d -> [[i+d,j], [i,j+d]]) [-1..1]
    -- computes [i-1,j] [i,j-1] [i,j] [i+1,j] [i,j+1]
    -- [i,j] is returned twice, but that won't matter for our purposes

flowsTo :: [Coords] -> [Altitude] -> Graph                      -- golf'd as (%)
flowsTo cs vs = map lowIndex cs
  where
    lowIndex is = snd . fst                          -- take just the Index of
                  . minimum                          -- the lowest of
                  . filter (inNeighborhood is . snd) -- those with coords nearby
                  $ gv                               -- from the data

    inNeighborhood :: Coords -> Coords -> Bool
    inNeighborhood is ds = ds `elem` neighborhood is

    gv :: [((Altitude, Index), Coords)]
        -- the altitudes paired with their index and coordinates
    gv = zip (zip vs [0..]) cs


flowInput :: [Int] -> Graph                                     -- golf'd as g
flowInput (size:vs) = iterate step (flowsTo coords vs) !! (size * size)
  where
    coords = sequence [[1..size],[1..size]]
        -- generates [1,1], [1,2] ... [size,size]

    step :: Graph -> Graph
    step v = map (v!!) v
        -- follow each arc one step

main' :: IO ()
main' = interact $
            unwords . map show      -- counts a single line of text
            . reverse . sort        -- counts from hi to lo
            . map length            -- for each common group, get the count
            . group . sort          -- order cells by common final cell index
            . flowInput             -- compute the final cell index graph
            . map read . words      -- all input as a list of Int
MtnViewMark
źródło
Byłoby wspaniale, gdybyś mógł wyjaśnić, co się tutaj dzieje.
Nie, że Charles
@Charles - gotowe!
MtnViewMark
1

Ruby, 216

r=[]
M=gets('').split.map &:to_i
N=M.shift
g=M.map{1}
M.sort.reverse.map{|w|t=[c=M.index(w),c%N<0?c:c-1,c%N<N-1?c+1:c,c+N,c-N].min_by{|y|M[y]&&y>=0?M[y]:M.max}
M[c]+=1
t!=c ?g[t]+=g[c]:r<<g[c]}
$><<r.sort.reverse*' '

Jest to nieco inne podejście, które wywołuje „przepływ” tylko na każdym kwadracie (wydajność zależy od wydajności Array :: index). Przechodzi od najwyższego wzniesienia do najniższego, opróżniając pojedynczo komórkę do swojego najniższego sąsiada i zaznaczając komórkę jako wykonaną (dodając 1 do rzędnej), gdy jest zrobiona.

Skomentowane i rozmieszczone:

results=[]
ELEVATIONS = gets('').split.map &:to_i  # ELEVATIONS is the input map
MAP_SIZE = ELEVATIONS.shift             # MAP_SIZE is the first line of input
watershed_size = ELEVATIONS.map{1}      # watershed_size is the size of the watershed of each cell

ELEVATIONS.sort.reverse.map { |water_level| 
    # target_index is where the water flows to.  It's the minimum elevation of the (up to) 5 cells:
    target_index = [
        current_index = ELEVATIONS.index(water_level),                              # this cell
        (current_index % MAP_SIZE) < 0           ? current_index : current_index-1, # left if possible
        (current_index % MAP_SIZE) >= MAP_SIZE-1 ? current_index : current_index+1, # right if possible
        current_index + MAP_SIZE,                                                   # below
        current_index - MAP_SIZE                                                    # above
    ].min_by{ |y|
        # if y is out of range, use max. Else, use ELEVATIONS[y]
        (ELEVATIONS[y] && y>=0) ? ELEVATIONS[y] : ELEVATIONS.max
    }
# done with this cell.
# increment the elevation to mark done since it no longer matters
ELEVATIONS[current_index] += 1

# if this is not a sink
(target_index != current_index) ? 
    # add my watershed size to the target's
    watershed_size[target_index] += watershed_size[current_index] 
    # else, push my watershed size onto results
    : results << watershed_size[current_index]}

Dziennik zmian:

216 - lepszy sposób na odznaczenie wskaźników spoza zakresu

221 - okazuje się, że „11” pojawia się przed „2” ... powróć do to_i, ale zaoszczędź trochę miejsca na naszych getses.

224 - Po co w ogóle deklarować s? I each=>map

229 - masywna gra w golfa - najpierw posortuj elewacje w s(i w ten sposób upuść whileklauzulę), użyj min_byzamiast sort_by{...}[0], nie zawracaj sobie głowy to_iwysokościami, użyj flat_mapi zmniejsz select{}blok

271 - przeniesiono rozmiar zlewu do nowej tablicy i użyto sort_by

315 - przeniesiono wyniki do tablicy, która dawała różnego rodzaju korzyści, i skrócono listę indeksów sąsiadów. również zyskał jeden znak w indeksie lambda

355 - pierwsze zatwierdzenie

Nie ten Charles
źródło
1

Python - 470 447 445 393 392 378 376 375 374 369 bajtów

Nie mogę się powstrzymać!

Nie jest to zwycięskie rozwiązanie, ale miałem dużo zabawy z jego tworzeniem. Ta wersja nie zakłada, że ​​dane wejściowe mają być przechowywane w dowolnym miejscu i zamiast tego odczytuje je ze standardowego wejścia. Maksymalna głębokość rekurencji = największa odległość od punktu do zlewu.

def f(x,m=[],d=[],s=[]):
 n=[e[a]if b else 99for a,b in(x-1,x%z),(x+1,x%z<z-1),(x-z,x/z),(x+z,x/z<z-1)];t=min(n)
 if t<e[x]:r=f(x+(-1,1,-z,z)[n.index(t)])[0];s[r]+=x not in m;m+=[x]
 else:c=x not in d;d+=[x]*c;r=d.index(x);s+=[1]*c
 return r,s
z,e=input(),[]
exec'e+=map(int,raw_input().split());'*z
for x in range(z*z):s=f(x)[1]
print' '.join(map(str,sorted(s)[::-1]))

Nie mam dzisiaj czasu, aby to wyjaśnić, ale oto niepoznany kod:

W rzeczywistości jest zupełnie inny niż oryginalny kod. Czytam linie S ze standardowego, podzielonego, mapującego na int i spłaszczam listy, by uzyskać spłaszczone pole. Następnie raz przewijam wszystkie płytki (nazwijmy je kafelkami). Funkcja przepływu sprawdza sąsiadujące kafelki i wybiera tę o najmniejszej wartości. Jeśli jest mniejsza niż wartość bieżącego kafelka, przejdź do niego i wróć. Jeśli nie, bieżący kafelek jest umywalką i tworzona jest nowa umywalka. Zwracana wartość rekurencji to identyfikator basenu.

# --- ORIGINAL SOURCE ---

# lowest neighboring cell = unique and next
# neihboring cells all higher = sink and end

basinm = [] # list of the used tiles
basins = {} # list of basin sizes
basinf = [] # tuples of basin sinks
field = []  # 2d-list representing the elevation map
size = 0

def flow(x, y):
    global basinf, basinm
    print "Coordinate: ", x, y
    nearby = []
    nearby += [field[y][x-1] if x > 0 else 99]
    nearby += [field[y][x+1] if x < size-1 else 99]
    nearby += [field[y-1][x] if y > 0 else 99]
    nearby += [field[y+1][x] if y < size-1 else 99]
    print nearby
    next = min(nearby)
    if next < field[y][x]:
        i = nearby.index(next)
        r = flow(x+(-1,1,0,0)[i], y+(0,0,-1,1)[i])
        if (x,y) not in basinm:
            basins[r] += 1
            basinm += [(x,y)]
    else:
        c = (x,y) not in basinf
        if c:
            basinf += [(x,y)]
        r = basinf.index((x,y))
        if c: basins[r] = 1
    return r

size = input()
field = [map(int,raw_input().split()) for _ in range(size)]
print field
for y in range(size):
    for x in range(size):
        flow(x, y)
print
print ' '.join(map(str,sorted(basins.values(),reverse=1)))
patrz
źródło
1

JavaScript (ES6) 190 203

Edytuj Trochę więcej ES6ish (1 rok później ...)

Zdefiniuj funkcję z wierszami wejściowymi jako ciągiem znaków, w tym znakami nowej linii, zwracaj dane wyjściowe jako ciąg znaków ze spacjami końcowymi

F=l=>{[s,...m]=l.split(/\s+/);for(j=t=[];k=j<s*s;t[i]=-~t[i])for(i=j++;k;i+=k)k=r=0,[for(z of[-s,+s,i%s?-1:+s,(i+1)%s?1:+s])(q=m[z+i]-m[i])<r&&(k=z,r=q)];return t.sort((a,b)=>b-a).join(' ')}

// Less golfed
U=l=>{
      [s,...m] = l.split(/\s+/);
      for (j=t=[]; k=j<s*s; t[i]=-~t[i])
        for(i=j++; k; i+=k)
          k=r=0,
          [for(z of [-s,+s,i%s?-1:+s,(i+1)%s?1:+s]) (q=m[z+i]-m[i]) < r && (k=z,r=q)];
      return t.sort((a,b)=>b-a).join(' ')
    }

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

out(F('5\n1 0 2 5 8\n 2 3 4 7 9\n 3 5 7 8 9\n 1 2 5 4 2\n 3 3 5 2 1'))// "11 7 7"

out(F('4\n0 2 1 3\n2 1 0 4\n3 3 3 3\n5 5 2 1')) //"7 5 4"
<pre id=O></pre>

edc65
źródło
0

Perl 6, 419 404

Dodano nowe linie dla przejrzystości. Możesz je bezpiecznie usunąć.

my \d=$*IN.lines[0];my @a=$*IN.lines.map(*.trim.split(" "));my @b;my $i=0;my $j=0;
for @a {for @$_ {my $c=$_;my $p=$i;my $q=$j;my &y={@a[$p+$_[0]][$q+$_[1]]//Inf};
loop {my @n=(0,1),(1,0);push @n,(-1,0) if $p;push @n,(0,-1) if $q;my \[email protected](
&y)[0];my \h=y(o);last if h>$c;$c=h;$p+=o[0];$q+=o[1]};@b[$i][$j]=($p,$q);++$j};
$j=0;++$i};say join " ",bag(@b.map(*.flat).flat.map(~*)).values.sort: {$^b <=>$^a}

Stare rozwiązanie:

my \d=$*IN.lines[0];my @a=$*IN.lines.map(*.trim.split(" "));my @b;my $i=0;my $j=0;
for @a {for @$_ {
my $c=$_;my $p=$i;my $q=$j;
loop {my @n=(0,1),(1,0);@n.push: (-1,0) if $p;@n.push: (0,-1) if $q;
my \[email protected]({@a[$p+$_[0]][$q+$_[1]]//Inf})[0];
my \h=@a[$p+o[0]][$q+o[1]];last if h>$c;
$c=h;$p+=o[0];$q+=o[1]};@b[$i][$j]=($p,$q);++$j};$j=0;++$i};
say join " ",bag(@b.map(*.flat.flat).flat.map(~*)).values.sort: {$^b <=>$^a}

A jednak mnie biją rozwiązania Python i JavaScript.

bb94
źródło