简体   繁体   中英

Find connected components in array

I'd like to apply some kind of special pattern find algorithm using Swift.

Some explanations:

I'm getting a simple 1-dimensional array that could look like this:

var array = [ 
               "0000000000000000000",
               "0000001110000000000",
               "0000011111000000000",
               "0000001110000000000",
               "0000000000000000000",
               "0001100000000000000",
               "0001100000000011000",
               "0011100000000011000",
               "0000000000000000000"
            ]

And I'd like to extract the connected areas of "1"-characters (connected components).

Have a look at this:

       111
      11111
       111

 11
 11         11
111         11

I'd like to get as result a multidimensional array that includes all x/y-positions of the single components.

var result = [
                [ [6,1], [7,1], [8,1], [5,2], [6,2], [7,2], [8,2], [9,2], [6,3], [7,3], [8,2] ] // positions of the first area (the biggest one on top)

                [ [3,5], [4,5], [3,6], [4,6], [2,7], [3,7], [4,7] ] // area bottom left

                [ [14,6], [15,6], [14,7], [15,7] ] // area bottom right (smallest area)
             ]

I've coded the function for javascript. You can find the code right here:

 var matrix = [ "0000000000000000000", "0000001110000000000", "0000011111000000000", "0000001110000000000", "0000000000000000000", "0001100000000000000", "0001100000000011000", "0011100000000011000", "0000000000000000000" ] Array.prototype.extract_components_positions = function(offset) { var array = this.map(item => item.split('')).map(str => Array.from(str, Number)), default_value = 0, result_object = {} function test_connection(array, i, j) { if (array[i] && array[i][j] === -1) { if (!result_object[default_value]) result_object[default_value] = []; result_object[default_value].push([j, i]); array[i][j] = 1; for (var k = offset; k > 0; k--) { test_connection(array, i + k, j); // left - right test_connection(array, i, j + k); // top - bottom test_connection(array, i - k, j); // right - left test_connection(array, i, j - k); // bottom - top } return true } } array.forEach(function(a) { a.forEach(function(b, i, bb) { bb[i] = -b }) }); array.forEach(function(a, i, aa) { a.forEach(function(b, j, bb) { test_connection(aa, i, j) && default_value++ }) }) return [result_object]; } var result = matrix.extract_components_positions(1); console.log(JSON.stringify(result)) 

but I have a big problems translating this Javascript code into Swift!

 func extract_components_positions(matrix: [[String]],offset: Int) {
    var array = [[]]  // no idea how to use map to split the array from ["0011100"],... to ["0","0","1","1",...], ...
    var default_value = 0,
        result_object = [[Int]()]

    func testconnection(matrix: [[String]], i: Int, j: Int) -> [[Int]] {
        if (Int(array[i][j] as! Int) == -1) {
            array[i][j] = 1
            for var k in offset...0 {
                testconnection(matrix: array, i: i+k, j: j) // error: "Cannot convert value of type '[[Any]]' to expected argument type '[[String]]'"
                testconnection(matrix: array, i: i, j: j+k)
                testconnection(matrix: array, i: i-k, j: j)
                testconnection(matrix: array, i: i, j: j-k)
            }
        }
    }
    array.forEach { (a) in
        a.forEach({ (b, i, bb) in // error: "Contextual closure type '(Any) -> Void' expects 1 argument, but 3 were used in closure body"
            bb[i] = -b
        })
    }
    array.forEach { (a, i, aa) in // error: "Contextual closure type '([Any]) -> Void' expects 1 argument, but 3 were used in closure body"
        a.forEach({ (b, j, bb) in
            testconnection(aa, i, j) && default_value++
        })
    }
    return result_object
}

Any help how to fix my code would be very appreciated.

Look like you are playing Minesweeper! Here's my solution (in swift 4.0, Xcode 9.2). See inline comments for explanation.

let array = [
    "0000000000000000000",
    "0000001110000000000",
    "0000011111000000000",
    "0000001110000000000",
    "0000000000000000000",
    "0001100000000000000",
    "0001100000000011000",
    "0011100000000011000",
    "0000000000000000000"
]

// A structure to hold the cell's coordinate as Int array
// can become confusing very quickly
struct Cell: Equatable {
    var row: Int
    var column: Int
    var clusterIndex: Int?

    static func == (lhs: Cell, rhs: Cell) -> Bool {
        return lhs.row == rhs.row && lhs.column == rhs.column
    }
}

// Get all the "1" cells
var cells = array.enumerated().flatMap { arg -> [Cell] in
    let (rowIndex, str) = arg

    // The flatMap below will become compactMap in Swift 4.1
    return str.enumerated().flatMap { colIndex, char in
        if char == "1" {
            return Cell(row: rowIndex, column: colIndex, clusterIndex: nil)
        } else {
            return nil
        }
    }
}

// Assign each cell a clusterIndex
for (i, currentCell) in cells.enumerated() {

    // A cell may not have all four neighbors, or not all its
    // neighbors are "1" cells, hence the "potential"
    let potentialNeighbors = [
        Cell(row: currentCell.row - 1, column: currentCell.column, clusterIndex: nil), // above
        Cell(row: currentCell.row + 1, column: currentCell.column, clusterIndex: nil), // below
        Cell(row: currentCell.row, column: currentCell.column - 1, clusterIndex: nil), // left
        Cell(row: currentCell.row, column: currentCell.column + 1, clusterIndex: nil)  // right
    ]

    // Get the actual neighboring cells and their indexes
    let neighborsAndIndexes = cells.enumerated().filter { arg in
        let (_, c) = arg
        return potentialNeighbors.contains(c)
    }
    let neighborIndexes = neighborsAndIndexes.map { $0.0 }
    let neighbors = neighborsAndIndexes.map { $0.1 }

    // Determine what clusterIndex we should give the current cell and its neighbors
    var clusterIndex = 0

    if currentCell.clusterIndex != nil {
        // If the current cell already has a clusteredIndex, reuse it
        clusterIndex = currentCell.clusterIndex!
    } else if let neighborClusterIndex = neighbors.first(where: { $0.clusterIndex != nil })?.clusterIndex {
        // If the current cell has a neighbor whose clusterIndex is not nil, use that
        clusterIndex = neighborClusterIndex
    } else {
        // Else increment from the max existing clusterIndex
        clusterIndex = (cells.map({ $0.clusterIndex ?? 0 }).max() ?? 0) + 1
    }

    // Assign the same clusterIndex to the current cell and its neighbors
    ([i] + neighborIndexes).forEach {
        cells[$0].clusterIndex = clusterIndex
    }
}

// Group the cells by their clusterIndex
let clusters = Dictionary(grouping: cells, by: { $0.clusterIndex! })
    .sorted(by: { $0.key < $1.key })
    .map { $0.value }

// Print the result
// Visualize which cell belong to which cluster and how it appears on the board
for i in 0..<array.count {
    for j in 0..<array[0].count {
        if let clusterIndex = cells.first(where: { $0.row == i && $0.column == j })?.clusterIndex {
            print(clusterIndex, terminator: "")
        } else {
            print("-", terminator: "")
        }
    }
    print() // print a newline
}

Result:

-------------------
------111----------
-----11111---------
------111----------
-------------------
---22--------------
---22---------33---
--222---------33---
-------------------

Note that in Swift 4.1 (currently in beta), the flatMap we use here has been renamed to compactMap . This is not to say that flatMap is going away completely. flatMap has 3 versions, only 1 of them has been renamed to compactMap . For more info, see SE-0187 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM