Help me shuffle some YAML data in Ruby?
January 4, 2022 10:54 PM   Subscribe

I'm trying to learn some Ruby while doing this hobby scripting project. Get some YAML data, walk through categories within the data, shuffle the category children, tada, output it. The YAML validates. But the Ruby is driving me crazy. Code, data, and desired output example inside...

Here's the ruby script:
#!/usr/bin/ruby
# ruby is version 2.5.1p57

require 'yaml'
require 'pp' # Pretty printer

# Open yaml File
superstuff = YAML.load_file('/home/username/data/sample.yaml')

# Iterate through what's there, and hopefully randomly sample some data.
superstuff.each do |superstuff_element|
    superstuff_element.each do |superstuff_subelement|
        puts "- Category: #{superstuff_subelement[0]}"
        # Why did I just have to specify 0 above?
        superstuff_subelement.each do |item|
            # I sant to do:
            # puts item.sample(3)
            # And ta-dah, three random subelements are chosen, like random superpowers.
            # but that doesn't work bc it says it's a string? And why am I three levels deep here? 
            puts item.class
            # See? It says I'm working with a string.
            puts item.class
            # until just now, NOW it says it's an array!???
            # Those puts output this: 
            # - String
            # - string
            # - Array
            # - Array
            # ...??? I give up for now
        end
    end
end
Here's the YAML. It has validated since the start, but it didn't really start to work at first until I added colons at the end of the major category titles.

Then I added colons after everything, and things got more confusing and made me wonder if I really want YAML for such a simple project. IDK what Ruby is wanting from my YAML. Also IDK what would be a simpler alternative to YAML for this.

- APTITUDES: 
    - Fighting
    - Presence
    - Aircraft
    - Occultism
    - Technology
    - Business
    - Performance
    - Vehicles
- POWERS: 
    - Absorption
    - Insubstantiality
    - Summoning
    - Life Support
    - Super Leap
    - Burrowing
    - Mental Blast
    - Super Running
    - Communication
    - Mental Paralysis
Example of the Markdown output desired:

- APTITUDES
    - Business
    - Occultism
    - Fighting
- POWERS
    - Life Support
    - Communication
    - Burrowing
I could have done this in bash without the YAML, but I thought I'd learn some YAML and Ruby this time. Any info, clue-bats, or tips welcome! TIA.
posted by circular to Computers & Internet (6 answers total) 1 user marked this as a favorite
 
Best answer: YAML is probably a good match for this project, but the structure is a bit off.

At the top level of the YAML data you have a list, and each item in the list is a hash with one key and a value that is a list/array. That's making the data more confusing than it needs to be, it's the equivalent of this Ruby data:

[{"APTITUDES" => ["Fighting", "Presence"]}, {"POWERS" => [ .. etc ...]}]


You can flatten the data so the top level is just one hash with multiple keys:
APTITUDES: 
  - Fighting
  - Presence
  - Aircraft
  - Occultism
  - Technology
  - Business
  - Performance
  - Vehicles
POWERS: 
  - Absorption
  - Insubstantiality
  - Summoning
  - Life Support
  - Super Leap
  - Burrowing
  - Mental Blast
  - Super Running
  - Communication
  - Mental Paralysis
Indentation is 2 characters.

That would give a structure of {"APTITUDES" => ["Fighting", "Presence"], "POWERS" => [ .. etc ...]} that is probably what you want.
posted by BinaryApe at 12:51 AM on January 5, 2022


Best answer: Okay, I’m not a YAML expert but I believe your current syntax is giving you the following data structure:

The YAML file contains a sequence of mappings, where each of your categories is a mapping. Each mapping contains one key/value pair, where the key is the category name and the value is the sequence of qualities.

Therefore:

- superstuff is an array of mappings.
- superstuff_sublement is a MAPPING with a single key/value pair, which is why you have to do superstuff_subelement[0] to get the category name.
- item is… okay, this is where my YAML/Ruby knowledge breaks down a bit but I believe what’s happening when you try to iterate through a mapping is that you’re iterating through a list that contains each key and each value as elements. Since the keys are strings and the values are arrays that’s why you’re seeing both types.


Someone with more YAML knowledge than me can probably tell you a better way to represent your data using YAML, but in the meantime, this is a very helpful reference if you’re familiar with JSON.

On preview, BinaryApe’s suggestion looks good.
posted by mekily at 1:03 AM on January 5, 2022 [1 favorite]


Best answer: Another thing: .each is great for arrays but with hashes it will return both the key and the value inside an array, which is confusing. There's a special method for dealing with key/values from hashes - each_pair
#!/usr/bin/ruby
require 'yaml'

superstuff = YAML.load_file('sample.yaml')

superstuff.each_pair do |key, list_of_things|

  puts "- #{key}"

  list_of_things.each do |thing|

    puts "  - #{thing}"

  end

end
(this assumes you've used my tweaked YAML from the earlier reply)
posted by BinaryApe at 1:04 AM on January 5, 2022


Best answer: I poked around at it, incorporated BinaryApe's suggestion for yaml structure, got it working, and saved it as a gist on GitHub to avoid markup here. I left in debugging statements where I paused to check data structures.

I prefer yaml to json; it allows for comments, and it's easier to write by hand, as opposed to json's strictness about commas and quotes.
posted by Pronoiac at 3:19 AM on January 5, 2022 [1 favorite]


Response by poster: Thanks everyone, that was really helpful. I updated the YAML structure and then ended up with this code after the YAML load_file call:

superstuff.each_pair do |key, list_of_things|
    puts "- #{key}"
    list_of_things.shuffle!
    counter = 1
    list_of_things.each do |thing|
        if counter < 4
            puts "    - #{thing}"
            counter += 1
        else
            break
        end
    end
end
The updated YAML format changes were: Remove minus sign from first level items; change indentation to 2 characters.

The output is now suitably superheroic! I appreciate all the help.
posted by circular at 8:44 AM on January 5, 2022 [1 favorite]


Glad it worked!

.first() can save you from using the if/else/end to get the first 4 items:
list_of_things.shuffle.first(4).each { |thing| puts "    - #{thing}" }
{} in this situation are not a hash, they're another way to write do ... end for a block of code
posted by BinaryApe at 10:02 PM on January 5, 2022 [1 favorite]


« Older How close to my summons date can I postpone jury...   |   How to transfer data from old android phone to new... Newer »
This thread is closed to new comments.