Centroid of a set of N-dimensional points

From Rosetta Code
Revision as of 08:19, 19 July 2023 by PureFox (talk | contribs) (→‎{{header|Wren}}: Added alternative version.)
Centroid of a set of N-dimensional points is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

In analytic geometry, the centroid of a set of points is a point in the same domain as the set. The centroid point is chosen to show a property which can be calculated for that set.

Consider the centroid defined as the arithmetic mean of a set of points of arbitrary dimension.

Task

Create a function in your chosen programming language to calculate such a centroid using an arbitrary number of points of arbitrary dimension.

Test your function with the following groups of points
one-dimensional: (1), (2), (3)
two-dimensional: (8, 2), (0, 0)
three-dimensional: the set (5, 5, 0), (10, 10, 0) and the set (1, 3.1, 6.5), (-2, -5, 3.4), (-7, -4, 9), (2, 0, 3)
five-dimensional: (0, 0, 0, 0, 1), (0, 0, 0, 1, 0), (0, 0, 1, 0, 0), (0, 1, 0, 0, 0)


Stretch task
   Show a 3D plot image of the second 3-dimensional set and its centroid.
See Also
[Wikipedia page]
[Wolfram Mathworld on Centroid]


C

The image will, of course, be the same as Wren and Go when the relevant points are fed into Gnuplot.

#include <stdio.h>

void centroid(int n, int d, double pts[n][d]) {
    int i, j;
    double ctr[d];
    for (j = 0; j < d; ++j) {
        ctr[j] = 0.0;
        for (i = 0; i < n; ++i) {
            ctr[j] += pts[i][j];
        }
        ctr[j] /= n;
    }
    printf("{");
    for (i = 0; i < n; ++i) {
        printf("{");
        for (j = 0; j < d; ++j) {
            printf("%g", pts[i][j]);
            if (j < d -1) printf(", ");
        }
        printf("}");
        if (i < n - 1) printf(", ");
    }
    printf("} => Centroid: {");
    for (j = 0; j < d; ++j) {
        printf("%g", ctr[j]);
        if (j < d-1) printf(", ");
    }
    printf("}\n");
}

int main() {
    double pts1[3][1] = { {1}, {2}, {3} };
    double pts2[2][2] = { {8, 2}, {0, 0} };
    double pts3[2][3] = { {5, 5, 0}, {10, 10, 0} };
    double pts4[4][3] = { {1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3} };
    double pts5[4][5] = { {0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0} };

    centroid(3, 1, pts1);
    centroid(2, 2, pts2);
    centroid(2, 3, pts3);
    centroid(4, 3, pts4);
    centroid(4, 5, pts5);
    return 0;
}
Output:
{{1}, {2}, {3}} => Centroid: {2}
{{8, 2}, {0, 0}} => Centroid: {4, 1}
{{5, 5, 0}, {10, 10, 0}} => Centroid: {7.5, 7.5, 0}
{{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}} => Centroid: {-1.5, -1.475, 5.475}
{{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}} => Centroid: {0, 0.25, 0.25, 0.25, 0.25}

Go

Translation of: Wren

The image will, of course, be the same as Wren when the relevant points are fed into Gnuplot.

package main

import (
    "fmt"
    "log"
)

func centroid(pts [][]float64) []float64 {
    n := len(pts)
    if n == 0 {
        log.Fatal("Slice must contain at least one point.")
    }
    d := len(pts[0])
    for i := 1; i < n; i++ {
        if len(pts[i]) != d {
            log.Fatal("Points must all have the same dimension.")
        }
    }
    res := make([]float64, d)
    for j := 0; j < d; j++ {
        for i := 0; i < n; i++ {
            res[j] += pts[i][j]
        }
        res[j] /= float64(n)
    }
    return res
}

func main() {
    points := [][][]float64{
        {{1}, {2}, {3}},
        {{8, 2}, {0, 0}},
        {{5, 5, 0}, {10, 10, 0}},
        {{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}},
        {{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}},
    }
    for _, pts := range points {
        fmt.Println(pts, "=> Centroid:", centroid(pts))
    }
}
Output:
[[1] [2] [3]] => Centroid: [2]
[[8 2] [0 0]] => Centroid: [4 1]
[[5 5 0] [10 10 0]] => Centroid: [7.5 7.5 0]
[[1 3.1 6.5] [-2 -5 3.4] [-7 -4 9] [2 0 3]] => Centroid: [-1.5 -1.475 5.475]
[[0 0 0 0 1] [0 0 0 1 0] [0 0 1 0 0] [0 1 0 0 0]] => Centroid: [0 0.25 0.25 0.25 0.25]

Julia

using Plots

struct Point{T, N}
    v::Vector{T}
end

function centroid(points::Vector{Point{T, N}}) where N where T
    arr = zeros(T, N)
    for p in points, (i, x) in enumerate(p.v)
        arr[i] += x
    end
    return Point{T, N}(arr / length(points))
end

function centroid(arr)
    isempty(arr) && return Point{Float64, 0}(arr)
    n = length(arr[begin])
    t = typeof(arr[begin][begin])
    return centroid([Point{t, n}(v) for v in arr])
end

const testvecs = [[[1], [2], [3]],
                  [(8, 2), (0, 0)],
                  [[5, 5, 0], [10, 10, 0]],
                  [[1.0, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9.0], [2.0, 0.0, 3.0],],
                  [[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0],],
                 ]

function test_centroids(tests)
    for t in tests
        isempty(t) && error("The empty set of points $t has no centroid")
        vvec = [Point{Float64, length(t[begin])}(collect(v)) for v in t]
        println("$t => $(centroid(vvec))")
    end
    xyz = [p[1] for p in tests[4]], [p[2] for p in tests[4]], [p[3] for p in tests[4]]
    cpoint = centroid(tests[4]).v
    for i in eachindex(cpoint)
        push!(xyz[i], cpoint[i])
    end
    scatter(xyz..., color = [:navy, :navy, :navy, :navy, :red], legend = :none)
end

test_centroids(testvecs)
Output:
[[1], [2], [3]] => Point{Float64, 1}([2.0])
[(8, 2), (0, 0)] => Point{Float64, 2}([4.0, 1.0])
[[5, 5, 0], [10, 10, 0]] => Point{Float64, 3}([7.5, 7.5, 0.0])
[[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, 3.0]] => Point{Float64, 3}([-1.5, -1.475, 5.475])
[[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]] => Point{Float64, 5}([0.0, 0.25, 0.25, 0.25, 0.25])

Phix

with javascript_semantics
function centroid(sequence pts)
    return apply(columnize(pts),average)
end function

constant points = {{{1}, {2}, {3}},
                   {{8, 2}, {0, 0}},
                   {{5, 5, 0}, {10, 10, 0}},
                   {{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}},
                   {{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}}}
for p in points do
    printf(1,"%v ==> Centroid: %v\n",{p,centroid(p)})
end for
Output:
{{1},{2},{3}} ==> Centroid: {2}
{{8,2},{0,0}} ==> Centroid: {4,1}
{{5,5,0},{10,10,0}} ==> Centroid: {7.5,7.5,0}
{{1,3.1,6.5},{-2,-5,3.4},{-7,-4,9},{2,0,3}} ==> Centroid: {-1.5,-1.475,5.475}
{{0,0,0,0,1},{0,0,0,1,0},{0,0,1,0,0},{0,1,0,0,0}} ==> Centroid: {0,0.25,0.25,0.25,0.25}

Raku

sub centroid { ( [»+«] @^LoL ) »/» +@^LoL }

say .&centroid for
    ( (1,), (2,), (3,) ),
    ( (8, 2), (0, 0) ),
    ( (5, 5, 0), (10, 10, 0) ),
    ( (1, 3.1, 6.5), (-2, -5, 3.4), (-7, -4, 9), (2, 0, 3) ),
    ( (0, 0, 0, 0, 1), (0, 0, 0, 1, 0), (0, 0, 1, 0, 0), (0, 1, 0, 0, 0) ),
;
Output:
(2)
(4 1)
(7.5 7.5 0)
(-1.5 -1.475 5.475)
(0 0.25 0.25 0.25 0.25)

Wren

var centroid = Fn.new { |pts|
    var n = pts.count
    if (n == 0) Fiber.abort("List must contain at least one point.")
    var d = pts[0].count
    if (pts.skip(1).any { |p| p.count != d }) {
        Fiber.abort("Points must all have the same dimension.")
    }
    var res = List.filled(d, 0)
    for (j in 0...d) {
        for (i in 0...n) {
            res[j] = res[j] + pts[i][j]
        }
        res[j] = res[j] / n
    }
    return res
}

var points = [
    [ [1], [2], [3] ],
    [ [8, 2], [0, 0] ],
    [ [5, 5, 0], [10, 10, 0] ],
    [ [1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3] ],
    [ [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0] ]
]

for (pts in points) {
    System.print("%(pts) => Centroid: %(centroid.call(pts))")
}
Output:
[[1], [2], [3]] => Centroid: [2]
[[8, 2], [0, 0]] => Centroid: [4, 1]
[[5, 5, 0], [10, 10, 0]] => Centroid: [7.5, 7.5, 0]
[[1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3]] => Centroid: [-1.5, -1.475, 5.475]
[[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]] => Centroid: [0, 0.25, 0.25, 0.25, 0.25]
Library: Wren-seq
Library: Wren-math

Or, more concise using library methods - output identical.

import "./seq" for Lst
import "./math" for Nums

var centroid = Fn.new { |pts|
    return Lst.columns(pts).map { |c| Nums.mean(c) }.toList
}

var points = [
    [ [1], [2], [3] ],
    [ [8, 2], [0, 0] ],
    [ [5, 5, 0], [10, 10, 0] ],
    [ [1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3] ],
    [ [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0] ]
]

for (pts in points) {
    System.print("%(pts) => Centroid: %(centroid.call(pts))")
}