-
Notifications
You must be signed in to change notification settings - Fork 0
Added code for sub-expressions and order of operations #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jakemh
wants to merge
1
commit into
master
Choose a base branch
from
test_branch
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| require_relative 'data_main' | ||
| require_relative 'parse' | ||
|
|
||
| module TagExpressions | ||
| module Order | ||
| ACCUMULATE = 100 | ||
| SKIP = 0 | ||
| def self.data | ||
| @@data = TagExpressions::Data::tags | ||
| end | ||
|
|
||
| def self.default_options | ||
| {:until_up_to => 0, | ||
| :until_less_than_or_equal_to => 0, | ||
| :skip => SKIP, | ||
| :accumulate => ACCUMULATE, | ||
| :starting_at => -1} | ||
| end | ||
|
|
||
| # use following API: | ||
| # :accumulate => accumulate this many items | ||
| # :skip => do not accumulate until this many matches are found | ||
| # :until_less_than => accumulate until PAST this value | ||
| # :until_up_to => accumulate UP TO this value | ||
| # :must_accumulate => return false if cannot accumulate this many values | ||
| def self.update_return_with_options(return_list, ref, count, opt = options) | ||
|
|
||
| return_list_orig_length = return_list.length | ||
|
|
||
| if return_list.length < opt[:accumulate] and ref >= opt[:until_up_to] | ||
| if (return_list[-1] != ref or opt[:include_duplicates]) | ||
| count[0] += 1 | ||
| if count[0] > opt[:skip] | ||
| return_list.push(ref) if ref != nil | ||
| if opt.has_key? :until_less_than_or_equal_to and opt[:until_less_than_or_equal_to] >= ref | ||
| return false | ||
| end | ||
| end | ||
| end | ||
| else | ||
| return false | ||
| end | ||
| if opt.has_key?(:must_accumulate) and (return_list.length - return_list_orig_length) < opt[:must_accumulate] | ||
| return false | ||
| end | ||
| true | ||
| end | ||
|
|
||
| def self.union(set0, set1, opt = {}) | ||
| opt = default_options.merge(opt) | ||
|
|
||
| count = [0] | ||
| return_list = [] | ||
| set0_prev_length = 0 | ||
| i = [-1, -1] | ||
|
|
||
| set0_temp = set0; set1_temp = set1 | ||
| set0_proc = set0; set1_proc = set1 #default initialization | ||
| set0_proc = lambda{ |opt| base(set0_temp, opt).reverse} if !set0.kind_of? Proc | ||
| set1_proc = lambda{ |opt| base(set1_temp, opt).reverse} if !set1.kind_of? Proc | ||
|
|
||
| # inner option is to allow union to convert starting_at value to the accumulate value | ||
| # when it is not the outer call | ||
| if opt.delete(:inner) | ||
| opt[:skip] = opt[:starting_at] * -1 - 1 | ||
| end | ||
|
|
||
| while return_list.length < opt[:accumulate] and (i[1] > -ACCUMULATE or i[0] > -ACCUMULATE) | ||
| ref1 = set0_proc.call({:accumulate => 1, :starting_at => i[0], :inner => true}) | ||
| ref2 = set1_proc.call({:accumulate => 1, :starting_at => i[1], :inner => true}) | ||
| refs = [ref1, ref2] | ||
|
|
||
| if ref1[0] == nil | ||
| ref = ref2[0] | ||
| i[1] -= 1 | ||
| elsif ref2[0] == nil | ||
| ref = ref1[0] | ||
| i[0] -= 1 | ||
|
|
||
| elsif ref1[0] > ref2[0] | ||
| ref = ref1[0] | ||
| i[0] -= 1 | ||
| else | ||
| ref = ref2[0] | ||
| i[1] -= 1 | ||
| end | ||
|
|
||
| if ref == nil or update_return_with_options(return_list, ref, count, opt) == false | ||
| break | ||
| end | ||
| end | ||
| return return_list | ||
| end | ||
|
|
||
| # id is added to list if set1 val is not equal to reference val | ||
| def self.difference(set0, set1, opt = {}) | ||
| return deduction(set0, set1, opt) do |set0, set1| | ||
| true if set0[0] != set1[0] | ||
| end | ||
| end | ||
|
|
||
| # id is added to list if set1 val is equal to reference val | ||
| def self.intersection(set0, set1, opt = {}) | ||
| return deduction(set0, set1, opt) do |set0, set1| | ||
| true if set0[0] == set1[0] | ||
| end | ||
| end | ||
|
|
||
|
|
||
| # iterate reference list by one | ||
| # advance the set1 until it is less than or equal to the first set | ||
| # yield returns condition | ||
| def self.deduction(set0, set1, opt = {}) | ||
| #subtract set1 from set0 | ||
| opt = default_options.merge(opt) | ||
| count = [0] | ||
| return_list = [] | ||
| set0_prev_length = 0 | ||
| i = [opt[:starting_at] || -1, -1] | ||
|
|
||
| set0_temp = set0; set1_temp = set1 | ||
| set0_proc = set0; set1_proc = set1 | ||
| set0_proc = lambda{ |opt| base(set0_temp, opt).reverse} if !set0.kind_of? Proc | ||
| set1_proc = lambda{ |opt| base(set1_temp, opt).reverse} if !set1.kind_of? Proc | ||
| while return_list.length < (opt[:accumulate]) and i[0] > -ACCUMULATE | ||
| set0 = set0_proc.call({:accumulate => 1, :starting_at => i[0], :inner => true}) | ||
| if set0[0] | ||
| set1 = set1_proc.call({:until_less_than_or_equal_to => set0[0], :inner => true}) | ||
|
|
||
| if yield set0, set1 | ||
| break if update_return_with_options(return_list, set0[0], count, opt) == false | ||
| end | ||
|
|
||
| i[0] -= 1 | ||
|
|
||
| else break | ||
| end | ||
| end | ||
| return return_list | ||
| end | ||
|
|
||
| # iterate a base case (reference to a list from db) that is not associated with a set operator | ||
| def self.base(set0, opt = {}) | ||
| opt = default_options.merge(opt) | ||
| count = [0] | ||
| return_list = [] | ||
| i = [opt[:starting_at] || -1, -1] | ||
| while return_list.length < (opt[:accumulate]) and i[0] > -ACCUMULATE and set0[i[0]] != nil | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A nice substitute for edit: never mind, forgot we weren't in rails. |
||
| break if update_return_with_options(return_list, set0[i[0]], count, opt) == false | ||
| i[0] -= 1 | ||
| end | ||
| return return_list | ||
| end | ||
| end | ||
| end | ||
|
|
||
|
|
||
|
|
||
| ##### some example code ###### | ||
|
|
||
| ruby = TagExpressions::Order.data["Ruby"]; # p ruby.reverse | ||
| adult = TagExpressions::Order.data["Adult"]; # p adult.reverse | ||
| # p (ruby + adult).sort.uniq.reverse | ||
| air = TagExpressions::Order.data["Air"]; # p air.reverse | ||
| aeroplane = TagExpressions::Order.data["Aeroplane"]; #p aeroplane.reverse | ||
| album = TagExpressions::Order.data["Album"];# p album.reverse | ||
| # p (ruby + adult).sort.uniq.reverse | ||
|
|
||
| room = TagExpressions::Order.data["Room"]; # p room.reverse | ||
| # p (aeroplane & air).uniq.sort.reverse | ||
| # puts | ||
| # ruby.reverse | ||
| # adult.reverse | ||
|
|
||
| # puts | ||
|
|
||
| air_plus_aeroplane = lambda{ |opt| TagExpressions::Order.union(air, aeroplane, opt).reverse } | ||
| air_and_aeroplane = lambda{ |opt| TagExpressions::Order.intersection(air, aeroplane, opt).reverse } | ||
| ruby_plus_adult = lambda{ |opt| TagExpressions::Order.union(ruby, air_and_aeroplane, opt).reverse } | ||
|
|
||
| ruby_and_adult = lambda{ |opt| TagExpressions::Order.intersection_base(ruby, air_and_aeroplane, opt).reverse } | ||
| ruby_plus_adult__and__air_plus_aeroplane = lambda{ |opt| TagExpressions::Order.intersection(ruby_plus_adult, air_plus_aeroplane, opt).reverse } | ||
|
|
||
| # p TagExpressions::Order.difference(ruby_plus_adult, air_plus_aeroplane ) | ||
| # p TagExpressions::Order.union(ruby, adult) | ||
| # p ((ruby + adult) - (air + aeroplane)).sort.uniq.reverse | ||
| # p ((ruby + adult)).sort.uniq.reverse | ||
| # p TagExpressions::Order.intersection(ruby_plus_adult, air_plus_aeroplane) | ||
| # p ((ruby - adult) - (ruby & adult)).sort.reverse.uniq | ||
|
|
||
| # p "STARTING VERY VERY SLOW EVALUATION" | ||
| # p TagExpressions::Order.difference(ruby_plus_adult, ruby_plus_adult__and__air_plus_aeroplane) | ||
| # p ((ruby + (air & aeroplane)) - ((ruby + (air & aeroplane)) & (air + aeroplane))).sort{ |a,b| b <=> a }.uniq | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| require_relative 'spec_helper' | ||
| require_relative 'models' | ||
|
|
||
| describe "General Order of Operation Tag Evaluation" do | ||
|
|
||
| lue = lambda{ TagExpressions.data["LUE"] } | ||
| programming = lambda{ TagExpressions.data["Programming"] } | ||
| java = lambda{ TagExpressions.data["Java"] } | ||
| heartbreaks = lambda{ TagExpressions.data["Heartbreaks"] } | ||
| current_events = lambda{ TagExpressions.data["current_events"] } | ||
| ruby = lambda{ TagExpressions.data["Ruby"] } | ||
| sports = lambda{ TagExpressions.data["Sports"] } | ||
|
|
||
|
|
||
| before(:each) do | ||
| TagExpressions::Data::reset_tags | ||
| TagExpressions::Data::DB::setup | ||
| end | ||
|
|
||
| after(:each) do | ||
| TagExpressions::Data::Topic.delete_all | ||
| TagExpressions::Data::Tag.delete_all | ||
| end | ||
|
|
||
| it 'should allow difference' do | ||
| topic1 = Topic::create_with_tags("LUE, Programming") | ||
| topic2 = Topic::create_with_tags("LUE, Programming") | ||
| topic3 = Topic::create_with_tags("LUE, Java") | ||
| Order.difference(lue.(), programming.()).should == (lue.() - programming.()) | ||
| end | ||
|
|
||
| it 'should allow union' do | ||
| topic1 = Topic::create_with_tags("LUE") | ||
| topic2 = Topic::create_with_tags("Programming") | ||
| topic3 = Topic::create_with_tags("Java") | ||
|
|
||
| Order.union(Order.union(lue.(), programming.()).reverse, java.()).should == (lue.() + programming.() + java.()).sort.reverse | ||
|
|
||
| end | ||
|
|
||
| it 'should allow intersection' do | ||
| topic1 = Topic::create_with_tags("LUE, Programming, Java") | ||
| topic2 = Topic::create_with_tags("Programming, Java") | ||
| topic3 = Topic::create_with_tags("Java, Programming") | ||
|
|
||
| Order.intersection(Order.intersection(lue.(), programming.()), java.()).should == (lue.() & programming.() & java.()) | ||
| end | ||
|
|
||
| it 'should handle all at once' do | ||
| topic1 = Topic::create_with_tags("LUE, Programming") | ||
| topic2 = Topic::create_with_tags("Java") | ||
| topic3 = Topic::create_with_tags("Programming, LUE, Heartbreaks") | ||
| topic4 = Topic::create_with_tags("Java, Programming, LUE, Heartbreaks") | ||
|
|
||
| lue_plus_java = Order.union(lue.(), java.()).reverse | ||
| lue_plus_java_minus_heartbreaks = Order.difference(lue_plus_java, heartbreaks.()).reverse | ||
|
|
||
| Order.intersection(programming.(), lue_plus_java_minus_heartbreaks).should == programming.() & lue.() + java.() - heartbreaks.() | ||
| end | ||
|
|
||
| it 'should handle complex cases' do | ||
| topic1 = Topic::create_with_tags("LUE, Programming") | ||
| topic2 = Topic::create_with_tags("Java, Programming") | ||
| topic3 = Topic::create_with_tags("Programming, LUE, Heartbreaks") | ||
| topic4 = Topic::create_with_tags("Java, Programming, LUE, Heartbreaks") | ||
| topic5 = Topic::create_with_tags("Current Events, Programming, Java, Ruby") | ||
| topic6 = Topic::create_with_tags("Ruby, Sports, Heartbreaks, Programming") | ||
| topic7 = Topic::create_with_tags("LUE") | ||
| topic8 = Topic::create_with_tags("Java, Current_Events, LUE, Heartbreaks") | ||
| topic9 = Topic::create_with_tags("LUE, Current Events, Programming, Java, Ruby, Heartbreaks") | ||
| topic10 = Topic::create_with_tags("LUE, Current Events, Programming, Java, Ruby") | ||
|
|
||
| lue__plus__java = Order.union(lue.(), java.()).reverse | ||
| lue__plus__java__minus__current_events = Order.difference(lue__plus__java, current_events.()).reverse | ||
| programming__plus__ruby = Order.union(programming.(), ruby.()).reverse | ||
| heartbreaks__minus__sports = Order.difference(heartbreaks.(), sports.()).reverse | ||
|
|
||
| Order.intersection(lue__plus__java__minus__current_events, | ||
| Order.intersection(programming__plus__ruby, heartbreaks__minus__sports).reverse).should == | ||
| (lue.() + java.() - current_events.() & programming.() + ruby.() & heartbreaks.() - sports.()).sort.reverse | ||
| end | ||
| # it 'should be able to restrict accumulation' do | ||
| # topic1 = Topic::create_with_tags("Programming") | ||
| # topic2 = Topic::create_with_tags("Programming") | ||
| # topic3 = Topic::create_with_tags("Programming") | ||
| # topic4 = Topic::create_with_tags("Programming") | ||
| # topic5 = Topic::create_with_tags("Movies") | ||
| # topic6 = Topic::create_with_tags("Movies") | ||
| # topic7 = Topic::create_with_tags("Movies") | ||
|
|
||
| # options = {:accumulate => 5} | ||
| # result = Expressions::evaluate("Programming + Movies", options) | ||
| # result.should_not == [topic7, topic6, topic5, topic4, topic3, topic2, topic1] | ||
| # result.should == [topic7, topic6, topic5, topic4, topic3] | ||
|
|
||
| # end | ||
|
|
||
| # it 'should be able to skip results' do | ||
| # topic1 = Topic::create_with_tags("LUE") | ||
| # topic2 = Topic::create_with_tags("Programming") | ||
| # topic3 = Topic::create_with_tags("Java") | ||
| # topic4 = Topic::create_with_tags("Current_Events") | ||
| # topic5 = Topic::create_with_tags("Ruby") | ||
| # topic6 = Topic::create_with_tags("Movies") | ||
| # topic7 = Topic::create_with_tags("Pets") | ||
|
|
||
| # options = {:skip => 2} | ||
| # result = Expressions::evaluate("LUE + Programming + Java + Current_Events + Ruby + Movies + Pets", options) | ||
| # result.should_not == [topic7, topic6, topic5, topic4, topic3, topic2, topic1] | ||
| # result.should == [topic5, topic4, topic3, topic2, topic1] | ||
|
|
||
| # end | ||
| end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on abstracting some of these more complicated checks into private helper methods? That usually helps as a reminder of what the check is actually doing, and avoid nested
ifs.e.g.:
then this block of code can just be:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(feel free to do this with some of the other
ifstatements in the code as well... it makes everything more readable especially when you go back later imo)