A mesh defining a surface has uniquely numbered vertices, and named, simple-polygonal faces described usually by an ordered list of edge numbers going around the face,

Faces from a mesh 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.


For example: External image of two faces
Rough textual version without edges:


      1         
                        17
 7   A
             B

       11                     
                  23

  • A is the triangle (1, 11, 7), or equally (7, 11, 1), going anti-clockwise, or

any of all the rotations of those ordered vertices.

      1         
                        
 7   A
            

       11
  • B is the four-sided face (1, 17, 23, 11), or equally (23, 17, 1, 11) or any

of their rotations.

1         
                  17

       B

 11                     
            23

Let's call the above the perimeter format as it traces around the perimeter.

A second format

A separate algorithm returns polygonal faces consisting of a face name and an unordered set of edge definitions for each face.

  • A single edge is described by the vertex numbers at its two ends, always in

ascending order.

  • All edges for the face are given, but in an undefined order.

For example face A could be described by the edges (1, 11), (7, 11), and (1, 7) (The order of each vertex number in an edge is ascending, but the order in which the edges are stated is arbitrary).

Similarly face B could be described by the edges (11, 23), (1, 17), (17, 23), and (1, 11) in arbitrary order of the edges.

Let's call this second format the edge format.


Task

1. Write a routine to check if two perimeter formatted faces have the same perimeter use it o check if the following pairs of perimeters are the same:

 Q: (8, 1, 3)
 R: (1, 3, 8)

 U: (18, 8, 14, 10, 12, 17, 19)
 V: (8, 14, 10, 12, 17, 19, 18)

2. Write a routine and use it to transform the following faces from edge to perimeter format.

 E: {(1, 11), (7, 11), (1, 7)}
 F: {(11, 23), (1, 17), (17, 23), (1, 11)}
 G: {(8, 14), (17, 19), (10, 12), (10, 14), (12, 17), (8, 18), (18, 19)}
 H: {(1, 3), (9, 11), (3, 11), (1, 11)}

Show your output here.


Julia

<lang julia>iseq(f, g) = any(n -> f == circshift(g, n), 1:length(g))

vec_and_set(edges) = (a = Int[]; for e in edges push!(a, e[1], e[2]) end; (a, Set(a)))

isvalid(edges) = ((a, s) = vec_and_set(edges); all(x -> sum(y -> y == x, a) == 2, s))

function toface(evec)

   if isempty(evec) || !isvalid(evec)
       throw("Invalid Edge vector for a Face.")
   end
   toarr(p) = [p[1], p[2]]
   ret, edges = toarr(evec[1]), copy(evec[2:end])
   while !isempty(edges)
      i = findfirst(x -> ret[end] == x[1] || ret[end] == x[2], edges)
      pts = toarr(edges[i])
      push!(ret, ret[end] == pts[1] ? pts[2] : pts[1])
      deleteat!(edges, i)
   end
   return ret[1:end-1]

end

const faces1 = [

   [[8, 1, 3], [1, 3, 8]],
   [[18, 8, 14, 10, 12, 17, 19], [8, 14, 10, 12, 17, 19, 18]]

]

const faces2 = [

   [(1, 11), (7, 11), (1, 7)], [(11, 23), (1, 17), (17, 23), (1, 11)],
   [(8, 14), (17, 19), (10, 12), (10, 14), (12, 17), (8, 18), (18, 19)],
   [(1, 3), (9, 11), (3, 11), (1, 11)]

]

for faces in faces1

   println("Faces are ", iseq(faces[1], faces[2]) ? "" : "not ", "equivalent.")

end

for face in faces2

   println(toface(face))

end

</lang>

Output:
Faces are equivalent.
Faces are equivalent.
[1, 11, 7]
[11, 23, 17, 1]
[8, 14, 10, 12, 17, 19, 18]
ERROR: LoadError: "Invalid Edge vector for a Face."

Perl 6

Works with: Rakudo version 2019.11

<lang perl6>sub check-equivalence ($a, $b) { so $a.Bag eqv $b.Bag }

sub edge-to-periphery (@a is copy) {

   return Nil unless @a.List.Bag.values.all == 2;
   my @b = @a.shift.flat;
   while @a > 1 {
       for @a.kv -> $k, $v {
           if $v[0] == @b.tail {
               @b.push: $v[1];
               @a.splice($k,1);
               last
           }
           elsif $v[1] == @b.tail {
               @b.push: $v[0];
               @a.splice($k,1);
               last
           }
       }
   }
   @b

}

say 'Perimeter format equality checks:';

for (8, 1, 3), (1, 3, 8),

   (18, 8, 14, 10, 12, 17, 19), (8, 14, 10, 12, 17, 19, 18)
 -> $a, $b {
    say "({$a.join: ', '})  equivalent to  ({$b.join: ', '})? ",
        check-equivalence($a, $b)

}

say "\nEdge to perimeter format translations:";

for ((1, 11), (7, 11), (1, 7)),

   ((11, 23), (1, 17), (17, 23), (1, 11)),
   ((8, 14), (17, 19), (10, 12), (10, 14), (12, 17), (8, 18), (18, 19)),
   ((1, 3), (9, 11), (3, 11), (1, 11))
 {
   .gist.print;
   say "  ==>  ({.&edge-to-periphery || 'Invalid edge format'})";

}</lang>

Output:
Perimeter format equality checks:
(8, 1, 3)  equivalent to  (1, 3, 8)? True
(18, 8, 14, 10, 12, 17, 19)  equivalent to  (8, 14, 10, 12, 17, 19, 18)? True

Edge to perimeter format translations:
((1 11) (7 11) (1 7))  ==>  (1 11 7)
((11 23) (1 17) (17 23) (1 11))  ==>  (11 23 17 1)
((8 14) (17 19) (10 12) (10 14) (12 17) (8 18) (18 19))  ==>  (8 14 10 12 17 19 18)
((1 3) (9 11) (3 11) (1 11))  ==>  (Invalid edge format)

Python

<lang python>def perim_equal(p1, p2):

   # Cheap tests first
   if len(p1) != len(p2) or set(p1) != set(p2):
       return False
   if any(p2 == (p1[n:] + p1[:n]) for n in range(len(p1))):
       return True
   p2 = p2[::-1] # not inplace
   return any(p2 == (p1[n:] + p1[:n]) for n in range(len(p1)))

def edge_to_periphery(e):

   edges = sorted(e)
   p = list(edges.pop(0)) if edges else []
   last = p[-1] if p else None
   while edges:
       for n, (i, j) in enumerate(edges):
           if i == last:
               p.append(j)
               last = j
               edges.pop(n)
               break
           elif j == last:
               p.append(i)
               last = i
               edges.pop(n)
               break
       else:
           #raise ValueError(f'Invalid edge format: {e}')
           return ">>>Error! Invalid edge format<<<"
   return p[:-1]

if __name__ == '__main__':

   print('Perimeter format equality checks:')
   for eq_check in [
           { 'Q': (8, 1, 3),
             'R': (1, 3, 8)},
           { 'U': (18, 8, 14, 10, 12, 17, 19),
             'V': (8, 14, 10, 12, 17, 19, 18)} ]:
       (n1, p1), (n2, p2) = eq_check.items()
       eq = '==' if perim_equal(p1, p2) else '!='
       print(' ', n1, eq, n2)
   print('\nEdge to perimeter format translations:')
   edge_d = {
    'E': {(1, 11), (7, 11), (1, 7)},
    'F': {(11, 23), (1, 17), (17, 23), (1, 11)},
    'G': {(8, 14), (17, 19), (10, 12), (10, 14), (12, 17), (8, 18), (18, 19)},
    'H': {(1, 3), (9, 11), (3, 11), (1, 11)}
           }
   for name, edges in edge_d.items():
       print(f"  {name}: {edges}\n     -> {edge_to_periphery(edges)}")</lang>
Output:
Perimeter format equality checks:
  Q == R
  U == V

Edge to perimeter format translations:
  E: {(7, 11), (1, 11), (1, 7)}
     -> [1, 7, 11]
  F: {(11, 23), (1, 11), (1, 17), (17, 23)}
     -> [1, 11, 23, 17]
  G: {(8, 14), (17, 19), (10, 12), (10, 14), (12, 17), (8, 18), (18, 19)}
     -> [8, 14, 10, 12, 17, 19, 18]
  H: {(1, 11), (9, 11), (1, 3), (3, 11)}
     -> >>>Error! Invalid edge format<<<

zkl

Translation of: Python

<lang zkl>fcn perimSame(p1, p2){

  if(p1.len() != p2.len()) return(False);
  foreach p in (p1){ if(not p2.holds(p)) return(False) }
  True

}

fcn edge_to_periphery(faces){

  edges:=faces.copy().sort(fcn(a,b){ if(a[0]!=b[0]) a[0]<b[0] else a[1]<b[1] });
  p,last := ( if(edges) edges.pop(0).copy() else T ), ( p and p[-1] or Void );
  while(edges){
     foreach n, ij in ([0..].zip(edges)){
        i,j := ij;
        if(i==last)     { p.append(j); last=j; edges.pop(n); break; }
        else if(j==last){ p.append(i); last=i; edges.pop(n); break; }
     }
     fallthrough{ return(">>>Error! Invalid edge format<<<") }
  }
  p[0,-1]

}</lang> <lang zkl>println("Perimeter format equality checks:"); ps:=T( T( T(8,1,3), T(1,3,8) ),

      T( T(18, 8, 14, 10, 12, 17, 19), T(8, 14, 10, 12, 17, 19, 18) ) );

foreach p1,p2 in (ps)

  { println(pp(p1), "  equivalent to  ", pp(p2), "? ", perimSame(p1,p2)) }

println("\nEdge to perimeter format translations:"); edge_d:=T(

       T(T( 1, 11), T( 7, 11), T( 1,  7) ),
       T(T(11, 23), T( 1, 17), T(17, 23), T( 1, 11) ),
       T(T( 8, 14), T(17, 19), T(10, 12), T(10, 14), T(12, 17), T(8, 18), T(18, 19) ),
       T(T( 1,  3), T( 9, 11), T( 3, 11), T( 1, 11) ),
       );

foreach edges in (edge_d)

  { println(ppp(edges), "  --> ", edge_to_periphery(edges)) }

fcn pp(a){ a.concat(", ","(",")") } fcn ppp(edges){ pp(edges.apply(pp)) }</lang>

Output:
Perimeter format equality checks:
(8, 1, 3)  equivalent to  (1, 3, 8)? True
(18, 8, 14, 10, 12, 17, 19)  equivalent to  (8, 14, 10, 12, 17, 19, 18)? True

Edge to perimeter format translations:
((1 11), (7 11), (1 7))  --> L(1,7,11)
((11 23), (1 17), (17 23), (1 11))  --> L(1,11,23,17)
((8 14), (17 19), (10 12), (10 14), (12 17), (8 18), (18 19))  --> L(8,14,10,12,17,19,18)
((1 3), (9 11), (3 11), (1 11))  --> >>>Error! Invalid edge format<<<