numo-narray 0.9.1.4 → 0.9.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -4
  3. data/ext/numo/narray/array.c +17 -7
  4. data/ext/numo/narray/data.c +39 -36
  5. data/ext/numo/narray/extconf.rb +1 -0
  6. data/ext/numo/narray/gen/narray_def.rb +4 -0
  7. data/ext/numo/narray/gen/spec.rb +5 -1
  8. data/ext/numo/narray/gen/tmpl/accum.c +2 -2
  9. data/ext/numo/narray/gen/tmpl/accum_arg.c +88 -0
  10. data/ext/numo/narray/gen/tmpl/accum_binary.c +1 -1
  11. data/ext/numo/narray/gen/tmpl/accum_index.c +25 -14
  12. data/ext/numo/narray/gen/tmpl/aref.c +5 -35
  13. data/ext/numo/narray/gen/tmpl/aset.c +7 -37
  14. data/ext/numo/narray/gen/tmpl/bincount.c +7 -7
  15. data/ext/numo/narray/gen/tmpl/clip.c +11 -15
  16. data/ext/numo/narray/gen/tmpl/cum.c +1 -1
  17. data/ext/numo/narray/gen/tmpl/each.c +4 -2
  18. data/ext/numo/narray/gen/tmpl/each_with_index.c +5 -2
  19. data/ext/numo/narray/gen/tmpl/lib.c +2 -2
  20. data/ext/numo/narray/gen/tmpl/logseq.c +6 -5
  21. data/ext/numo/narray/gen/tmpl/map_with_index.c +5 -6
  22. data/ext/numo/narray/gen/tmpl/median.c +2 -2
  23. data/ext/numo/narray/gen/tmpl/minmax.c +1 -1
  24. data/ext/numo/narray/gen/tmpl/poly.c +4 -4
  25. data/ext/numo/narray/gen/tmpl/qsort.c +1 -1
  26. data/ext/numo/narray/gen/tmpl/rand.c +8 -6
  27. data/ext/numo/narray/gen/tmpl/rand_norm.c +18 -16
  28. data/ext/numo/narray/gen/tmpl/seq.c +5 -4
  29. data/ext/numo/narray/gen/tmpl/sort.c +3 -3
  30. data/ext/numo/narray/gen/tmpl/sort_index.c +2 -2
  31. data/ext/numo/narray/gen/tmpl/store_array.c +14 -2
  32. data/ext/numo/narray/gen/tmpl/unary_s.c +55 -31
  33. data/ext/numo/narray/gen/tmpl_bit/aref.c +22 -30
  34. data/ext/numo/narray/gen/tmpl_bit/aset.c +20 -34
  35. data/ext/numo/narray/gen/tmpl_bit/binary.c +42 -14
  36. data/ext/numo/narray/gen/tmpl_bit/bit_count.c +5 -0
  37. data/ext/numo/narray/gen/tmpl_bit/bit_reduce.c +5 -0
  38. data/ext/numo/narray/gen/tmpl_bit/store_array.c +14 -2
  39. data/ext/numo/narray/gen/tmpl_bit/store_bit.c +21 -7
  40. data/ext/numo/narray/gen/tmpl_bit/unary.c +21 -7
  41. data/ext/numo/narray/index.c +369 -59
  42. data/ext/numo/narray/math.c +2 -2
  43. data/ext/numo/narray/narray.c +45 -27
  44. data/ext/numo/narray/ndloop.c +2 -2
  45. data/ext/numo/narray/numo/intern.h +3 -2
  46. data/ext/numo/narray/numo/narray.h +24 -5
  47. data/ext/numo/narray/numo/ndloop.h +2 -2
  48. data/ext/numo/narray/numo/template.h +4 -6
  49. data/ext/numo/narray/numo/types/complex.h +2 -2
  50. data/ext/numo/narray/step.c +58 -252
  51. data/ext/numo/narray/struct.c +2 -2
  52. data/lib/numo/narray/extra.rb +172 -212
  53. data/numo-narray.gemspec +9 -5
  54. metadata +18 -17
@@ -44,44 +44,68 @@ static void
44
44
  SET_DATA_INDEX(p2,idx2,dtype,x);
45
45
  }
46
46
  } else {
47
- <% if is_simd and !is_complex and %w[sqrt].include? name %>
48
- // Check number of elements. & Check same alignment.
49
- if ((n >= num_pack) && is_same_aligned2(&((dtype*)p1)[i], &((dtype*)p2)[i], SIMD_ALIGNMENT_SIZE)){
50
- // Calculate up to the position just before the start of SIMD computation.
51
- cnt = get_count_of_elements_not_aligned_to_simd_size(&((dtype*)p1)[i], SIMD_ALIGNMENT_SIZE, sizeof(dtype));
52
- for (i=0; i < cnt; i++) {
53
- ((dtype*)p2)[i] = m_<%=name%>(((dtype*)p1)[i]);
54
- }
47
+ //<% if need_align %>
48
+ if (is_aligned(p1,sizeof(dtype)) &&
49
+ is_aligned(p2,sizeof(dtype)) ) {
50
+ if (s1 == sizeof(dtype) &&
51
+ s2 == sizeof(dtype) ) {
52
+ //<% if is_simd and !is_complex and %w[sqrt].include? name %>
53
+ // Check number of elements. & Check same alignment.
54
+ if ((n >= num_pack) && is_same_aligned2(&((dtype*)p1)[i], &((dtype*)p2)[i], SIMD_ALIGNMENT_SIZE)){
55
+ // Calculate up to the position just before the start of SIMD computation.
56
+ cnt = get_count_of_elements_not_aligned_to_simd_size(&((dtype*)p1)[i], SIMD_ALIGNMENT_SIZE, sizeof(dtype));
57
+ for (i=0; i < cnt; i++) {
58
+ ((dtype*)p2)[i] = m_<%=name%>(((dtype*)p1)[i]);
59
+ }
55
60
 
56
- // Get the count of SIMD computation loops.
57
- cnt_simd_loop = (n - i) % num_pack;
61
+ // Get the count of SIMD computation loops.
62
+ cnt_simd_loop = (n - i) % num_pack;
63
+
64
+ // SIMD computation.
65
+ if (p1 == p2) { // inplace case
66
+ for(; i < n - cnt_simd_loop; i += num_pack){
67
+ a = _mm_load_<%=simd_type%>(&((dtype*)p1)[i]);
68
+ a = _mm_<%=name%>_<%=simd_type%>(a);
69
+ _mm_store_<%=simd_type%>(&((dtype*)p1)[i], a);
70
+ }
71
+ } else {
72
+ for(; i < n - cnt_simd_loop; i += num_pack){
73
+ a = _mm_load_<%=simd_type%>(&((dtype*)p1)[i]);
74
+ a = _mm_<%=name%>_<%=simd_type%>(a);
75
+ _mm_stream_<%=simd_type%>(&((dtype*)p2)[i], a);
76
+ }
77
+ }
58
78
 
59
- // SIMD computation.
60
- if (p1 == p2) { // inplace case
61
- for(; i < n - cnt_simd_loop; i += num_pack){
62
- a = _mm_load_<%=simd_type%>(&((dtype*)p1)[i]);
63
- a = _mm_<%=name%>_<%=simd_type%>(a);
64
- _mm_store_<%=simd_type%>(&((dtype*)p1)[i], a);
65
79
  }
66
- } else {
67
- for(; i < n - cnt_simd_loop; i += num_pack){
68
- a = _mm_load_<%=simd_type%>(&((dtype*)p1)[i]);
69
- a = _mm_<%=name%>_<%=simd_type%>(a);
70
- _mm_stream_<%=simd_type%>(&((dtype*)p2)[i], a);
80
+ // Compute the remainder of the SIMD operation.
81
+ if (cnt_simd_loop != 0){
82
+ //<% end %>
83
+ for (; i<n; i++) {
84
+ ((dtype*)p2)[i] = m_<%=name%>(((dtype*)p1)[i]);
85
+ }
86
+ //<% if is_simd and !is_complex and %w[sqrt].include? name %>
71
87
  }
88
+ //<% end %>
89
+ return;
72
90
  }
73
-
74
- }
75
- // Compute the remainder of the SIMD operation.
76
- if (cnt_simd_loop != 0){
77
- <% end %>
78
- for (; i<n; i++) {
79
- ((dtype*)p2)[i] = m_<%=name%>(((dtype*)p1)[i]);
91
+ if (is_aligned_step(s1,sizeof(dtype)) &&
92
+ is_aligned_step(s2,sizeof(dtype)) ) {
93
+ //<% end %>
94
+ for (i=0; i<n; i++) {
95
+ *(dtype*)p2 = m_<%=name%>(*(dtype*)p1);
96
+ p1 += s1;
97
+ p2 += s2;
98
+ }
99
+ return;
100
+ //<% if need_align %>
80
101
  }
81
- <% if is_simd and !is_complex and %w[sqrt].include? name %>
82
102
  }
83
- <% end %>
84
- return;
103
+ for (i=0; i<n; i++) {
104
+ GET_DATA_STRIDE(p1,s1,dtype,x);
105
+ x = m_<%=name%>(x);
106
+ SET_DATA_STRIDE(p2,s2,dtype,x);
107
+ }
108
+ //<% end %>
85
109
  }
86
110
  }
87
111
  }
@@ -1,40 +1,32 @@
1
1
  /*
2
- Array element referenece or slice view.
2
+ Multi-dimensional element reference.
3
3
  @overload [](dim0,...,dimL)
4
- @param [Numeric,Range,etc] dim0,...,dimL Multi-dimensional Index.
5
- @return [Numeric,NArray::<%=class_name%>] Element object or NArray view.
6
-
7
- --- Returns the element at +dim0+, +dim1+, ... are Numeric indices
8
- for each dimension, or returns a NArray View as a sliced subarray if
9
- +dim0+, +dim1+, ... includes other than Numeric index, e.g., Range
10
- or Array or true.
4
+ @param [Numeric,Range,Array,Numo::Int32,Numo::Int64,Numo::Bit,TrueClass,FalseClass,Symbol] dim0,...,dimL multi-dimensional indices.
5
+ @return [Numeric,Numo::Bit] an element or NArray view.
6
+ @see Numo::NArray#[]
7
+ @see #[]=
11
8
 
12
9
  @example
13
- a = Numo::DFloat.new(4,5).seq
14
- => Numo::DFloat#shape=[4,5]
15
- [[0, 1, 2, 3, 4],
16
- [5, 6, 7, 8, 9],
17
- [10, 11, 12, 13, 14],
18
- [15, 16, 17, 18, 19]]
19
-
20
- a[1,1]
21
- => 6.0
10
+ a = Numo::Int32.new(3,4).seq
11
+ # => Numo::Int32#shape=[3,4]
12
+ # [[0, 1, 2, 3],
13
+ # [4, 5, 6, 7],
14
+ # [8, 9, 10, 11]]
22
15
 
23
- a[1..3,1]
24
- => Numo::DFloat#shape=[3]
25
- [6, 11, 16]
16
+ b = (a%2).eq(0)
17
+ # => Numo::Bit#shape=[3,4]
18
+ # [[1, 0, 1, 0],
19
+ # [1, 0, 1, 0],
20
+ # [1, 0, 1, 0]]
26
21
 
27
- a[1,[1,3,4]]
28
- => Numo::DFloat#shape=[3]
29
- [6, 8, 9]
22
+ b[true,(0..-1)%2]
23
+ # => Numo::Bit(view)#shape=[3,2]
24
+ # [[1, 1],
25
+ # [1, 1],
26
+ # [1, 1]]
30
27
 
31
- a[true,2].fill(99)
32
- a
33
- => Numo::DFloat#shape=[4,5]
34
- [[0, 1, 99, 3, 4],
35
- [5, 6, 99, 8, 9],
36
- [10, 11, 99, 13, 14],
37
- [15, 16, 99, 18, 19]]
28
+ b[1,1]
29
+ # => 0
38
30
  */
39
31
  static VALUE
40
32
  <%=c_func(-1)%>(int argc, VALUE *argv, VALUE self)
@@ -1,41 +1,27 @@
1
1
  /*
2
- Array element(s) set.
3
- @overload []=(dim0,..,dimL,val)
4
- @param [Numeric,Range,etc] dim0,..,dimL Multi-dimensional Index.
5
- @param [Numeric,Numo::NArray,etc] val Value(s) to be set to self.
6
- @return [Numeric] returns val (last argument).
7
-
8
- --- Replace element(s) at +dim0+, +dim1+, ... (index/range/array/true
9
- for each dimention). Broadcasting mechanism is applied.
2
+ Multi-dimensional element assignment.
3
+ @overload []=(dim0,...,dimL,val)
4
+ @param [Numeric,Range,Array,Numo::Int32,Numo::Int64,Numo::Bit,TrueClass,FalseClass,Symbol] dim0,...,dimL multi-dimensional indices.
5
+ @param [Numeric,Numo::NArray,Array] val Value(s) to be set to self.
6
+ @return [Numeric,Numo::NArray,Array] returns `val` (last argument).
7
+ @see Numo::NArray#[]=
8
+ @see #[]
10
9
 
11
10
  @example
12
- a = Numo::DFloat.new(3,4).seq
13
- => Numo::DFloat#shape=[3,4]
14
- [[0, 1, 2, 3],
15
- [4, 5, 6, 7],
16
- [8, 9, 10, 11]]
17
-
18
- a[1,2]=99
19
- a
20
- => Numo::DFloat#shape=[3,4]
21
- [[0, 1, 2, 3],
22
- [4, 5, 99, 7],
23
- [8, 9, 10, 11]]
24
-
25
- a[1,[0,2]] = [101,102]
11
+ a = Numo::Bit.new(4,5).fill(0)
12
+ # => Numo::Bit#shape=[4,5]
13
+ # [[0, 0, 0, 0, 0],
14
+ # [0, 0, 0, 0, 0],
15
+ # [0, 0, 0, 0, 0],
16
+ # [0, 0, 0, 0, 0]]
17
+
18
+ a[(0..-1)%2,(1..-1)%2] = 1
26
19
  a
27
- => Numo::DFloat#shape=[3,4]
28
- [[0, 1, 2, 3],
29
- [101, 5, 102, 7],
30
- [8, 9, 10, 11]]
31
-
32
- a[1,true]=99
33
- a
34
- => Numo::DFloat#shape=[3,4]
35
- [[0, 1, 2, 3],
36
- [99, 99, 99, 99],
37
- [8, 9, 10, 11]]
38
-
20
+ # => Numo::Bit#shape=[4,5]
21
+ # [[0, 1, 0, 1, 0],
22
+ # [0, 0, 0, 0, 0],
23
+ # [0, 1, 0, 1, 0],
24
+ # [0, 0, 0, 0, 0]]
39
25
  */
40
26
  static VALUE
41
27
  <%=c_func(-1)%>(int argc, VALUE *argv, VALUE self)
@@ -21,10 +21,8 @@ static void
21
21
  STORE_BIT_STEP(a3, p3, s3, idx3, x);
22
22
  }
23
23
  } else {
24
- o1 = p1 % NB;
25
- o1 -= p3;
26
- o2 = p2 % NB;
27
- o2 -= p3;
24
+ o1 = p1-p3;
25
+ o2 = p2-p3;
28
26
  l1 = NB+o1;
29
27
  r1 = NB-o1;
30
28
  l2 = NB+o2;
@@ -54,23 +52,53 @@ static void
54
52
  }
55
53
  } else {
56
54
  for (; n>=NB; n-=NB) {
57
- x = *a1>>o1;
58
- if (o1<0) x |= *(a1-1)>>l1;
59
- if (o1>0) x |= *(a1+1)<<r1;
55
+ if (o1==0) {
56
+ x = *a1;
57
+ } else if (o1>0) {
58
+ x = *a1>>o1 | *(a1+1)<<r1;
59
+ } else {
60
+ x = *a1<<-o1 | *(a1-1)>>l1;
61
+ }
60
62
  a1++;
61
- y = *a2>>o2;
62
- if (o2<0) y |= *(a2-1)>>l2;
63
- if (o2>0) y |= *(a2+1)<<r2;
63
+ if (o2==0) {
64
+ y = *a2;
65
+ } else if (o2>0) {
66
+ y = *a2>>o2 | *(a2+1)<<r2;
67
+ } else {
68
+ y = *a2<<-o2 | *(a2-1)>>l2;
69
+ }
64
70
  a2++;
65
71
  x = m_<%=name%>(x,y);
66
72
  *(a3++) = x;
67
73
  }
68
74
  }
69
75
  if (n>0) {
70
- x = *a1>>o1;
71
- if (o1<0) x |= *(a1-1)>>l1;
72
- y = *a2>>o2;
73
- if (o2<0) y |= *(a2-1)>>l2;
76
+ if (o1==0) {
77
+ x = *a1;
78
+ } else if (o1>0) {
79
+ x = *a1>>o1;
80
+ if ((int)n>r1) {
81
+ x |= *(a1+1)<<r1;
82
+ }
83
+ } else {
84
+ x = *(a1-1)>>l1;
85
+ if ((int)n>-o1) {
86
+ x |= *a1<<-o1;
87
+ }
88
+ }
89
+ if (o2==0) {
90
+ y = *a2;
91
+ } else if (o2>0) {
92
+ y = *a2>>o2;
93
+ if ((int)n>r2) {
94
+ y |= *(a2+1)<<r2;
95
+ }
96
+ } else {
97
+ y = *(a2-1)>>l2;
98
+ if ((int)n>-o2) {
99
+ y |= *a2<<-o2;
100
+ }
101
+ }
74
102
  x = m_<%=name%>(x,y);
75
103
  *a3 = (x & SLB(n)) | (*a3 & BALL<<n);
76
104
  }
@@ -75,10 +75,15 @@ static VALUE
75
75
  <%=c_func(-1)%>(int argc, VALUE *argv, VALUE self)
76
76
  {
77
77
  VALUE v, reduce;
78
+ narray_t *na;
78
79
  ndfunc_arg_in_t ain[3] = {{cT,0},{sym_reduce,0},{sym_init,0}};
79
80
  ndfunc_arg_out_t aout[1] = {{numo_cInt64,0}};
80
81
  ndfunc_t ndf = { <%=c_iter%>, FULL_LOOP_NIP, 3, 1, ain, aout };
81
82
 
83
+ GetNArray(self,na);
84
+ if (NA_SIZE(na)==0) {
85
+ return INT2FIX(0);
86
+ }
82
87
  reduce = na_reduce_dimension(argc, argv, 1, &self, &ndf, 0);
83
88
  v = na_ndloop(&ndf, 3, self, reduce, INT2FIX(0));
84
89
  return rb_funcall(v,rb_intern("extract"),0);
@@ -107,10 +107,15 @@ static VALUE
107
107
  <%=c_func(-1)%>(int argc, VALUE *argv, VALUE self)
108
108
  {
109
109
  VALUE v, reduce;
110
+ narray_t *na;
110
111
  ndfunc_arg_in_t ain[3] = {{cT,0},{sym_reduce,0},{sym_init,0}};
111
112
  ndfunc_arg_out_t aout[1] = {{numo_cBit,0}};
112
113
  ndfunc_t ndf = {<%=c_iter%>, FULL_LOOP_NIP, 3,1, ain,aout};
113
114
 
115
+ GetNArray(self,na);
116
+ if (NA_SIZE(na)==0) {
117
+ return Qfalse;
118
+ }
114
119
  reduce = na_reduce_dimension(argc, argv, 1, &self, &ndf, 0);
115
120
  v = na_ndloop(&ndf, 3, self, reduce, INT2FIX(<%=init_bit%>));
116
121
  if (argc > 0) {
@@ -48,7 +48,13 @@ static void
48
48
  if (idx1) {
49
49
  for (i=i1=0; i1<n1 && i<n; i++,i1++) {
50
50
  x = ptr[i1];
51
- if (rb_obj_is_kind_of(x, rb_cRange) || rb_obj_is_kind_of(x, na_cStep)) {
51
+ if (rb_obj_is_kind_of(x, rb_cRange)
52
+ #ifdef HAVE_RB_ARITHMETIC_SEQUENCE_EXTRACT
53
+ || rb_obj_is_kind_of(x, rb_cArithSeq)
54
+ #else
55
+ || rb_obj_is_kind_of(x, rb_cEnumerator)
56
+ #endif
57
+ ) {
52
58
  nary_step_sequence(x,&len,&beg,&step);
53
59
  for (c=0; c<len && i<n; c++,i++) {
54
60
  y = beg + step * c;
@@ -65,7 +71,13 @@ static void
65
71
  } else {
66
72
  for (i=i1=0; i1<n1 && i<n; i++,i1++) {
67
73
  x = ptr[i1];
68
- if (rb_obj_is_kind_of(x, rb_cRange) || rb_obj_is_kind_of(x, na_cStep)) {
74
+ if (rb_obj_is_kind_of(x, rb_cRange)
75
+ #ifdef HAVE_RB_ARITHMETIC_SEQUENCE_EXTRACT
76
+ || rb_obj_is_kind_of(x, rb_cArithSeq)
77
+ #else
78
+ || rb_obj_is_kind_of(x, rb_cEnumerator)
79
+ #endif
80
+ ) {
69
81
  nary_step_sequence(x,&len,&beg,&step);
70
82
  for (c=0; c<len && i<n; c++,i++) {
71
83
  y = beg + step * c;
@@ -18,8 +18,7 @@ static void
18
18
  STORE_BIT_STEP(a3, p3, s3, idx3, x);
19
19
  }
20
20
  } else {
21
- o1 = p1 % NB;
22
- o1 -= p3;
21
+ o1 = p1-p3;
23
22
  l1 = NB+o1;
24
23
  r1 = NB-o1;
25
24
  if (p3>0 || n<NB) {
@@ -40,16 +39,31 @@ static void
40
39
  }
41
40
  } else {
42
41
  for (; n>=NB; n-=NB) {
43
- x = *a1>>o1;
44
- if (o1<0) x |= *(a1-1)>>l1;
45
- if (o1>0) x |= *(a1+1)<<r1;
42
+ if (o1==0) {
43
+ x = *a1;
44
+ } else if (o1>0) {
45
+ x = *a1>>o1 | *(a1+1)<<r1;
46
+ } else {
47
+ x = *a1<<-o1 | *(a1-1)>>l1;
48
+ }
46
49
  a1++;
47
50
  *(a3++) = x;
48
51
  }
49
52
  }
50
53
  if (n>0) {
51
- x = *a1>>o1;
52
- if (o1<0) x |= *(a1-1)>>l1;
54
+ if (o1==0) {
55
+ x = *a1;
56
+ } else if (o1>0) {
57
+ x = *a1>>o1;
58
+ if ((int)n>r1) {
59
+ x |= *(a1+1)<<r1;
60
+ }
61
+ } else {
62
+ x = *(a1-1)>>l1;
63
+ if ((int)n>-o1) {
64
+ x |= *a1<<-o1;
65
+ }
66
+ }
53
67
  *a3 = (x & SLB(n)) | (*a3 & BALL<<n);
54
68
  }
55
69
  }
@@ -20,8 +20,7 @@ static void
20
20
  STORE_BIT_STEP(a3, p3, s3, idx3, y);
21
21
  }
22
22
  } else {
23
- o1 = p1 % NB;
24
- o1 -= p3;
23
+ o1 = p1-p3;
25
24
  l1 = NB+o1;
26
25
  r1 = NB-o1;
27
26
  if (p3>0 || n<NB) {
@@ -44,17 +43,32 @@ static void
44
43
  }
45
44
  } else {
46
45
  for (; n>=NB; n-=NB) {
47
- x = *a1>>o1;
48
- if (o1<0) x |= *(a1-1)>>l1;
49
- if (o1>0) x |= *(a1+1)<<r1;
46
+ if (o1==0) {
47
+ x = *a1;
48
+ } else if (o1>0) {
49
+ x = *a1>>o1 | *(a1+1)<<r1;
50
+ } else {
51
+ x = *a1<<-o1 | *(a1-1)>>l1;
52
+ }
50
53
  a1++;
51
54
  y = m_<%=name%>(x);
52
55
  *(a3++) = y;
53
56
  }
54
57
  }
55
58
  if (n>0) {
56
- x = *a1>>o1;
57
- if (o1<0) x |= *(a1-1)>>l1;
59
+ if (o1==0) {
60
+ x = *a1;
61
+ } else if (o1>0) {
62
+ x = *a1>>o1;
63
+ if ((int)n>r1) {
64
+ x |= *(a1+1)<<r1;
65
+ }
66
+ } else {
67
+ x = *(a1-1)>>l1;
68
+ if ((int)n>-o1) {
69
+ x |= *a1<<-o1;
70
+ }
71
+ }
58
72
  y = m_<%=name%>(x);
59
73
  *a3 = (y & SLB(n)) | (*a3 & BALL<<n);
60
74
  }