Associating values from one array to another in Ruby - ruby

I'm building a simple program after learning a little bit of Ruby. I'm trying to associate values from one array to another here's what I've got so far.
ColorValues = ["Black", "Brown", "Red", "Orange", "Yellow", "Green", "Cyan", "Blue", "Violet", "Pink", "Grey"]
(0..127).each_slice(12) {|i| p i}
.each_slice returns these arrays
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
[24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
[36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]
[48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71]
[72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83]
[84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95]
[96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107]
[108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119]
[120, 121, 122, 123, 124, 125, 126, 127]
What I'm attempting to do is then take the returned arrays and associate them with each color in the ColorValues[] array
i.e.
"Black" = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
"Brown" = [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
I'm sure there's a simple way of doing this I'm just not sure how to go about doing it.

Using Enumerable#zip and Hash::[]
colorValues = ["Black", "Brown", "Red", "Orange", "Yellow", "Green", "Cyan", "Blue", "Violet", "Pink", "Grey"]
Hash[colorValues.zip((0..127).each_slice(12))]
# => {"Black"=>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
# "Brown"=>[12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
# "Red"=>[24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
# "Orange"=>[36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],
# "Yellow"=>[48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
# "Green"=>[60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71],
# "Cyan"=>[72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83],
# "Blue"=>[84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95],
# "Violet"=>[96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107],
# "Pink"=>[108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119],
# "Grey"=>[120, 121, 122, 123, 124, 125, 126, 127]}

You can use zip for that:
ColorValues.zip( (0..127).each_slice(12) )

Method #zip is convenient, but it turns out that your problem is so frequent in Ruby, that I have overloaded #>> operator on Array class to perform zipping into a hash. First, install y_support gem. Then,
require 'y_support/core_ext/array' # or require 'y_support/all'
h = ColorValues >> ( 0..127 ).each_slice( 12 ) # returns a hash like in falsetru's answer
h["Black"] #=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Related

Why is there a long list of PolygonVertexIndex without any negatives (In one Fbx file)?

I made a default cylinder using blender and exported it into fbx file. When I looked at the content of the file after converting it into json, I found the field "PolygonVertexIndex" (under "Geometry"-"Objects") contains:"3, 1, 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7" and " 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60".
These consistent positive indices series confuse me a lot since I believe fbx
PolygonVertexIndex store indices in "positive, positive, negative" (when the surface is triangular) or "positive, positive, positive, negative" (when the surface is quadrilateral). And I cannot find any information regarding consistent positive indices when I googled.
The whole list looks like:
["PolygonVertexIndex", [[0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7,
9, -9, 8, 9, 11, -11, 10, 11, 13, -13, 12, 13, 15, -15, 14, 15, 17,
-17, 16, 17, 19, -19, 18, 19, 21, -21, 20, 21, 23, -23, 22, 23, 25, -25, 24, 25, 27, -27, 26, 27, 29, -29, 28, 29, 31, -31, 30, 31, 33, -33, 32, 33, 35, -35, 34, 35, 37, -37, 36, 37, 39, -39, 38, 39, 41, -41, 40, 41, 43, -43, 42, 43, 45, -45, 44, 45, 47, -47, 46, 47, 49, -49, 48, 49, 51, -51, 50, 51, 53, -53, 52, 53, 55, -55, 54, 55, 57, -57, 56, 57, 59, -59, 58, 59, 61, -61, 3, 1, 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19,
17, 15, 13, 11, 9, 7, -6, 60, 61, 63, -63, 62, 63, 1, -1, 0, 2, 4, 6,
8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42,
44, 46, 48, 50, 52, 54, 56, 58, 60, -63]], "i", []],
It can be seen that the indices are fine at the start of the list but goes weird later.
The list contains negative numbers to end a polygon and looks consistent. A polygon is not restricted to be a triangle or a quad, it can contain as many vertices as it needs.
One way to create a cylinder mesh is using quads on the sides and circles on both ends. In your case, the two long series of positive numbers are the two circles encoded as two big polygons containing 32 vertices each.

Ruby FFI read_array_of_uint8 returns different results for the same read_string

I'm trying to pinpoint a bug we're having and this is as far as I could come.
We're creating a memory pointer which is then filled by a C library:
hash_result_pointer = FFI::MemoryPointer.new(:uint8, 144)
CLibrary(hash_result_pointer)
We're testing this on a macOS High Sierra and on a Ubuntu Xenial via Docker. Here are the outcomes for both:
macOS High Sierra
p hash_result_pointer.read_string
# "\x0F\xE1\x11%\v\xC2)a#"
p hash_result_pointer.read_array_of_uint8(144)
# [15, 225, 17, 37, 11, 194, 41, 97, 64, 0, 32, 156, 191, 7, 38, 254, 119, 123, 104, 145, 2, 160, 137, 112, 10, 193, 71, 15, 3, 134, 245, 5, 6, 24, 234, 1, 100, 4, 188, 45, 158, 28, 120, 130, 42, 62, 152, 15, 1, 97, 48, 16, 0, 41, 125, 5, 4, 20, 142, 3, 10, 3, 156, 1, 65, 10, 122, 15, 52, 67, 52, 109, 38, 13, 18, 128, 47, 44, 53, 90, 23, 72, 32, 82, 56, 17, 42, 126, 57, 71, 71, 80, 9, 180, **65**, 80, 18, 9, 5, 56, 26, 17, 2, 121, 21, 52, 21, 92, 74, 19, 92, 83, 84, 45, 89, 123, 34, 117, 75, 89, 8, 7, 19, 12, 8, 5, 8, 8, 25, 1, 20, 14, 48, 3, 24, 57, 12, 32, 18, 90, 3, 35, 15, 34]
Ubuntu Xenial via Docker
p hash_result_pointer.read_string
# "\x0F\xE1\x11%\v\xC2)a#"
p hash_result_pointer.read_array_of_uint8(144)
# [15, 225, 17, 37, 11, 194, 41, 97, 64, 0, 32, 156, 191, 7, 38, 254, 119, 123, 104, 145, 2, 160, 137, 112, 10, 193, 71, 15, 3, 134, 245, 5, 6, 24, 234, 1, 100, 4, 188, 45, 158, 28, 120, 130, 42, 62, 152, 15, 1, 97, 48, 16, 0, 41, 125, 5, 4, 20, 142, 3, 10, 3, 156, 1, 65, 10, 122, 15, 52, 67, 52, 109, 38, 13, 18, 128, 47, 44, 53, 90, 23, 72, 32, 82, 56, 17, 42, 126, 57, 71, 71, 80, 9, 180, **66**, 80, 18, 9, 5, 56, 26, 17, 2, 121, 21, 52, 21, 92, 74, 19, 92, 83, 84, 45, 89, 123, 34, 117, 75, 89, 8, 7, 19, 12, 8, 5, 8, 8, 25, 1, 20, 14, 48, 3, 24, 57, 12, 32, 18, 90, 3, 35, 15, 34]
As you can see, there is one array element that differs by 1 (65 vs 66) between the two environments. I have no clue why this is happening and I also don't know what could cause this and where shall I look further. Would appreciate any tip!

Ruby 2.3.1 sort_by function change?

Here's a small ruby script:
p "ruby #{ RUBY_VERSION }p#{ RUBY_PATCHLEVEL }"
p 100.times.collect{|i| i}.sort_by{|j| j % 1}
I would have expect the same result from a version to another. In my case, it's not. Here's the results
"ruby 2.2.3p173"
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99
"ruby 2.3.1p112"
[99, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 0]
Is that normal?
Ruby doesn't guarantee you a sort order if the items are the same.
As to why the result changed between versions, this looks like a relevant change: ruby 2.3 tries to use a c-standard library provided implementation of quicksort in more cases than before.
Have a look at Is sort in Ruby stable?. The quick answer is no, it's not. What this means is that if two values are equivalent, in your case always equal to 0, you can't make any assumptions about where they go in relation to one another.

Algorithm for Iterating thru a list using pagination?

I am trying to come up with an algorithm to iterate thru a list via pagination.
I'm only interested in the initial index and the size of the "page".
For example if my list is 100 items long, and the page length is 10:
1st page: starts at 0, length 10
2nd page: starts at 11, length 10
3rd page: starts at 21, length 10
...
Nth page: starts at 90, length 10
My problem is coming up with an elegant solution that satisfies these cases:
1. list has 9 elements, page length is 10
1st page: starts at 0, length 9
2. list has 84 elements, page length is 10
1st page: starts at 0, length 10
2nd page: starts at 11, length 10
3rd page: starts at 21, length 10
...
Nth page: starts at 80, length 4
I could do this with a bunch of conditionals and the modulo operation, but I was wondering if anyone could offer a better/elegant approach to this problem.
Thanks!
There follows some code doing it the long way in Python which could be used for other languages too; followed by how it could be done in a more maintainable fashion by the intermediate Pythoneer:
>>> from pprint import pprint as pp
>>> n, perpage = 84, 10
>>> mylist = list(range(n))
>>> mylist[:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> mylist[-10:] # last ten items
[74, 75, 76, 77, 78, 79, 80, 81, 82, 83]
>>> sublists = []
>>> for i in range(n):
pagenum, offset = divmod(i, perpage)
if offset == 0:
# first in new page so create another sublist
sublists.append([])
# add item to end of last sublist
sublists[pagenum].append(i)
>>> pp(sublists)
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
[80, 81, 82, 83]]
>>> # Alternatively
>>> sublists2 = [mylist[i:i+perpage] for i in range(0, n, perpage)]
>>> pp(sublists2)
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
[80, 81, 82, 83]]
>>>

sort an array of arrays based on number of occurences in ruby

I want this integer array to be sorted in the right order based on its number of occurrences.
question = [[1, 7, 8, 9, 10, 11, 12, 19, 20, 21, 31, 32, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 81, 129, 132, 133, 134, 135, 136, 139], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 81, 129, 130, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141], [30], [77]]
question.flatten.uniq.size = 90
answer = sort_it(question)
answer = [77, 68, 8, 9, 10, 11, 12, 19, 20, 21, 31, 139, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 135, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 136, 66, 67, 7, 70, 71, 72, 73, 74, 75, 76, 1, 78, 79, 81, 129, 132, 133, 134, 45, 65, 32, 2, 3, 4, 5, 6, 13, 14, 15, 16, 17, 22, 23, 24, 25, 26, 27, 29, 33, 41, 69, 130, 137, 138, 140, 141, 30]
answer.uniq.size = 90
Here is my Ruby code:
def sort_it(actual)
join=[]
buffer = actual.dup
final = [ ]
(actual.size-2).downto(0) {|j|
join.unshift(actual.map{|i| i }.inject(:"&"))
actual.pop
}
ordered_join = join.reverse.flatten
final << ordered_join
final << buffer.flatten - ordered_join
final.flatten
end
Is this approach OK? Is there a more efficient approach?
EDIT:
As a tribute to tokland and niklas, edited the answer which was in the wrong order before.
Thanks!
Use group_by:
question.flatten.group_by{|x| x}.sort_by{|k, v| -v.size}.map(&:first)
Answer with sort_by{|k, v| -v.size} calls v.size every time elements are compared. More effective solution:
question.flatten.group_by(&:to_i).map{|k,v| [k, -v.size]}.sort_by(&:last).map(&:first)
Though size of array is easy to get, it is unnecessary expense (O(sorting algorithm) instead of O(n)), and this idiom is good to remember anyway for more expensive operations

Resources