Centroid of a set of N-dimensional points
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
ALGOL 68
BEGIN # find the centroid of some N-dimensional points #
# returns the centroid of points #
OP CENTROID = ( [,]REAL points )[]REAL:
BEGIN
INT number of points = ( 1 UPB points - 1 LWB points ) + 1;
[ 2 LWB points : 2 UPB points ]REAL result;
FOR j FROM 2 LWB points TO 2 UPB points DO
REAL sum := 0;
FOR i FROM 1 LWB points TO 1 UPB points DO sum +:= points[ i, j ] OD;
result[ j ] := sum / number of points
OD;
result
END # CENTROID # ;
OP A = ( INT v )[]REAL: v; # coerces v to []REAL #
OP FMT = ( REAL v )STRING: # formsts v with up to 2 decimals #
BEGIN
STRING result := fixed( v, -0, 2 );
IF result[ LWB result ] = "." THEN "0" +=: result FI;
WHILE result[ UPB result ] = "0" DO result := result[ : UPB result - 1 ] OD;
IF result[ UPB result ] = "." THEN result := result[ : UPB result - 1 ] FI;
" " + result
END # FMT # ;
OP SHOW = ( []REAL v )VOID: # show a 1D array (row) of reals #
BEGIN
print( ( "[" ) );
FOR i FROM LWB v TO UPB v DO print( ( FMT v[ i ] ) ) OD;
print( ( " ]" ) )
END # SHOW # ;
OP SHOW = ( [,]REAL v )VOID: # show a 2D array of reals #
BEGIN
print( ( "[" ) );
FOR i FROM 1 LWB v TO 1 UPB v DO SHOW v[ i, : ] OD;
print( ( "]" ) )
END # SHOW # ;
# task test cases #
PROC test = ( [,]REAL points )VOID: # test the CENTROID operator #
BEGIN SHOW points; print( ( " -> " ) );
SHOW CENTROID points; print( ( newline ) )
END # test # ;
test( ( A(1), A(2), A(3) ) );
test( ( ( 8, 2 ), ( 0, 0 ) ) );
test( ( ( 5, 5, 0 ), ( 10, 10, 0 ) ) );
test( ( ( 1, 3.1, 6.5 ), ( -2, -5, 3.4 )
, ( -7, -4, 9 ), ( 2, 0, 3 )
)
);
test( ( ( 0, 0, 0, 0, 1 ), ( 0, 0, 0, 1, 0 )
, ( 0, 0, 1, 0, 0 ), ( 0, 1, 0, 0, 0 )
)
)
END
- Output:
[[ 1 ][ 2 ][ 3 ]] -> [ 2 ] [[ 8 2 ][ 0 0 ]] -> [ 4 1 ] [[ 5 5 0 ][ 10 10 0 ]] -> [ 7.5 7.5 0 ] [[ 1 3.1 6.5 ][ -2 -5 3.4 ][ -7 -4 9 ][ 2 0 3 ]] -> [ -1.5 -1.47 5.47 ] [[ 0 0 0 0 1 ][ 0 0 0 1 0 ][ 0 0 1 0 0 ][ 0 1 0 0 0 ]] -> [ 0 0.25 0.25 0.25 0.25 ]
ALGOL W
begin % find the centroid of some N dimensional points %
% sets cPoints to the centroid of points %
procedure centroid( real array points ( *, * )
; integer value numberOfPoints, dimension
; real array cPoint ( * )
) ;
for j := 1 until dimension do begin
real sum;
sum := 0;
for i := 1 until numberOfpoints do sum := sum + points( i, j );
cPoint( j ) := sum / numberOfPoints
end centroid ;
begin % task test cases %
% show a real number with two decimal places if if is not integral %
procedure show ( real value rValue ) ;
begin
integer iValue;
iValue := truncate( rValue );
if iValue = rValue then writeon( s_w := 0, i_w := 1, " ", iValue )
else writeon( s_w := 0
, r_format := "A", r_w := 4, r_d := 2
, " ", rValue
)
end show ;
procedure testCentroid( real array points ( *, * )
; integer value numberOfPoints, dimension
) ;
begin
real array cPoint( 1 :: dimension );
centroid( points, numberOfPoints, dimension, cPoint );
write( "[" );
for i := 1 until numberOfPoints do begin
writeon( "[" );
for j := 1 until dimension do show( points( i, j ) );
writeon( " ]" );
end for_i ;
writeon( "] -> [" );
for j := 1 until dimension do show( cPoint( j ) );
writeon( " ]" )
end testCentroid ;
real array p1 ( 1 :: 3, 1 :: 1 ); real array p2 ( 1 :: 2, 1 :: 2 );
real array p3 ( 1 :: 2, 1 :: 3 ); real array p4 ( 1 :: 4, 1 :: 3 );
real array p5 ( 1 :: 4, 1 :: 5 );
p1( 1, 1 ) := 1; p1( 2, 1 ) := 2; p1( 3, 1 ) := 3;
p2( 1, 1 ) := 8; p2( 1, 2 ) := 2;
p2( 2, 1 ) := 0; p2( 2, 2 ) := 0;
p3( 1, 1 ) := 5; p3( 1, 2 ) := 5; p3( 1, 3 ) := 0;
p3( 2, 1 ) := 10; p3( 2, 2 ) := 10; p3( 2, 3 ) := 0;
p4( 1, 1 ) := 1; p4( 1, 2 ) := 3.1; p4( 1, 3 ) := 6.5;
p4( 2, 1 ) := -2; p4( 2, 2 ) := -5; p4( 2, 3 ) := 3.4;
p4( 3, 1 ) := -7; p4( 3, 2 ) := -4; p4( 3, 3 ) := 9;
p4( 4, 1 ) := 2; p4( 4, 2 ) := 0; p4( 4, 3 ) := 3;
p5( 1, 1 ) := 0; p5( 1, 2 ) := 0; p5( 1, 3 ) := 0; p5( 1, 4 ) := 0; p5( 1, 5 ) := 1;
p5( 2, 1 ) := 0; p5( 2, 2 ) := 0; p5( 2, 3 ) := 0; p5( 2, 4 ) := 1; p5( 2, 5 ) := 0;
p5( 3, 1 ) := 0; p5( 3, 2 ) := 0; p5( 3, 3 ) := 1; p5( 3, 4 ) := 0; p5( 3, 5 ) := 0;
p5( 4, 1 ) := 0; p5( 4, 2 ) := 1; p5( 4, 3 ) := 0; p5( 4, 4 ) := 0; p5( 4, 5 ) := 0;
testCentroid( p1, 3, 1 );
testCentroid( p2, 2, 2 );
testCentroid( p3, 2, 3 );
testCentroid( p4, 4, 3 );
testCentroid( p5, 4, 5 )
end
end.
- Output:
[[ 1 ][ 2 ][ 3 ]] -> [ 2 ] [[ 8 2 ][ 0 0 ]] -> [ 4 1 ] [[ 5 5 0 ][ 10 10 0 ]] -> [ 7.50 7.50 0 ] [[ 1 3.10 6.50 ][ -2 -5 3.40 ][ -7 -4 9 ][ 2 0 3 ]] -> [ -1.50 -1.47 5.47 ] [[ 0 0 0 0 1 ][ 0 0 0 1 0 ][ 0 0 1 0 0 ][ 0 1 0 0 0 ]] -> [ 0 0.25 0.25 0.25 0.25 ]
BASIC256
subroutine Centroid(n, d, pts)
dim ctr(d)
for j = 0 to d-1
ctr[j] = 0
for i = 0 to n-1
ctr[j] += pts[i,j]
next
ctr[j] /= n
next
print "{";
for i = 0 to n-1
print "{";
for j = 0 to d-1
print pts[i,j];
if j < d-1 then print ", ";
next
print "}";
if i < n-1 then print ", ";
next
print "} => Centroid: {";
for j = 0 to d-1
print ctr[j];
if j < d-1 then print ", ";
next
print "}"
end subroutine
pts1 = {{1}, {2}, {3}}
pts2 = {{8, 2}, {0, 0}}
pts3 = {{5, 5, 0}, {10, 10, 0}}
pts4 = {{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}}
pts5 = {{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}}
call Centroid(3, 1, pts1)
call Centroid(2, 2, pts2)
call Centroid(2, 3, pts3)
call Centroid(4, 3, pts4)
call Centroid(4, 5, pts5)
end
- Output:
Similar to FreeBASIC entry.
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}
C++
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <vector>
template <typename T>
void print_vector(const std::vector<T>& vec) {
std::cout << "[";
for ( uint32_t i = 0; i < vec.size() - 1; ++i ) {
std::cout << vec[i] << ", ";
}
std::cout << vec.back() << "]";
}
template <typename T>
void print_2D_vector(const std::vector<std::vector<T>>& vecs) {
std::cout << "[";
for ( uint32_t i = 0; i < vecs.size() - 1; ++i ) {
print_vector(vecs[i]); std::cout << ", ";
}
print_vector(vecs.back()); std::cout << "]";
}
bool all_same_size(const std::vector<std::vector<double>>& points, const uint32_t& dimension) {
for ( uint32_t i = 1; i < points.size(); ++i ) {
if ( points[i].size() != dimension ) {
return false;
}
}
return true;
}
std::vector<double> centroid(const std::vector<std::vector<double>>& points) {
if ( points.empty() ) {
throw std::invalid_argument("Vector must contain at least one point.");
}
const uint32_t dimension = points[0].size();
if ( ! all_same_size(points, dimension) ) {
throw std::invalid_argument("Points must all have the same dimension.");
}
std::vector<double> result(dimension, 0.0);
for ( uint32_t j = 0; j < dimension; ++j ) {
for ( uint32_t i = 0; i < points.size(); ++i ) {
result[j] += points[i][j];
}
result[j] /= points.size();
}
return result;
}
int main() {
std::vector<std::vector<std::vector<double>>> vector_points = {
std::vector{ std::vector{ 1.0 }, std::vector{ 2.0 }, std::vector{ 3.0 } },
std::vector{ std::vector{ 8.0, 2.0 }, std::vector{ 0.0, 0.0 } },
std::vector{ std::vector{ 5.0, 5.0, 0.0 }, std::vector{ 10.0, 10.0, 0.0 } },
std::vector{ std::vector{ 1.0, 3.1, 6.5 }, std::vector{ -2.0, -5.0, 3.4 },
std::vector{ -7.0, -4.0, 9.0 }, std::vector{ 2.0, 0.0, 3.0 } },
std::vector{ std::vector{ 0.0, 0.0, 0.0, 0.0, 1.0 }, std::vector{ 0.0, 0.0, 0.0, 1.0, 0.0 },
std::vector{ 0.0, 0.0, 1.0, 0.0, 0.0 }, std::vector{ 0.0, 1.0, 0.0, 0.0, 0.0 } }
};
for ( const std::vector<std::vector<double>>& points : vector_points ) {
print_2D_vector(points); std::cout << " => Centroid: "; print_vector(centroid(points)); std::cout << std::endl;
}
}
- 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]
DuckDB
# Note: this implementation of transpose() is potentially lossy for jagged inputs
create or replace function transpose(lst) as
select list_transform( range(1, 1+length(lst[1])),
j -> list_transform(range(1, length(lst)+1),
i -> lst[i][j]) );
create or replace function centroid(points) as (
if ( length(points) = 0,
error('centroid: list must contain at least one point.'),
if (length(list_filter(points, x -> length(x) != length(points[1]))) > 0,
error('centroid: points must all have the same dimension.'),
transpose(points)
.list_transform(x -> list_sum(x) / length(points))))
);
## Examples
select points, centroid(points)
from unnest([
[[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]]
]) _(points);
- Output:
(elided)
┌───────────────────────────────────────────────────────────────────────┬───────────────────────────────┐ │ points │ centroid(points) │ │ decimal(11,1)[][] │ double[] │ ├───────────────────────────────────────────────────────────────────────┼───────────────────────────────┤ │ [[1.0], [2.0], [3.0]] │ [2.0] │ │ [[8.0, 2.0], [0.0, 0.0]] │ [4.0, 1.0] │ │ [[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]] │ [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, … │ [-1.5, -1.475, 5.475] │ │ [[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1… │ [0.0, 0.25, 0.25, 0.25, 0.25] │ └───────────────────────────────────────────────────────────────────────┴───────────────────────────────┘
FreeBASIC
Sub Centroid(n As Ubyte, d As Ubyte, pts() As Single)
Dim As Ubyte i, j
Dim As Single ctr(d)
For j = 0 To d-1
ctr(j) = 0
For i = 0 To n-1
ctr(j)+ = pts(i,j)
Next
ctr(j) /= n
Next
Print "{";
For i = 0 To n-1
Print "{";
For j = 0 To d-1
Print Using "&"; pts(i,j);
If j < d-1 Then Print ", ";
Next
Print "}";
If i < n-1 Then Print ", ";
Next
Print "} => Centroid: {";
For j = 0 To d-1
Print Using "&"; ctr(j);
If j < d-1 Then Print ", ";
Next
Print "}"
End Sub
Dim pts1(2, 1) As Single = {{1}, {2}, {3}}
Dim pts2(1, 1) As Single = {{8, 2}, {0, 0}}
Dim pts3(1, 2) As Single = {{5, 5, 0}, {10, 10, 0}}
Dim pts4(3, 2) As Single = {{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}}
Dim pts5(3, 4) As Single = {{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())
Sleep
- 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}
Fōrmulæ
Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for storage and transfer purposes more than visualization and edition.
Programs in Fōrmulæ are created/edited online in its website.
In this page you can see and run the program(s) related to this task and their results. You can also change either the programs or the parameters they are called with, for experimentation, but remember that these programs were created with the main purpose of showing a clear solution of the task, and they generally lack any kind of validation.
Solution
Definition of function
Test cases:
Go
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]
Java
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class CentroidOfASetOfNDimensionalPoints {
public static void main(String[] args) {
List<List<List<Double>>> listPoints = List.of(
List.of( List.of( 1.0 ), List.of( 2.0 ), List.of( 3.0 ) ),
List.of( List.of( 8.0, 2.0 ), List.of( 0.0, 0.0 ) ),
List.of( List.of( 5.0, 5.0, 0.0 ), List.of( 10.0, 10.0, 0.0 ) ),
List.of( List.of( 1.0, 3.1, 6.5 ), List.of( -2.0, -5.0, 3.4 ),
List.of( -7.0, -4.0, 9.0 ), List.of( 2.0, 0.0, 3.0 ) ),
List.of( List.of( 0.0, 0.0, 0.0, 0.0, 1.0 ), List.of( 0.0, 0.0, 0.0, 1.0, 0.0 ),
List.of( 0.0, 0.0, 1.0, 0.0, 0.0 ), List.of( 0.0, 1.0, 0.0, 0.0, 0.0 ) )
);
listPoints.forEach( points -> {
System.out.println(points + " => Centroid: " + centroid(points));
} );
}
private static List<Double> centroid(List<List<Double>> points) {
if ( points.isEmpty() ) {
throw new AssertionError("List must contain at least one point.");
}
final int dimension = points.getFirst().size();
if ( ! points.stream().skip(1).allMatch( list -> list.size() == dimension ) ) {
throw new AssertionError("Points must all have the same dimension.");
}
List<Double> result = Stream.generate( () -> 0.0 ).limit(dimension).collect(Collectors.toList());
for ( int j = 0; j < dimension; j++ ) {
for ( int i = 0; i < points.size(); i++ ) {
result.set(j, result.get(j) + points.get(i).get(j));
}
result.set(j, result.get(j) / points.size());
}
return result;
}
}
- Output:
[[1.0], [2.0], [3.0]] => Centroid: [2.0] [[8.0, 2.0], [0.0, 0.0]] => Centroid: [4.0, 1.0] [[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]] => Centroid: [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]] => Centroid: [-1.5, -1.475, 5.475] [[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0]] => Centroid: [0.0, 0.25, 0.25, 0.25, 0.25]
jq
Also works with gojq, the Go implementation of jq.
With a trivial change to the last line (the one using string interpolation), the following also works with jaq, the Rust implementation of jq.
# Input: an array of points of the same dimension (i.e. numeric arrays of the same length)
def centroid:
length as $n
| if ($n == 0) then "centroid: list must contain at least one point." | error else . end
| (.[0]|length) as $d
| if any( .[]; length != $d )
then "centroid: points must all have the same dimension." | error
else .
end
| transpose
| map( add / $n ) ;
def 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] ]
];
points[]
| "\(.) => Centroid: \(centroid)"
- Output:
Essentially as for Wren.
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])
Lua
Based on the Algol 68 sample.
do -- find the centroid of some N-dimensional points
function centroid( points ) -- returns the centroid of points
local result = {}
if #points > 0 then
for j = 1, #points[ 1 ] do
local sum = 0
for i = 1, #points do sum = sum + points[ i ][ j ] end
result[ j ] = sum / #points
end
end
return result
end
function show1d( v ) -- show a 1D array of floats
io.write( "[" )
for i = 1, #v do io.write( " ", v[ i ] ) end
io.write( " ]" )
end
function show2d( v ) -- show a 2D array of floats
io.write( "[" )
for i = 1 , #v do show1d( v[ i ] ) end
io.write( "]" )
end
-- task test cases
function testCentroid( points )
show2d( points )
io.write( " -> " )
show1d( centroid( points ) )
io.write( "\n" )
end
testCentroid{ { 1 }, { 2 }, { 3 } }
testCentroid{ { 8, 2 }, { 0, 0 } }
testCentroid{ { 5, 5, 0 }, { 10, 10, 0 } }
testCentroid{ { 1, 3.1, 6.5 }, { -2, -5, 3.4 }
, { -7, -4, 9 }, { 2, 0, 3 }
}
testCentroid{ { 0, 0, 0, 0, 1 }, { 0, 0, 0, 1, 0 }
, { 0, 0, 1, 0, 0 }, { 0, 1, 0, 0, 0 }
}
end
- Output:
[[ 1 ][ 2 ][ 3 ]] -> [ 2 ] [[ 8 2 ][ 0 0 ]] -> [ 4 1 ] [[ 5 5 0 ][ 10 10 0 ]] -> [ 7.5 7.5 0 ] [[ 1 3.1 6.5 ][ -2 -5 3.4 ][ -7 -4 9 ][ 2 0 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 ]] -> [ 0 0.25 0.25 0.25 0.25 ]
Nim
type Coords[N: static Positive] = array[N, float]
proc centroid(points: openArray[Coords]): Coords =
## Return the coordinates of the centroid of the given points.
for point in points:
for i, coord in point:
result[i] += coord
for coord in result.mitems:
coord /= points.len.toFloat
proc displayCentroid(points: openArray[Coords]) =
echo "Set: ", points
echo "Centroid: ", points.centroid
echo()
const
Points1: seq[Coords[1]] = @[[1], [2], [3]]
Points2: seq[Coords[2]] = @[[8, 2], [0, 0]]
Points3: seq[Coords[3]] = @[[5, 5, 0], [10, 10, 0]]
Points4: seq[Coords[3]] = @[[1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3]]
Points5: seq[Coords[5]] = @[[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]]
Points1.displayCentroid()
Points2.displayCentroid()
Points3.displayCentroid()
Points4.displayCentroid()
Points5.displayCentroid()
- Output:
Set: [[1.0], [2.0], [3.0]] Centroid: [2.0] Set: [[8.0, 2.0], [0.0, 0.0]] Centroid: [4.0, 1.0] Set: [[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]] Centroid: [7.5, 7.5, 0.0] Set: [[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, 3.0]] Centroid: [-1.5, -1.475, 5.475] Set: [[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0]] Centroid: [0.0, 0.25, 0.25, 0.25, 0.25]
Perl
PDL library, with plot
use v5.36;
use PDL;
sub centroid ($LoL) {
return pdl($LoL)->transpose->average;
}
sub plot_with_centroid ($LoL) {
require PDL::Graphics::Gnuplot;
my $p = pdl($LoL);
my $pc = $p->glue(1, centroid($p));
my @xyz = map { $pc->slice("($_)") } 0..2;
my $colors = [8,8,8,8,7];
PDL::Graphics::Gnuplot->new('png')->plot3d(
square => 1,
grid => [qw<xtics ytics ztics>],
{ with => 'points', pt => 7, ps => 2, linecolor => 'variable', },
@xyz, $colors,
);
}
my @tests = (
[ [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] ],
);
say centroid($_) for @tests;
plot_with_centroid($tests[3]);
- Output:
[2] [4 1] [7.5 7.5 0] [-1.5 -1.475 5.475] [0 0.25 0.25 0.25 0.25]
Direct calculation
use v5.36;
sub centroid ($LoL) {
my $n = $#{ $LoL };
my $d = $#{ $LoL->@[0] };
my @C = 0 x ($d+1);
for my $i (0..$d) {
$C[$i] += $LoL->@[$_]->@[$i] for 0..$n;
$C[$i] /= $n+1
}
@C
}
say join ' ', 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
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}
Python
from typing import List
def centroid(points: List[List[float]]) -> List[float]:
"""
Calculate the centroid (geometric center) of a set of points in n-dimensional space.
Args:
points: A list of points, where each point is a list of float coordinates.
All points must have the same dimensionality.
Returns:
A list representing the centroid point coordinates.
Raises:
ValueError: If the input list is empty or if points have inconsistent dimensions.
"""
# Check if the input list is empty
if not points:
raise ValueError("List must contain at least one point")
# Determine the number of dimensions
dimension = len(points[0])
if not all(len(l) == dimension for l in points):
raise ValueError("Points must all have the same dimension")
result = [0.0] * dimension # Initialise centroid coordinates with zeros
for i in range(dimension):
for j in range(len(points)):
result[i] += points[j][i] # Sum up corresponding coordinates
result[i] /= len(points) # Compute the average for each coordinate
return result
points: List[List[List[float]]] = [
[[1.0], [2.0], [3.0]],
[[8.0, 2.0], [0.0, 0.0]],
[[5.0, 5.0, 0.0], [10.0, 10.0, 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]],
[[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0]],
]
for point in points:
print(f"\nSet: {point}\nCentroid: {centroid(point)}")
Set: [[1.0], [2.0], [3.0]] Centroid: [2.0] Set: [[8.0, 2.0], [0.0, 0.0]] Centroid: [4.0, 1.0] Set: [[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]] Centroid: [7.5, 7.5, 0.0] Set: [[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, 3.0]] Centroid: [-1.5, -1.475, 5.475] Set: [[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0]] Centroid: [0.0, 0.25, 0.25, 0.25, 0.25]
Raku
sub centroid { ( [»+«] @^LoL ) »/» +@^LoL }
say .¢roid 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)
Sidef
func centroid(l) {
l.combine {|*a| a.sum } »/» l.len
}
[
[ [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] ],
].each {
say centroid(_)
}
- Output:
[2] [4, 1] [15/2, 15/2, 0] [-3/2, -59/40, 219/40] [0, 1/4, 1/4, 1/4, 1/4]
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]
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))")
}
XPL0
include xpllib; \for Print
proc Centroid(N, D, Pts);
int N, D; real Pts;
int I, J;
real Ctr;
[Ctr:= RlRes(D);
for J:= 0 to D-1 do
[Ctr(J):= 0.0;
for I:= 0 to N-1 do
Ctr(J):= Ctr(J) + Pts(I,J);
Ctr(J):= Ctr(J) / float(N);
];
Print("[");
for I:= 0 to N-1 do
[Print("[");
for J:= 0 to D-1 do
[Print("%g", Pts(I,J));
if J < D-1 then Print(", ");
];
Print("]");
if I < N-1 then Print(", ");
];
Print("] => Centroid: [");
for J:= 0 to D-1 do
[Print("%g", Ctr(J));
if J < D-1 then Print(", ");
];
Print("]\n");
];
real Pts1, Pts2, Pts3, Pts4, Pts5;
[Pts1:= [ [1.], [2.], [3.] ];
Pts2:= [ [8., 2.], [0., 0.] ];
Pts3:= [ [5., 5., 0.], [10., 10., 0.] ];
Pts4:= [ [1., 3.1, 6.5], [-2., -5., 3.4], [-7., -4., 9.], [2., 0., 3.] ];
Pts5:= [ [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);
]
- 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]