Haskell, 228 227 225 224 bytes
import Data.List
z=zipWith
a!b=div(max(a*a)(a*b))a
l x=z(!)(z(!)x(0:x))$tail x++[0]
s=(\x->length.($x).filter<$>[(>0),(<0)]).nub.(>>=id).(until=<<((==)=<<))((.)>>=id$transpose.map l).z(\i->z(\j x->2^i*j*(2*x-1))[1,3..])[1..]
Try it online!
Explanation:
The idea for this solution is as follows: Initialise the matrix with unique values in each cell, positive for 1
and negative for 0
. Then repeatedly compare each cell with its neighbours and, if the neighbour has the same sign but a number with a larger absolute value, replace the cell's number wit the neighbour's number. Once this hits a fixed point, count the number of distinct positive numbers for the number of 1
regions and the distinct negative numbers for the number of 0
regions.
In code:
s=(\x->length.($x).filter<$>[(>0),(<0)]).nub.(>>=id).(until=<<((==)=<<))((.)>>=id$transpose.map l).z(\i->z(\j x->2^i*j*(2*x-1))[1,3..])[1..]
can be separated into the preprocessing (assigning numbers to cells), the iteration, and the postprocessing (counting cells)
Preprocessing
The preprocessing part is the function
z(\i->z(\j x->2^i*j*(2*x-1))[1,3..])[1..]
Which uses z
as abbreviation for zipWith
to shave off a few bytes.
What we do here is zip the two dimensional array with integer indices at the rows and odd integer indices at the columns. We do this since we can build a unique integer from a pair of integers (i,j)
using the formula (2^i)*(2j+1)
. If we generate only odd integers for j
, we can skip calculating the 2*j+1
, saving three bytes.
With the unique number, we now only have to multiply in a sign based on the value in the matrix, which is obtained as 2*x-1
Iteration
The iteration is done by
(until=<<((==)=<<))((.)>>=id$transpose.map l)
Since the input is in the form of a list of lists, we perform the neighbour comparison on each row, transpose the matrix, perform the comparison on each row again (which due to the transpose is what was the columns before) and transpose again. The code that accomplishes one of these steps is
((.)>>=id$transpose.map l)
where l
is the comparison function (detailed below) and transpose.map l
performs one half of the comparison and transposition steps. (.)>>=id
performs its argument twice, being the pointfree form of \f -> f.f
and by one byte shorter in this case due to operator precedence rules.
l
is defined in the row above as l x=z(!)(z(!)x(0:x))$tail x++[0]
. This code performs a comparison operator (!)
(see below) on every cell with first its left neighbour, and then with its right neighbour, by zipping the list x
with the right shifted list 0:x
and the left shifted list tail x++[0]
in turn. We use zeroes to pad the shifted lists, since they can never occur in the preprocessed matrix.
a!b
is defined in the row above this as a!b=div(max(a*a)(a*b))a
. What we want to do here is the following case distinction:
- If
sgn(a) = -sgn(b)
, we have two opposing areas in the matrix and do not wish to unify them, so a
stays unchanged
- If
sgn(b) = 0
, we have the corner case where b
is padding and therefore a
stays unchanged
- If
sgn(a) = sgn(b)
, we wish to unify the two areas and take the one with the bigger absolute value (for convenience's sake).
Note that sgn(a)
can never be 0
. We accomplish this with the formula given. If the signs of a
and b
differ, a*b
is less or equal to zero, while a*a
is always greater than zero, so we pick it as the maximum and divide with a
to get back a
. Otherwise, max(a*a)(a*b)
is abs(a)*max(abs(a),(abs(b))
, and by dividing this by a
, we get sgn(a)*max(abs(a),abs(b))
, which is the number with the larger absolute value.
To iterate the function ((.)>>=id$transpose.map l)
until it reached a fixed point, we use (until=<<((==)=<<))
, which is taken from this stackoverflow answer.
Postprocessing
For postprocessing, we use the part
(\x->length.($x).filter<$>[(>0),(<0)]).nub.(>>=id)
which is just a collection of steps.
(>>=id)
squashes the list of lists into a single list,
nub
gets rid of doubles,
(\x->length.($x).filter<$>[(>0),(<0)])
partitions the list into a pair of lists, one for positive and one for negative numbers, and calculates their lengths.
Can we assume the output is a 2-item list? e.g.
1 1
in either order? – streetster – 2019-07-28T10:55:59.657yes but in order for 1's and then 0's island – KB joy – 2019-07-28T10:59:45.110
11You should add a testcase like
[[1,0];[0,1]]
to make sure diagonal connectivity is not included – Sanchises – 2019-07-28T11:01:54.2038I'd suggest the output can be in either order as long as the order is specified - it doesn't add any value to force an order – streetster – 2019-07-28T11:15:58.770
8Welcome to the site! – Arnauld – 2019-07-28T12:36:35.263
1What was answered in the comments should be clarified in the body of the challenge. And more specifically, if you really want us to return 1's before 0's, it should be clearly stated. – Arnauld – 2019-07-28T21:23:07.643
I'd like to see a PMA/Snails answer https://codegolf.stackexchange.com/questions/47311/language-design-2-d-pattern-matching/47495#47495
– Jerry Jeremiah – 2019-07-28T22:56:16.1504Suggested test case:
11111 / 10001 / 10101 / 10001 / 11111
→2 1
– Kevin Cruijssen – 2019-07-29T08:00:23.957Should we output only 2 numbers, or we output the ascii art? – tsh – 2019-07-30T02:23:04.057
@tsh It is clear fom the last 2 test cases that only the counts are needed. – Adám – 2019-07-30T09:08:30.810