Delete duplicate string characters in a array

Hello everybody. :smiley:

The “uniq” method is a very good way to remove duplicate string characters in a “Array”.

I find myself in a situation where the method does not work.

def creer_et_remplacer_materiaux_habillages(ents)
  mod = Sketchup.active_model
  mat = mod.materials
  mod.start_operation('mataddpn', true)  
    ents.grep(Sketchup::ComponentInstance).each do |e|
	  ["Exploser"].each do |ajouter|
		if e.definition.name =~ /#{ajouter}/
		  if e.material.name.include?"PN"
			mathab = []
			mathab << "#{e.material.name}-#{e.definition.name}"
			mathab2 = mathab.uniq
			puts mathab2
			path = File.join(Sketchup.find_support_file('plugins'), 
		      "TNT_ClickCuisine2/Materials/FACADES/"
			)
            for th in mathab2
			  mathab2.each do |mh|
		      mattex = "4-#{mh.split("-")[0]}.jpg"
              math = mat.add(mh)			
			  math = mat[th]
			  math.texture = path + mattex
			  e.material = math
              end			  
		    end
	      end
        end
      end		
      creer_et_remplacer_materiaux_habillages(e.definition.entities)		
    end
  mod.commit_operation
end

  mod = Sketchup.active_model
  ent = mod.active_entities
  sel = mod.selection
  instances = sel.grep(Sketchup::ComponentInstance)
  creer_et_remplacer_materiaux_habillages(instances) 

Here is the list obtained with the puts:

PN2-Exploser60
PN1-Exploser20#1
PN1-Exploser40
PN1-Exploser40
PN1-Exploser01PN
PN2-Exploser02PN
PN3-Exploser03PN
PN4-Exploser04PN
PN5-Exploser05PN
PN6-Exploser06PN
PN1-Exploser01PN
PN2-Exploser02PN
PN3-Exploser03PN
PN4-Exploser04PN
PN5-Exploser05PN
PN6-Exploser06PN
true

You will notice that there are several duplicates in this list.

Do you know where the problem comes from?

Thank you

You are setting the mathab = [] in the wrong part - too early…

Also,just use mathab.uniq! and/or mathab2.uniq! to remove any duplicate names before processing the list[s] ?

Hello TIG

The “mathab” array must exist before the “for” loop that uses it.
So I do not understand your remark!

Can you bring more details?

Thank you

If you used correct indentation it would maybe be obvious — and meaningful variable names improve readability and avoid bugs.

As far as I see, mathab is created as an array and immediately only one element is added. It never contains more than one element so it is always unique.

If there is still a problem in your code then you need to think again what you print to the console for debugging. Why not e.definition.name and mathab.inspect?

1 Like

It’s very hard to figure out what is the intended behavior of the code when it’s written partly in French. I have no idea what “mathab”, “mathab2”, “math”, “mataddpn” or “creer_et_remplacer_materiaux_habillages” is supposed to mean. Also in my experience abbreviation such as mod, mat, sel and e, although common, makes difficult to read code. If you instead write the full word model, materials, selection and entity it is much easier to understand what it refers to.

As Aerilius has already said correct indentation would also help a lot.

Computers pair up if, do, unless and certain other keywords with a corresponding end keyword to understand the hierarchy of the code. Humans rely on indentation. There is no way to read this code without first copying it into a code editor and fix the indentation.

See this example:

def method_name
  # This code runs whenever the method method_name is called.

  if some_statement
    # This code runs whenever the method is called AND some_statement evaluates to true.
  end # This ends the if.

  5.times do
    # This code runs 5 times whenever the method is called.

    if some_other statement
      # This code runs a maximum of 5 times and only while some_other_statement evaluates to true.
    end # this ends the if.
  end # This ends the do loop.
end # This ends the method definition.

If you indent the code arbitrarily it’s impossible to understand what code runs when and why, without carefully pairing up the keywords yourself.

def method_name
  # This code runs whenever the method method_name is called.

  if some_statement
    # This code runs whenever the method is called AND some_statement evaluates to true.
    end # Okay?

    5.times do
    # So when does this run? Does it depend on some_statement? How many times does it run?

    if some_other statement
      # Headache.
    end
    end # What code block ends here?

# Wait, what? Why is there an indentation level missing here?

end
2 Likes

I often check my code with the “puts array”.

I just noticed from your remarks that the construction of a table is more readable with the method “puts array.inspect

Here is an example of what I get:

["PN1-Exploser01PN"]
["PN2-Exploser02PN"]
["PN3-Exploser03PN"]
["PN4-Exploser04PN"]
["PN5-Exploser01PN"]

Can we convert it like that?

["PN1-Exploser01PN","PN2-Exploser02PN","PN3-Exploser03PN","PN4-Exploser04PN","PN5-Exploser01PN"]

Thank you.

To go back to the original question, the Array is made unique but that doesn’t make any difference as it only contains one value. To get a bigger Array containing the data from several component instances, the Array needs to be created before you iterate over the entities collection. Then you can uniq it after the loop. All this gets much clearer with correct indentation.

1 Like

I followed your instructions but nothing changes eneroth3!

All my “end” are aligned with the “if”, “do”, “for” so I do not understand you.

Can you copy paste my code with your vision of a good indentation?

This will be the best way for me to understand you.

I prefer to use abbreviations of type salt, mat, mod because it saves lines and not to confuse variables with methods.

cordially

David

Sorry, it is (almost) perfectly indented, but Firefox renders the indentation with a wrong tabstop width. Use consistently either only tabstops or only spaces but don’t mix them. Take also a look in your text editor’s settings and set it to use tabstop width = 2 and to replace tabstops by spaces.

Most important is consistency. Methods usually have parentheses, these can be used to distinguish them from variables. If you abbreviate common variables that is ok as long as there is a consistent scheme behind to avoid misunderstandings (see “math”). Abbreviated code does not run faster (it’s not assembler, and the compiler/interpreter is responsible for optimizations). Code must be good for humans.


Before we can fix the code, we (and you too) need to have an idea what the fixed code should achieve. There are two redundant for/each loops but I don’t think you want to do a square iteration (all combinations of mathab strings with other mathab strings). There is recursion that calls a UI method (nested start_operation!), but UI code and domain code should be separated (at least separate methods).

:arrow_right: Before drafting again an algorithm, you need a clear specification. Can you describe in phrases what it is supposed to achieve?

2 Likes

I didn’t notice tabs and spaces were mixed! Mixing is no good idea as there is no universally defined tabstop. Some systems/editors use 2, some 4 and some 8. Sticking to spaces only is the safest and most editors have a setting for this.

Regarding variable naming using abbreviation doesn’t help separate to them from methods as methods could also be named using abbreviation. Also, there are no methods named model, selection or material in the namespace used, so there is no risk of a mix-up.

Here’s my edited code with the following changes:

  • indentation
  • using full words instead of abbreviation (where i understand what it is supposed to mean),
  • using placeholder variable names var1, var2 etc where I don’t understand what is is supposed to mean,
  • using placeholder variable and method names for everything in French and
  • using placeholder operation name.
def french1(entities)
  model = Sketchup.active_model
  materials = model.materials
  model.start_operation('Operator Name', true)
  entities.grep(Sketchup::ComponentInstance).each do |entity|
    ["Exploser"].each do |french2|
      if entity.definition.name =~ /#{french2}/
        if entity.material.name.include?("PN")
          var1 = []
          var1 << "#{entity.material.name}-#{entity.definition.name}"
          var2 = var1.uniq
          puts var2
          path = File.join(
            Sketchup.find_support_file('plugins'),
            "TNT_ClickCuisine2/Materials/FACADES/"
          )
          for var3 in var2
            var2.each do |var4|
              mattex = "4-#{var4.split("-")[0]}.jpg"
              var5 = materials.add(var4)
              var5 = materials[var3]
              var5.texture = path + mattex
              entity.material = var5
            end
          end
        end
      end
    end
    french1(entity.definition.entities)
  end
  model.commit_operation
end

model = Sketchup.active_model
selection = model.selection
instances = selection.grep(Sketchup::ComponentInstance)
french1(instances)

You can change all the placeholder names to meaningful names that tell what a method does and what a variable represents. I just also noticed that mattex probably should be changed to file_name as it seems to represent a file name (not a material or texture or whatever mattex even refers to).

It is very hard for us here in the forum to understand what the code is even supposed to do when we don’t understand what variables are supposed to represent. It will also be equally hard for you to understand these names if you need to make any changes to the code in a few years from now when you no longer have it fresh in memory.

I’m glad to hear you say :slight_smile:

But almost perfect is not enough, it must be perfect!

Thank you eneroth3, for the effort you made so that I can understand you.
Indeed the indentation was not perfect and I would use your example for the next methods.

I agree with you, writing explicit variables undoubtedly improves understanding of the code.

However it is easier for me to write variables in French and my codes will always be difficult to read for you eneroth3.

Is it really insurmountable for you to understand a code with variables in another language?

In your example, “var1”, “var2”, “var3”, is not simpler than “mathab”, “mathab2”, “mattex” …

To conclude,
I start with the principle that we must always question ourselves to make progress even if our experience seems long and wonderful.

Thank you all for your help.

David

1 Like

They are equally hard to understand, but the left example is honest about being incomprehensible while the right is just cryptic. That said var1, var2 and var3 should of course be changed into something that explains what they are.

mathab doesn’t in any way tell what the variable represents. It looks like it is related to math or materials but it could just as well be a phone number or the distance between the earth and the moon. You just can’t tell from looking at it.


I explained this to Dave in his other thread, **`23` days ago**, along with a pretty picture ...

I explained this to Dave in his other thread, 23 days ago, along with a pretty picture …

3 posts were split to a new topic: Abbreviations vs full words in variable names

Possible specification:

Given a set of entities, find all contained component instances (recursively). If the instance's definition and material match a pattern, replace the instance material by a new material that is unique to the component definition and uses a texture image from an accompagnied resources folder.

To your approach:

(I guess) you actually wanted to avoid creating duplicate materials. Your approach was to iterate over entities to add material names (tuples of material&definition) to `mathab`. Then make the names unique, then create a material for each name, then iterate again over the entities to assign the material. This requires one loop after another, not nested loops.

An alternative strategy for avoiding duplication is to check existence (with include?) instead of using unique.

# Define specific strings and numbers as constants so that they are not spread over the code.
COMPONENT_PATTERNS = [/Exploser/]
MATERIAL_PATTERN = 'PN'
PATH_FACADES = File.join(Sketchup.find_support_file('plugins'), 'TNT_ClickCuisine2/Materials/FACADES/')

def do_create_and_replace_cover_materials_operation(entities)
  mod = Sketchup.active_model
  mod.start_operation('mataddpn', true)
  create_and_replace_cover_materials(entities)
  mod.commit_operation
end

# Make every component instance's material unique for the component definition.
def create_and_replace_cover_materials(entities, materials_cache={}, visited_definitions=[])
  entities.grep(Sketchup::ComponentInstance).each do |e|
    if COMPONENT_PATTERNS.any?{ |pattern| e.definition.name =~ pattern } # Run the block below only once if any of the patterns matches, not again for every match.
      if e.material.name.include?(MATERIAL_PATTERN) # Use parentheses
        # Make the instance's material unique for this definition.
        # To avoid duplicates, materials are created only once and reused from a cache. 
        # They are identified by the pair of instance material and definition.
        if !materials_cache.include?([e.material, e.definition])
          materials_cache[[e.material, e.definition]] = create_cover_material(e.material, e.definition)
        end
        e.material = materials_cache[[e.material, e.definition]]
      end
    end
    # Recursion over nested components.
    # Run the recursion only once per definition, not again for every instance that has the same definition.
    if !visited_definitions.include?(e.definition)
      visited_definitions << e.definition
      create_and_replace_cover_materials(e.definition.entities, materials_cache, visited_definitions)
    end
  end
end

# Create a single new material based on an existing material and a definition.
def create_cover_material(instance_material, definition)
  material_name = "#{instance_material.name}-#{definition.name}"
  material = instance_material.model.materials.add(material_name) # Entities have a reference to their model, no need to pass a model reference as parameter, no need to assume it is the active model.
  texture_filename = "4-#{instance_material.name}.jpg" # String splitting (or other operations) require assumptions on how the string is constructed, assumptions cause bugs.
  material.texture = File.join(PATH_FACADES, texture_filename) # Use File.join, not +
  return material
end

mod = Sketchup.active_model
ent = mod.active_entities
sel = mod.selection
instances = sel.grep(Sketchup::ComponentInstance)
do_create_and_replace_cover_materials_operation(instances) 
1 Like

Aerilius you have fully understood the purpose of my code. :sunglasses:

You have understood it so well that your solution works without creating duplicate materials!

For a person like me who is studying ruby ​​recently, your code is a concentrate of tips and solutions.

Using your code would be too easy!

I must achieve my research with my personal approach.

I confirm again that the use of parameters or variables with method names must be avoided!

selection = methods
sel = variables
s = settings

A code become very confused when the 3 are named selection!

Sorry “eneroth3” this time I will not follow your advice.

_

To return to my basic problem:

The error comes from the fact that the entities are nested.

Then the “<<” method fails to create a large array with all the definition names.

Instead, it creates multiple array with a single definition name for each of them.

I think there must be an alternative that would allow my approach to succeed as well as the Aerilius approach.

Sorry Dan, I have not found the time yet to apply your notepad ++ settings.
I was far from imagining that this created such great difficulties.

Both SketchUp forums (here and SketchUcation) do not format code well when you paste in text with embedded TAB characters. The Discourse forum engine (used here) has an 8 character default for TABs. So it moves all your code way over to the right, and causes unneeded horizontal scroll bars (and scrolling by those reading the code.)

In your solution “Aerilius”, you used constants is not it dangerous?

Imagine that another plugin uses the same constant name, it can lead to errors or I’m wrong?

It is assumed that any sample code given in the forum, will be wrapped within YOUR plugin sub-module namespace, that should be wrapped within YOUR author/company toplevel module namespace.

So, NO there will be no clashes between constants defined within YOUR namespaces (class or modules) and other people’s constants defined within THEIR namespaces (ie, classes or modules.)

Custom classes should only be defined at the toplevel by Ruby, (and in rare instances the SketchUp API,) and are meant for access by ALL extensions.

1 Like