twisty_puzzles 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/lib/twisty_puzzles.rb +37 -0
  4. data/lib/twisty_puzzles/abstract_direction.rb +38 -39
  5. data/lib/twisty_puzzles/abstract_move_parser.rb +32 -33
  6. data/lib/twisty_puzzles/algorithm.rb +112 -113
  7. data/lib/twisty_puzzles/algorithm_transformation.rb +19 -21
  8. data/lib/twisty_puzzles/axis_face_and_direction_move.rb +55 -56
  9. data/lib/twisty_puzzles/cancellation_helper.rb +124 -125
  10. data/lib/twisty_puzzles/commutator.rb +79 -80
  11. data/lib/twisty_puzzles/compiled_algorithm.rb +31 -32
  12. data/lib/twisty_puzzles/compiled_cube_algorithm.rb +49 -50
  13. data/lib/twisty_puzzles/compiled_skewb_algorithm.rb +18 -19
  14. data/lib/twisty_puzzles/coordinate.rb +245 -246
  15. data/lib/twisty_puzzles/cube.rb +494 -495
  16. data/lib/twisty_puzzles/cube_constants.rb +40 -41
  17. data/lib/twisty_puzzles/cube_direction.rb +15 -18
  18. data/lib/twisty_puzzles/cube_move.rb +289 -290
  19. data/lib/twisty_puzzles/cube_move_parser.rb +75 -76
  20. data/lib/twisty_puzzles/cube_print_helper.rb +132 -133
  21. data/lib/twisty_puzzles/cube_state.rb +80 -81
  22. data/lib/twisty_puzzles/move_type_creator.rb +17 -18
  23. data/lib/twisty_puzzles/parser.rb +176 -179
  24. data/lib/twisty_puzzles/part_cycle_factory.rb +39 -42
  25. data/lib/twisty_puzzles/puzzle.rb +16 -17
  26. data/lib/twisty_puzzles/reversible_applyable.rb +24 -25
  27. data/lib/twisty_puzzles/rotation.rb +74 -75
  28. data/lib/twisty_puzzles/skewb_direction.rb +14 -15
  29. data/lib/twisty_puzzles/skewb_move.rb +48 -49
  30. data/lib/twisty_puzzles/skewb_move_parser.rb +50 -51
  31. data/lib/twisty_puzzles/skewb_notation.rb +115 -118
  32. data/lib/twisty_puzzles/skewb_state.rb +120 -121
  33. data/lib/twisty_puzzles/state_helper.rb +20 -21
  34. data/lib/twisty_puzzles/sticker_cycle.rb +43 -44
  35. data/lib/twisty_puzzles/utils.rb +3 -0
  36. data/lib/twisty_puzzles/version.rb +3 -1
  37. metadata +3 -3
@@ -10,91 +10,90 @@ require 'twisty_puzzles/rotation'
10
10
  require 'twisty_puzzles/skewb_move'
11
11
 
12
12
  module TwistyPuzzles
13
-
14
- # Parser for cube moves.
15
- class CubeMoveParser < AbstractMoveParser
16
- REGEXP =
17
- begin
18
- axes_part = "(?<axis_name>[#{AbstractMove::AXES.join}])"
19
- face_names = CubeConstants::FACE_NAMES.join
20
- fat_move_part =
21
- "(?<width>\\d*)(?<fat_face_name>[#{face_names}])w"
22
- normal_move_part = "(?<face_name>[#{face_names}])"
23
- downcased_face_names = face_names.downcase
24
- maybe_fat_maybe_slice_move_part =
25
- "(?<maybe_fat_face_maybe_slice_name>[#{downcased_face_names}])"
26
- slice_move_part =
27
- "(?<slice_index>\\d+)(?<slice_name>[#{downcased_face_names}])"
28
- mslice_move_part =
29
- "(?<mslice_name>[#{AbstractMove::SLICE_FACES.keys.join}])"
30
- move_part = "(?:#{axes_part}|" \
31
- "#{fat_move_part}|" \
32
- "#{normal_move_part}|" \
33
- "#{maybe_fat_maybe_slice_move_part}|" \
34
- "#{slice_move_part}|#{mslice_move_part})"
35
- direction_names =
36
- AbstractDirection::POSSIBLE_DIRECTION_NAMES.flatten
37
- direction_names.sort_by! { |e| -e.length }
38
- direction_part = "(?<direction>#{direction_names.join('|')})"
39
- Regexp.new("#{move_part}#{direction_part}")
40
- end
13
+ # Parser for cube moves.
14
+ class CubeMoveParser < AbstractMoveParser
15
+ REGEXP =
16
+ begin
17
+ axes_part = "(?<axis_name>[#{AbstractMove::AXES.join}])"
18
+ face_names = CubeConstants::FACE_NAMES.join
19
+ fat_move_part =
20
+ "(?<width>\\d*)(?<fat_face_name>[#{face_names}])w"
21
+ normal_move_part = "(?<face_name>[#{face_names}])"
22
+ downcased_face_names = face_names.downcase
23
+ maybe_fat_maybe_slice_move_part =
24
+ "(?<maybe_fat_face_maybe_slice_name>[#{downcased_face_names}])"
25
+ slice_move_part =
26
+ "(?<slice_index>\\d+)(?<slice_name>[#{downcased_face_names}])"
27
+ mslice_move_part =
28
+ "(?<mslice_name>[#{AbstractMove::SLICE_FACES.keys.join}])"
29
+ move_part = "(?:#{axes_part}|" \
30
+ "#{fat_move_part}|" \
31
+ "#{normal_move_part}|" \
32
+ "#{maybe_fat_maybe_slice_move_part}|" \
33
+ "#{slice_move_part}|#{mslice_move_part})"
34
+ direction_names =
35
+ AbstractDirection::POSSIBLE_DIRECTION_NAMES.flatten
36
+ direction_names.sort_by! { |e| -e.length }
37
+ direction_part = "(?<direction>#{direction_names.join('|')})"
38
+ Regexp.new("#{move_part}#{direction_part}")
39
+ end
41
40
 
42
- MOVE_TYPE_CREATORS = [
43
- MoveTypeCreator.new(%i[axis_face direction], Rotation),
44
- MoveTypeCreator.new(%i[fat_face direction width], FatMove),
45
- MoveTypeCreator.new(%i[face direction], FatMove),
46
- MoveTypeCreator.new(%i[maybe_fat_face_maybe_slice_face direction], MaybeFatMaybeSliceMove),
47
- MoveTypeCreator.new(%i[slice_face direction slice_index], SliceMove),
48
- MoveTypeCreator.new(%i[mslice_face direction], MaybeFatMSliceMaybeInnerMSliceMove)
49
- ].freeze
41
+ MOVE_TYPE_CREATORS = [
42
+ MoveTypeCreator.new(%i[axis_face direction], Rotation),
43
+ MoveTypeCreator.new(%i[fat_face direction width], FatMove),
44
+ MoveTypeCreator.new(%i[face direction], FatMove),
45
+ MoveTypeCreator.new(%i[maybe_fat_face_maybe_slice_face direction], MaybeFatMaybeSliceMove),
46
+ MoveTypeCreator.new(%i[slice_face direction slice_index], SliceMove),
47
+ MoveTypeCreator.new(%i[mslice_face direction], MaybeFatMSliceMaybeInnerMSliceMove)
48
+ ].freeze
50
49
 
51
- INSTANCE = CubeMoveParser.new
52
- def regexp
53
- REGEXP
54
- end
50
+ INSTANCE = CubeMoveParser.new
51
+ def regexp
52
+ REGEXP
53
+ end
55
54
 
56
- def move_type_creators
57
- MOVE_TYPE_CREATORS
58
- end
55
+ def move_type_creators
56
+ MOVE_TYPE_CREATORS
57
+ end
59
58
 
60
- def parse_part_key(name)
61
- name.sub('_name', '_face').sub('face_face', 'face')
62
- end
59
+ def parse_part_key(name)
60
+ name.sub('_name', '_face').sub('face_face', 'face')
61
+ end
63
62
 
64
- def parse_direction(direction_string)
65
- value = AbstractDirection::POSSIBLE_DIRECTION_NAMES.index do |ds|
66
- ds.include?(direction_string)
67
- end + 1
68
- CubeDirection.new(value)
69
- end
63
+ def parse_direction(direction_string)
64
+ value = AbstractDirection::POSSIBLE_DIRECTION_NAMES.index do |ds|
65
+ ds.include?(direction_string)
66
+ end + 1
67
+ CubeDirection.new(value)
68
+ end
70
69
 
71
- def parse_axis_face(axis_face_string)
72
- Face::ELEMENTS[AbstractMove::AXES.index(axis_face_string)]
73
- end
70
+ def parse_axis_face(axis_face_string)
71
+ Face::ELEMENTS[AbstractMove::AXES.index(axis_face_string)]
72
+ end
74
73
 
75
- def parse_mslice_face(mslice_name)
76
- AbstractMove::SLICE_FACES[mslice_name]
77
- end
74
+ def parse_mslice_face(mslice_name)
75
+ AbstractMove::SLICE_FACES[mslice_name]
76
+ end
78
77
 
79
- def parse_width(width_string)
80
- width_string.empty? ? 2 : Integer(width_string, 10)
81
- end
78
+ def parse_width(width_string)
79
+ width_string.empty? ? 2 : Integer(width_string, 10)
80
+ end
82
81
 
83
- # rubocop:disable Metrics/CyclomaticComplexity
84
- def parse_move_part(name, value)
85
- case name
86
- when 'axis_name' then parse_axis_face(value)
87
- when 'width' then parse_width(value)
88
- when 'slice_index' then Integer(value, 10)
89
- when 'fat_face_name', 'face_name' then Face.by_name(value)
90
- when 'maybe_fat_face_maybe_slice_name', 'slice_name'
91
- Face.by_name(value.upcase)
92
- when 'mslice_name'
93
- parse_mslice_face(value)
94
- when 'direction' then parse_direction(value)
95
- else raise
96
- end
82
+ # rubocop:disable Metrics/CyclomaticComplexity
83
+ def parse_move_part(name, value)
84
+ case name
85
+ when 'axis_name' then parse_axis_face(value)
86
+ when 'width' then parse_width(value)
87
+ when 'slice_index' then Integer(value, 10)
88
+ when 'fat_face_name', 'face_name' then Face.by_name(value)
89
+ when 'maybe_fat_face_maybe_slice_name', 'slice_name'
90
+ Face.by_name(value.upcase)
91
+ when 'mslice_name'
92
+ parse_mslice_face(value)
93
+ when 'direction' then parse_direction(value)
94
+ else raise
97
95
  end
98
- # rubocop:enable Metrics/CyclomaticComplexity
99
96
  end
97
+ # rubocop:enable Metrics/CyclomaticComplexity
98
+ end
100
99
  end
@@ -4,157 +4,156 @@ require 'colorize'
4
4
  require 'twisty_puzzles/utils/array_helper'
5
5
 
6
6
  module TwistyPuzzles
7
-
8
- # Module to print and display cube and Skewb states.
9
- module CubePrintHelper
10
- include Utils::ArrayHelper
11
-
12
- def color_symbol(color)
13
- case color
14
- when :orange then :light_red
15
- when :unknown then :light_black
16
- else color
17
- end
7
+ # Module to print and display cube and Skewb states.
8
+ module CubePrintHelper
9
+ include Utils::ArrayHelper
10
+
11
+ def color_symbol(color)
12
+ case color
13
+ when :orange then :light_red
14
+ when :unknown then :light_black
15
+ else color
18
16
  end
17
+ end
19
18
 
20
- COLOR_MODES = %i[color nocolor].freeze
21
- ColorInfo = Struct.new(:reverse_lines_mode, :reverse_columns_mode, :skewb_corner_permutation)
22
- FACE_SYMBOL_INFOS = {
23
- U: ColorInfo.new(:reverse, :reverse, [2, 3, 0, 1]),
24
- L: ColorInfo.new(:keep, :reverse, [2, 0, 3, 1]),
25
- F: ColorInfo.new(:keep, :reverse, [2, 0, 3, 1]),
26
- R: ColorInfo.new(:keep, :keep, [1, 0, 3, 2]),
27
- B: ColorInfo.new(:keep, :keep, [1, 0, 3, 2]),
28
- D: ColorInfo.new(:keep, :reverse, [2, 0, 3, 1])
29
- }.freeze
30
-
31
- def color_character(color, color_mode)
32
- unless COLOR_MODES.include?(color_mode)
33
- raise ArgumentError, "Invalid color mode #{color_mode}"
34
- end
35
-
36
- char = color.to_s[0].upcase
37
- if color_mode == :color
38
- char.colorize(background: color_symbol(color))
39
- else
40
- char
41
- end
19
+ COLOR_MODES = %i[color nocolor].freeze
20
+ ColorInfo = Struct.new(:reverse_lines_mode, :reverse_columns_mode, :skewb_corner_permutation)
21
+ FACE_SYMBOL_INFOS = {
22
+ U: ColorInfo.new(:reverse, :reverse, [2, 3, 0, 1]),
23
+ L: ColorInfo.new(:keep, :reverse, [2, 0, 3, 1]),
24
+ F: ColorInfo.new(:keep, :reverse, [2, 0, 3, 1]),
25
+ R: ColorInfo.new(:keep, :keep, [1, 0, 3, 2]),
26
+ B: ColorInfo.new(:keep, :keep, [1, 0, 3, 2]),
27
+ D: ColorInfo.new(:keep, :reverse, [2, 0, 3, 1])
28
+ }.freeze
29
+
30
+ def color_character(color, color_mode)
31
+ unless COLOR_MODES.include?(color_mode)
32
+ raise ArgumentError, "Invalid color mode #{color_mode}"
42
33
  end
43
34
 
44
- def maybe_reverse(reverse_mode, stuff)
45
- if reverse_mode == :reverse
46
- stuff.reverse
47
- elsif reverse_mode == :keep
48
- stuff
49
- else
50
- raise
51
- end
35
+ char = color.to_s[0].upcase
36
+ if color_mode == :color
37
+ char.colorize(background: color_symbol(color))
38
+ else
39
+ char
52
40
  end
41
+ end
53
42
 
54
- def face_lines(cube_state, face_symbol, row_multiplicity = 1, column_multiplicity = 1)
55
- face = Face.for_face_symbol(face_symbol)
56
- face_symbol_info = FACE_SYMBOL_INFOS[face_symbol]
57
- stickers = cube_state.sticker_array(face)
58
- lines =
59
- stickers.collect_concat do |sticker_line|
60
- line = sticker_line.map { |c| yield(c) * column_multiplicity }
61
- [maybe_reverse(face_symbol_info.reverse_columns_mode, line).join] * row_multiplicity
62
- end
63
- maybe_reverse(face_symbol_info.reverse_lines_mode, lines)
43
+ def maybe_reverse(reverse_mode, stuff)
44
+ if reverse_mode == :reverse
45
+ stuff.reverse
46
+ elsif reverse_mode == :keep
47
+ stuff
48
+ else
49
+ raise
64
50
  end
51
+ end
65
52
 
66
- def simple_face_lines(cube_state, face_symbol, color_mode)
67
- face_lines(cube_state, face_symbol) { |c| color_character(c, color_mode) }
68
- end
53
+ def face_lines(cube_state, face_symbol, row_multiplicity = 1, column_multiplicity = 1)
54
+ face = Face.for_face_symbol(face_symbol)
55
+ face_symbol_info = FACE_SYMBOL_INFOS[face_symbol]
56
+ stickers = cube_state.sticker_array(face)
57
+ lines =
58
+ stickers.collect_concat do |sticker_line|
59
+ line = sticker_line.map { |c| yield(c) * column_multiplicity }
60
+ [maybe_reverse(face_symbol_info.reverse_columns_mode, line).join] * row_multiplicity
61
+ end
62
+ maybe_reverse(face_symbol_info.reverse_lines_mode, lines)
63
+ end
69
64
 
70
- SKEWB_FACE_SIZE = 5
65
+ def simple_face_lines(cube_state, face_symbol, color_mode)
66
+ face_lines(cube_state, face_symbol) { |c| color_character(c, color_mode) }
67
+ end
71
68
 
72
- def skewb_ascii_art_line(first_color, middle_color, last_color, num_first_color)
73
- raise if num_first_color > SKEWB_FACE_SIZE / 2
69
+ SKEWB_FACE_SIZE = 5
74
70
 
75
- first_color * num_first_color +
76
- middle_color * (SKEWB_FACE_SIZE - 2 * num_first_color) +
77
- last_color * num_first_color
78
- end
71
+ def skewb_ascii_art_line(first_color, middle_color, last_color, num_first_color)
72
+ raise if num_first_color > SKEWB_FACE_SIZE / 2
79
73
 
80
- def skewb_ascii_art(center_color, corner_colors)
81
- raise unless corner_colors.length == 4
82
-
83
- first_part =
84
- (1..SKEWB_FACE_SIZE / 2).to_a.reverse.map do |i|
85
- skewb_ascii_art_line(corner_colors[0], center_color, corner_colors[1], i)
86
- end
87
- middle_part = SKEWB_FACE_SIZE.odd? ? [center_color * SKEWB_FACE_SIZE] : []
88
- last_part =
89
- (1..SKEWB_FACE_SIZE / 2).map do |i|
90
- skewb_ascii_art_line(corner_colors[2], center_color, corner_colors[3], i)
91
- end
92
- first_part + middle_part + last_part
93
- end
74
+ first_color * num_first_color +
75
+ middle_color * (SKEWB_FACE_SIZE - 2 * num_first_color) +
76
+ last_color * num_first_color
77
+ end
94
78
 
95
- # Prints a Skewb face like this:
96
- # rrgww
97
- # rgggw
98
- # ggggg
99
- # ogggb
100
- # oogbb
101
- def skewb_face_lines(cube_state, face_symbol, color_mode)
102
- face = Face.for_face_symbol(face_symbol)
103
- face_symbol_info = FACE_SYMBOL_INFOS[face_symbol]
104
- stickers = cube_state.sticker_array(face)
105
- center_color = color_character(stickers[0], color_mode)
106
- corner_colors = stickers[1..-1].map { |c| color_character(c, color_mode) }
107
- permuted_corner_colors =
108
- apply_permutation(corner_colors, face_symbol_info.skewb_corner_permutation)
109
- raise unless corner_colors.length == 4
110
-
111
- skewb_ascii_art(center_color, permuted_corner_colors)
112
- end
79
+ def skewb_ascii_art(center_color, corner_colors)
80
+ raise unless corner_colors.length == 4
113
81
 
114
- def ll_string(cube_state, color_mode)
115
- top_face = face_lines(cube_state, :U, 2, 3) { |c| color_character(c, color_mode) }
116
- front_face = face_lines(cube_state, :F, 1, 3) { |c| color_character(c, color_mode) }
117
- right_face = face_lines(cube_state, :R, 1, 3) { |c| color_character(c, color_mode) }
118
- pll_line = front_face.first + right_face.first
119
- (top_face + [pll_line] * 3).join("\n")
120
- end
82
+ first_part =
83
+ (1..SKEWB_FACE_SIZE / 2).to_a.reverse.map do |i|
84
+ skewb_ascii_art_line(corner_colors[0], center_color, corner_colors[1], i)
85
+ end
86
+ middle_part = SKEWB_FACE_SIZE.odd? ? [center_color * SKEWB_FACE_SIZE] : []
87
+ last_part =
88
+ (1..SKEWB_FACE_SIZE / 2).map do |i|
89
+ skewb_ascii_art_line(corner_colors[2], center_color, corner_colors[3], i)
90
+ end
91
+ first_part + middle_part + last_part
92
+ end
121
93
 
122
- def cube_string(cube_state, color_mode)
123
- top_face = simple_face_lines(cube_state, :U, color_mode)
124
- left_face = simple_face_lines(cube_state, :L, color_mode)
125
- front_face = simple_face_lines(cube_state, :F, color_mode)
126
- right_face = simple_face_lines(cube_state, :R, color_mode)
127
- back_face = simple_face_lines(cube_state, :B, color_mode)
128
- bottom_face = simple_face_lines(cube_state, :D, color_mode)
129
- middle_belt = zip_concat_lines(left_face, front_face, right_face, back_face)
130
- lines = pad_lines(top_face, cube_state.n) + middle_belt +
131
- pad_lines(bottom_face, cube_state.n)
132
- lines.join("\n")
133
- end
94
+ # Prints a Skewb face like this:
95
+ # rrgww
96
+ # rgggw
97
+ # ggggg
98
+ # ogggb
99
+ # oogbb
100
+ def skewb_face_lines(cube_state, face_symbol, color_mode)
101
+ face = Face.for_face_symbol(face_symbol)
102
+ face_symbol_info = FACE_SYMBOL_INFOS[face_symbol]
103
+ stickers = cube_state.sticker_array(face)
104
+ center_color = color_character(stickers[0], color_mode)
105
+ corner_colors = stickers[1..-1].map { |c| color_character(c, color_mode) }
106
+ permuted_corner_colors =
107
+ apply_permutation(corner_colors, face_symbol_info.skewb_corner_permutation)
108
+ raise unless corner_colors.length == 4
109
+
110
+ skewb_ascii_art(center_color, permuted_corner_colors)
111
+ end
134
112
 
135
- def skewb_string(skewb_state, color_mode)
136
- top_face = skewb_face_lines(skewb_state, :U, color_mode)
137
- left_face = skewb_face_lines(skewb_state, :L, color_mode)
138
- front_face = skewb_face_lines(skewb_state, :F, color_mode)
139
- right_face = skewb_face_lines(skewb_state, :R, color_mode)
140
- back_face = skewb_face_lines(skewb_state, :B, color_mode)
141
- bottom_face = skewb_face_lines(skewb_state, :D, color_mode)
142
- middle_belt = zip_concat_lines(left_face, front_face, right_face, back_face)
143
- lines = pad_lines(top_face, SKEWB_FACE_SIZE) + middle_belt +
144
- pad_lines(bottom_face, SKEWB_FACE_SIZE)
145
- lines.join("\n")
146
- end
113
+ def ll_string(cube_state, color_mode)
114
+ top_face = face_lines(cube_state, :U, 2, 3) { |c| color_character(c, color_mode) }
115
+ front_face = face_lines(cube_state, :F, 1, 3) { |c| color_character(c, color_mode) }
116
+ right_face = face_lines(cube_state, :R, 1, 3) { |c| color_character(c, color_mode) }
117
+ pll_line = front_face.first + right_face.first
118
+ (top_face + [pll_line] * 3).join("\n")
119
+ end
147
120
 
148
- def empty_name
149
- ' '
150
- end
121
+ def cube_string(cube_state, color_mode)
122
+ top_face = simple_face_lines(cube_state, :U, color_mode)
123
+ left_face = simple_face_lines(cube_state, :L, color_mode)
124
+ front_face = simple_face_lines(cube_state, :F, color_mode)
125
+ right_face = simple_face_lines(cube_state, :R, color_mode)
126
+ back_face = simple_face_lines(cube_state, :B, color_mode)
127
+ bottom_face = simple_face_lines(cube_state, :D, color_mode)
128
+ middle_belt = zip_concat_lines(left_face, front_face, right_face, back_face)
129
+ lines = pad_lines(top_face, cube_state.n) + middle_belt +
130
+ pad_lines(bottom_face, cube_state.n)
131
+ lines.join("\n")
132
+ end
151
133
 
152
- def pad_lines(lines, padding)
153
- lines.map { |line| empty_name * padding + line }
154
- end
134
+ def skewb_string(skewb_state, color_mode)
135
+ top_face = skewb_face_lines(skewb_state, :U, color_mode)
136
+ left_face = skewb_face_lines(skewb_state, :L, color_mode)
137
+ front_face = skewb_face_lines(skewb_state, :F, color_mode)
138
+ right_face = skewb_face_lines(skewb_state, :R, color_mode)
139
+ back_face = skewb_face_lines(skewb_state, :B, color_mode)
140
+ bottom_face = skewb_face_lines(skewb_state, :D, color_mode)
141
+ middle_belt = zip_concat_lines(left_face, front_face, right_face, back_face)
142
+ lines = pad_lines(top_face, SKEWB_FACE_SIZE) + middle_belt +
143
+ pad_lines(bottom_face, SKEWB_FACE_SIZE)
144
+ lines.join("\n")
145
+ end
155
146
 
156
- def zip_concat_lines(*args)
157
- args.transpose.map(&:join)
158
- end
147
+ def empty_name
148
+ ' '
149
+ end
150
+
151
+ def pad_lines(lines, padding)
152
+ lines.map { |line| empty_name * padding + line }
153
+ end
154
+
155
+ def zip_concat_lines(*args)
156
+ args.transpose.map(&:join)
159
157
  end
158
+ end
160
159
  end