Rearrangements

2 problems from Rosalind — Bioinformatics Stronghold. Press Run on any block to execute it in your browser.

REAR — Reversal Distance

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

Where BA6C's 2-break distance had a closed form, this has none at size 10 and must be searched: 45 reversals per step, 3.6 million reachable orders, distance reaching 9. The builtin searches from both ends and meets in the middle, so each side only reaches depth 4 or 5.

# Rosalind: REAR — Reversal Distance
# https://rosalind.info/problems/rear/
#
# Given: Up to five pairs of permutations of length 10.
# Return: The reversal distance for each pair.

let pairs = [
    { from_order: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],  to_order: [3, 1, 5, 2, 7, 4, 9, 6, 10, 8] },
    { from_order: [3, 10, 8, 2, 5, 4, 7, 1, 6, 9],  to_order: [5, 2, 3, 1, 7, 4, 10, 8, 6, 9] },
    { from_order: [8, 6, 7, 9, 4, 1, 3, 10, 2, 5],  to_order: [8, 2, 7, 6, 9, 1, 5, 3, 10, 4] },
    { from_order: [3, 9, 10, 4, 1, 8, 6, 7, 5, 2],  to_order: [2, 9, 8, 5, 1, 7, 3, 4, 6, 10] },
    { from_order: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],  to_order: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] },
]

# The 2-break distance in BA6C had a closed form — blocks minus cycles. Reversal
# distance has no such formula at this size, so it has to be searched for, and
# the search is why `reversal_distance` is a builtin rather than written here:
# ten elements admit 45 reversals and 3.6 million reachable orders, and the
# distance reaches 9. A one-sided search to that depth is hopeless; the builtin
# searches from both ends and meets in the middle, so each side only reaches
# depth 4 or 5.
let distances = pairs |> map(|pair| reversal_distance(pair.from_order, pair.to_order))

println("Result:   " + (distances |> map(|d| str(d)) |> join(" ")))
println("Expected: 9 4 5 7 0")

fn test_rear_reversal_distance() {
    assert (distances |> map(|d| str(d)) |> join(" ")) == "9 4 5 7 0",
        "REAR: got " + str(distances)
    # Identical permutations are no distance apart, and the measure is symmetric
    # because a reversal undoes itself.
    assert distances[4] == 0, "REAR: the last pair is already sorted"
    for pair in pairs {
        assert reversal_distance(pair.to_order, pair.from_order)
            == reversal_distance(pair.from_order, pair.to_order),
            "REAR: the distance must be symmetric"
    }
}

SORT — Sorting by Reversals

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

REAR asks how far apart two gene orders are; this asks for the route, which is what actually says how the rearrangement happened. The assertion applies the reversals and checks they produce the target — a plausible list that does not sort is the failure this invites.

# Rosalind: SORT — Sorting by Reversals
# https://rosalind.info/problems/sort/
#
# Given: Two permutations of length 10.
# Return: The reversal distance, and a shortest collection of reversals taking
# the first to the second.

let from_order = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let to_order = [1, 8, 9, 3, 2, 7, 6, 5, 4, 10]

# REAR asks how far apart two gene orders are; this asks for the route. The
# search is the same — the distance is just the length of what comes back — but
# the sequence is what actually says how the rearrangement happened, and a
# distance without one is unfalsifiable.
#
# Any shortest sequence is correct. Reversals commute in some orders and not
# others, so several routes of the same length usually exist.
let steps = sorting_reversals(from_order, to_order)

println("Result:   " + str(len(steps)))
for step in steps { println("          " + str(step[0]) + " " + str(step[1])) }
println("Expected: 2, then 4 9 and 2 5 — any shortest route is accepted")

fn test_sort_sorting_by_reversals() {
    assert len(steps) == 2, "SORT: expected 2 reversals, got " + str(len(steps))
    assert len(steps) == reversal_distance(from_order, to_order),
        "SORT: the route must be as long as the distance"

    # The route has to work. Applying the reversals in order must produce the
    # target — a plausible-looking list that does not sort is the failure this
    # problem invites.
    let current = from_order
    for step in steps {
        let from_index = step[0] - 1
        let to_index = step[1] - 1
        let head = range(0, from_index) |> map(|i| current[i])
        let middle = range(from_index, to_index + 1)
            |> map(|i| current[to_index - (i - from_index)])
        let tail = range(to_index + 1, len(current)) |> map(|i| current[i])
        current = head + middle + tail
    }
    assert current == to_order,
        "SORT: the reversals give " + str(current) + ", not " + str(to_order)

    # Every interval is a real one, and reverses more than a single element.
    for step in steps {
        assert step[0] >= 1 and step[1] <= len(from_order), "SORT: interval out of range"
        assert step[0] < step[1], "SORT: reversing one element does nothing"
    }
}