Arrays

3 problems from Rosalind — Algorithmic Heights. Press Run on any block to execute it in your browser.

MAJ — Majority Element

solvedbrowser + CLI Problem statement Open in the workbench Download .bl

Writing this found that `{}` parsed as an empty block and evaluated to nil, so a tally could not be opened the obvious way. Fixed in the parser; `{}` in value position is now an empty map.

# Rosalind: MAJ — Majority Element
# https://rosalind.info/problems/maj/
#
# Given: A positive integer k, a positive integer n, and k arrays of size n.
# Return: For each array, an element occurring strictly more than n/2 times,
# or -1 if there is none.

let arrays = [
    [5, 5, 5, 5, 5, 5, 5, 5],
    [8, 7, 7, 7, 1, 7, 3, 7],
    [7, 1, 6, 5, 10, 100, 1000, 1],
    [5, 1, 6, 7, 1, 1, 10, 1],
]

# A tally is the direct reading of the definition. Comparing 2 * count against
# the length keeps the test in integers, so "strictly more than n/2" stays exact
# for odd n instead of depending on how a division rounds.
fn majority(xs) {
    let counts = {}
    for value in xs {
        let key = str(value)
        if contains(keys(counts), key) {
            counts[key] = counts[key] + 1
        } else {
            counts[key] = 1
        }
    }

    let answer = -1
    for key in keys(counts) {
        if counts[key] * 2 > len(xs) {
            answer = int(key)
        }
    }
    answer
}

let result = arrays |> map(|xs| str(majority(xs))) |> join(" ")

println("Result:   " + result)
println("Expected: 5 7 -1 -1")

fn test_maj_majority_elements() {
    assert result == "5 7 -1 -1", "MAJ: got " + result
}

2SUM — 2SUM

solvedbrowser + CLI Problem statement Open in the workbench Download .bl

Any valid pair is accepted, so the answers are checked against the definition rather than against the sample string: the scan reaches 8 and -8 first and reports 1 5 where the sample reports 2 4.

# Rosalind: 2SUM — 2SUM
# https://rosalind.info/problems/2sum/
#
# Given: A positive integer k, a positive integer n, and k arrays of size n.
# Return: For each array, two indices 1 <= p < q <= n with A[p] = -A[q] if they
# exist, and -1 otherwise.

let arrays = [
    [2, -3, 4, 10, 5],
    [8, 2, 4, -2, -8],
    [-5, 2, 3, 2, -4],
    [5, 4, -5, 6, 8],
]

# Written as A[p] + A[q] == 0 rather than A[p] == -A[q]: the same condition, but
# it does not depend on how unary minus binds inside an index expression.
fn two_sum(xs) {
    let answer = "-1"
    let p = 0
    while p < len(xs) and answer == "-1" {
        let q = p + 1
        while q < len(xs) and answer == "-1" {
            if xs[p] + xs[q] == 0 {
                answer = str(p + 1) + " " + str(q + 1)
            }
            q = q + 1
        }
        p = p + 1
    }
    answer
}

# Any valid pair is accepted, so the answers are checked against the definition
# rather than against one particular string. The sample output reports "2 4" for
# the second array; the scan below reaches 8 and -8 first and reports "1 5",
# which is equally correct.
fn is_valid(xs, answer) {
    if answer == "-1" {
        let found = false
        let p = 0
        while p < len(xs) {
            let q = p + 1
            while q < len(xs) {
                if xs[p] + xs[q] == 0 {
                    found = true
                }
                q = q + 1
            }
            p = p + 1
        }
        found == false
    } else {
        let parts = answer |> split(" ") |> map(|t| int(t))
        let p = parts[0] - 1
        let q = parts[1] - 1
        p < q and xs[p] + xs[q] == 0
    }
}

let answers = arrays |> map(|xs| two_sum(xs))
let verdicts = range(0, len(arrays)) |> map(|i| is_valid(arrays[i], answers[i]))

for i in range(0, len(arrays)) {
    println("Array " + str(i + 1) + ": " + answers[i] + "  valid: " + str(verdicts[i]))
}
println("Sample output: -1 / 2 4 / -1 / 1 3")

fn test_2sum_answers_are_valid() {
    assert verdicts == [true, true, true, true], "2SUM: " + str(verdicts)
    assert answers[0] == "-1", "2SUM[1]: expected no pair, got " + answers[0]
    assert answers[2] == "-1", "2SUM[3]: expected no pair, got " + answers[2]
}

3SUM — 3SUM

solvedbrowser + CLI Problem statement Open in the workbench Download .bl

# Rosalind: 3SUM — 3SUM
# https://rosalind.info/problems/3sum/
#
# Given: A positive integer k, a positive integer n, and k arrays of size n.
# Return: For each array, three indices 1 <= p < q < r <= n with
# A[p] + A[q] + A[r] = 0 if they exist, and -1 otherwise.

let arrays = [
    [2, -3, 4, 10, 5],
    [8, -6, 4, -2, -8],
    [-5, 2, 3, 2, -4],
    [2, 4, -5, 6, 8],
]

# The straightforward cubic scan. Taking the indices in increasing order gives
# the lexicographically first triple, which is the one the sample reports, so
# unlike 2SUM the answers here can be compared literally.
fn three_sum(xs) {
    let answer = "-1"
    let p = 0
    while p < len(xs) and answer == "-1" {
        let q = p + 1
        while q < len(xs) and answer == "-1" {
            let r = q + 1
            while r < len(xs) and answer == "-1" {
                if xs[p] + xs[q] + xs[r] == 0 {
                    answer = str(p + 1) + " " + str(q + 1) + " " + str(r + 1)
                }
                r = r + 1
            }
            q = q + 1
        }
        p = p + 1
    }
    answer
}

let answers = arrays |> map(|xs| three_sum(xs))
let result = answers |> join(" / ")

println("Result:   " + result)
println("Expected: -1 / 1 2 4 / 1 2 3 / -1")

fn test_3sum_finds_zero_triples() {
    assert result == "-1 / 1 2 4 / 1 2 3 / -1", "3SUM: got " + result
}