I would like to efficiently generate a unique list of combinations of numbers based on a starting list of numbers.
example start list = [1,2,3,4,5] but the algorithm should work for [1,2,3...n]
result =
[1],[2],[3],[4],[5]
[1,2],[1,3],[1,4],[1,5]
[1,2,3],[1,2,4],[1,2,5]
[1,3,4],[1,3,5],[1,4,5]
[2,3],[2,4],[2,5]
[2,3,4],[2,3,5]
[3,4],[3,5]
[3,4,5]
[4,5]
Note. I don't want duplicate combinations, although I could live with them, eg in the above example I don't really need the combination [1,3,2] because it already present as [1,2,3]
Just count 0 to 2^n - 1 and print the numbers according to the binary representation of your count. a 1 means you print that number and a 0 means you don't. Example:
set is {1, 2, 3, 4, 5}
count from 0 to 31:
count = 00000 => print {}
count = 00001 => print {1} (or 5, the order in which you do it really shouldn't matter)
count = 00010 => print {2}
00011 => print {1, 2}
00100 => print {3}
00101 => print {1, 3}
00110 => print {2, 3}
00111 => print {1, 2, 3}
...
11111 => print {1, 2, 3, 4, 5}
There is a name for what you're asking. It's called the power set.
Googling for "power set algorithm" led me to this recursive solution.
Ruby Algorithm
def powerset!(set)
return [set] if set.empty?
p = set.pop
subset = powerset!(set)
subset | subset.map { |x| x | [p] }
end
Power Set Intuition
If S = (a, b, c) then the powerset(S) is the set of all subsets
powerset(S) = {(), (a), (b), (c), (a,b), (a,c), (b,c), (a,b,c)}
The first "trick" is to try to define recursively.
What would be a stop state?
S = () has what powerset(S)?
How get to it?
Reduce set by one element
Consider taking an element out - in the above example, take out {c}
S = (a,b) then powerset(S) = {(), (a), (b), (a,b)}
What is missing?
powerset(S) = {(c), (a,c), (b,c), (a,b,c)}
hmmm
Notice any similarities? Look again...
powerset(S) = {(), (a), (b), (c), (a,b), (a,c), (b,c), (a,b,c)}
take any element out
powerset(S) = {(), (a), (b), (c), (a,b), (a,c), (b,c), (a,b,c)} is
powerset(S - {c}) = {(), (a), (b), (a,b)} unioned with
{c} U powerset(S - {c}) = { (c), (a,c), (b,c), (a,b,c)}
powerset(S) = powerset(S - {ei}) U ({ei} U powerset(S - {ei}))
where ei is an element of S (a singleton)
Pseudo-algorithm
Is the set passed empty? Done (Note that power set of {} is {{}})
If not, take an element out
recursively call method on the remainder of the set
return the set composed of the Union of
the powerset of the set without the element (from the recursive call)
this same set (i.e., 2.1) but with each element therein unioned with the element initially taken out
def power(a)
(0..a.size).map {|x| a.combination(x).to_a}.flatten(1)
end
From a comment by OP (copy edited):
The example is simplified form of what I am actually doing. The numbers are objects which have a property "Qty", I want to sum the quantities for every possible combination then chose the combination that uses the most objects where the sum of the quantities N is within some other boundaries, e.g. x < N < y.
What you have is an optimization problem. What you have assumed is that the right way to approach this optimization problem is to decompose it into an enumeration problem (which is what you asked) and then a filtration problem (which presumably you know how to do).
What you don't yet realize is that this kind of solution only works either (a) for theoretical analysis, or (b) for very small values of n. The enumeration you're asking for is exponential in n, which means you'd end up with something that would take far too long to run in practice.
Therefore, figure out how to pose your optimization problem as such, write a new question, and edit this one to point to it.
Same as hobodave's answer, but iterative and faster (in Ruby). It also works with both Array and Set.
def Powerset(set)
ret = set.class[set.class[]]
set.each do |s|
deepcopy = ret.map { |x| set.class.new(x) }
deepcopy.map { |r| r << s }
ret = ret + deepcopy
end
return ret
end
In my tests, IVlad's method doesn't work so well in Ruby.
Recursive and iterative solutions to calculate power set in scheme. Not fully tested though
(define (power_set set)
(cond
((empty? set) (list '()))
(else
(let ((part_res (power_set (cdr set))))
(append (map (lambda (s) (cons (car set) s)) part_res) part_res)))))
(define (power_set_iter set)
(let loop ((res '(())) (s set))
(if (empty? s)
res
(loop (append (map (lambda (i) (cons (car s) i)) res) res) (cdr s)))))
Hereafter is a recursive solution, which is similar to already posted ones. A few assertions are providing as kind of unit tests.
I didn't managed to use "set" Python type for representing set of sets. Python said that "set objects are unhashable" when trying expression like "s.add(set())".
See also solutions in many programming languages at http://rosettacode.org/wiki/Power_set
def generatePowerSet(s, niceSorting = True):
"""Generate power set of a given set.
The given set, as well as, return set of sets, are implemented
as lists.
"niceSorting" optionnaly sorts the powerset by increasing subset size.
"""
import copy
def setCmp(a,b):
"""Compare two sets (implemented as lists) for nice sorting"""
if len(a) < len(b):
return -1
elif len(a) > len(b):
return 1
else:
if len(a) == 0:
return 0
else:
if a < b:
return -1
elif a > b:
return 1
else:
return 0
# Initialize the power set "ps" of set "s" as an empty set
ps = list()
if len(s) == 0:
ps.append(list())
else:
# Generate "psx": the power set of "sx",
# which is "s" without a chosen element "x"
sx = copy.copy(s)
x = sx.pop()
psx = generatePowerSet(sx, False)
# Include "psx" to "ps"
ps.extend(psx)
# Include to "ps" any set, which contains "x"
# Such included sets are obtained by adding "x" to any subset of "sx"
for y in psx:
yx = copy.copy(y)
yx.append(x)
ps.append(yx)
if niceSorting:
ps.sort(cmp=setCmp)
return ps
assert generatePowerSet([]) == [[]]
assert generatePowerSet(['a']) == [[], ['a']]
assert generatePowerSet(['a', 'b']) == [[], ['a'], ['b'], ['a', 'b']]
assert generatePowerSet(['a', 'b','c']) == [[],
['a'], ['b'], ['c'],
['a', 'b'], ['a', 'c'], ['b', 'c'],
['a', 'b', 'c'] ]
assert generatePowerSet(['a', 'b','c'], False) == [ [],
['a'],
['b'],
['a', 'b'],
['c'],
['a', 'c'],
['b', 'c'],
['a', 'b', 'c'] ]
print generatePowerSet(range(4), True)
My colleague created an elegant way to do it in ruby. It uses IVlad's concept on the index set.
class Array
def select_by_index(&block)
# selects array element by index property
n = []
each_with_index do |e, i|
if block.call(i)
n << e
end
end
n
end
end
def pow(a)
# power set of a
max = (1 << a.length)
(0...max).map { |i| a.select_by_index { |k| (1 << k) & i != 0 }}
end
Related
I got stuck in the resolution of the next problem:
Imagine we have an array structure, any structure, but for this example let's use:
[
[ [1, 2], [3, 4], [5, 6] ],
[ 7, 8, 9, 10 ]
]
For convenience, I transform this structure into a flat array like:
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Imagine that after certain operations our array looks like this:
[ 1, 2, 3, 4, 12515, 25125, 12512, 8, 9, 10]
NOTE: those values are a result of some operation, I just want to point out that is independent from the structure or their positions.
What I would like to know is... given the first array structure, how can I transform the last flat array into the same structure as the first? So it will look like:
[
[ [1, 2], [3, 4] , [12515, 25125] ],
[ 12512, 8, 9, 10]
]
Any suggestions? I was just hardcoding the positions in to the given structure. But that's not dynamic.
Just recurse through the structure, and use an iterator to generate the values in order:
function fillWithStream(structure, iterator) {
for (var i=0; i<structure.length; i++)
if (Array.isArray(structure[i]))
fillWithStream(structure[i], iterator);
else
structure[i] = getNext(iterator);
}
function getNext(iterator) {
const res = iterator.next();
if (res.done) throw new Error("not enough elements in the iterator");
return res.value;
}
var structure = [
[ [1, 2], [3, 4], [5, 6] ],
[ 7, 8, 9, 10 ]
];
var seq = [1, 2, 3, 4, 12515, 25125, 12512, 8, 9, 10];
fillWithStream(structure, seq[Symbol.iterator]())
console.log(JSON.stringify(structure));
Here is a sketch in Scala. Whatever your language is, you first have to represent the tree-like data structure somehow:
sealed trait NestedArray
case class Leaf(arr: Array[Int]) extends NestedArray {
override def toString = arr.mkString("[", ",", "]")
}
case class Node(children: Array[NestedArray]) extends NestedArray {
override def toString =
children
.flatMap(_.toString.split("\n"))
.map(" " + _)
.mkString("[\n", "\n", "\n]")
}
object NestedArray {
def apply(ints: Int*) = Leaf(ints.toArray)
def apply(cs: NestedArray*) = Node(cs.toArray)
}
The only important part is the differentiation between the leaf nodes that hold arrays of integers, and the inner nodes that hold their child-nodes in arrays. The toString methods and extra constructors are not that important, it's mostly just for the little demo below.
Now you essentially want to build an encoder-decoder, where the encode part simply flattens everything, and decode part takes another nested array as argument, and reshapes a flat array into the shape of the nested array. The flattening is very simple:
def encode(a: NestedArray): Array[Int] = a match {
case Leaf(arr) => arr
case Node(cs) => cs flatMap encode
}
The restoring of the structure isn't all that difficult either. I've decided to keep the track of the position in the array by passing around an explicit int-index:
def decode(
shape: NestedArray,
flatArr: Array[Int]
): NestedArray = {
def recHelper(
startIdx: Int,
subshape: NestedArray
): (Int, NestedArray) = subshape match {
case Leaf(a) => {
val n = a.size
val subArray = Array.ofDim[Int](n)
System.arraycopy(flatArr, startIdx, subArray, 0, n)
(startIdx + n, Leaf(subArray))
}
case Node(cs) => {
var idx = startIdx
val childNodes = for (c <- cs) yield {
val (i, a) = recHelper(idx, c)
idx = i
a
}
(idx, Node(childNodes))
}
}
recHelper(0, shape)._2
}
Your example:
val original = NestedArray(
NestedArray(NestedArray(1, 2), NestedArray(3, 4), NestedArray(5, 6)),
NestedArray(NestedArray(7, 8, 9, 10))
)
println(original)
Here is what it looks like as ASCII-tree:
[
[
[1,2]
[3,4]
[5,6]
]
[
[7,8,9,10]
]
]
Now reconstruct a tree of same shape from a different array:
val flatArr = Array(1, 2, 3, 4, 12515, 25125, 12512, 8, 9, 10)
val reconstructed = decode(original, flatArr)
println(reconstructed)
this gives you:
[
[
[1,2]
[3,4]
[12515,25125]
]
[
[12512,8,9,10]
]
]
I hope that should be more or less comprehensible for anyone who does some functional programming in a not-too-remote descendant of ML.
Turns out I've already answered your question a few months back, a very similar one to it anyway.
The code there needs to be tweaked a little bit, to make it fit here. In Scheme:
(define (merge-tree-fringe vals tree k)
(cond
[(null? tree)
(k vals '())]
[(not (pair? tree)) ; for each leaf:
(k (cdr vals) (car vals))] ; USE the first of vals
[else
(merge-tree-fringe vals (car tree) (lambda (Avals r) ; collect 'r' from car,
(merge-tree-fringe Avals (cdr tree) (lambda (Dvals q) ; collect 'q' from cdr,
(k Dvals (cons r q))))))])) ; return the last vals and the combined results
The first argument is a linear list of values, the second is the nested list whose structure is to be re-created. Making sure there's enough elements in the linear list of values is on you.
We call it as
> (merge-tree-fringe '(1 2 3 4 5 6 7 8) '(a ((b) c) d) (lambda (vs r) (list r vs)))
'((1 ((2) 3) 4) (5 6 7 8))
> (merge-tree-fringe '(1 2 3 4 5 6 7 8) '(a ((b) c) d) (lambda (vs r) r))
'(1 ((2) 3) 4)
There's some verbiage at the linked answer with the explanations of what's going on. Short story short, it's written in CPS – continuation-passing style:
We process a part of the nested structure while substituting the leaves with the values from the linear supply; then we're processing the rest of the structure with the remaining supply; then we combine back the two results we got from processing the two sub-parts. For LISP-like nested lists, it's usually the "car" and the "cdr" of the "cons" cell, i.e. the tree's top node.
This is doing what Bergi's code is doing, essentially, but in a functional style.
In an imaginary pattern-matching pseudocode, which might be easier to read/follow, it is
merge-tree-fringe vals tree = g vals tree (vs r => r)
where
g vals [a, ...d] k = g vals a (avals r => -- avals: vals remaining after 'a'
g avals d (dvals q => -- dvals: remaining after 'd'
k dvals [r, ...q] )) -- combine the results
g vals [] k = k vals [] -- empty
g [v, ...vs] _ k = k vs v -- leaf: replace it
This computational pattern of threading a changing state through the computations is exactly what the State monad is about; with Haskell's do notation the above would be written as
merge_tree_fringe vals tree = evalState (g tree) vals
where
g [a, ...d] = do { r <- g a ; q <- g d ; return [r, ...q] }
g [] = do { return [] }
g _ = do { [v, ...vs] <- get ; put vs ; return v } -- leaf: replace
put and get work with the state being manipulated, updated and passed around implicitly; vals being the initial state; the final state being silently discarded by evalState, like our (vs r => r) above also does, but explicitly so.
I'm exploring Functional Programming with the Ruby language. Below is my version of a Fold in Ruby. I've tested it on a variety of functions, reverse, filter, map etc, and it returns the results as expected. But it mutates data and needs assignment statements. Can anyone help me to do the same but without violating the Functional paradigm? Can anyone help me with the partial application of the curried function at the bottom? I suspect there something obvious I'm missing. Thanks.
fold_l = lambda do |ray, base, funcky|
if ray == []
base
else
base = funcky.call(base,ray.first)
ray.shift
fold_l.call(ray,base,funcky)
end
end
abc = [1, 2, 3, 4, 5, 6, 7]
mapper = lambda {|sum, x| sum << x*x}
lengthy = lambda {|sum, _| sum + 1}
p fold_l.call(abc,[],mapper) ## works fine
p abc ## but mutates data!!
abc = [1, 2, 3, 4, 5, 6, 7]
p curryFold = fold_l.curry.(abc).(0).(lengthy) ## works fine
lengthC = curryFold.(base:0).(funcky:lengthy)
p lengthC.call.(abc) ## but this gives error
Rework your fold_l function to not mangle the arguments its given:
def fold_l(ray, base, funcky)
return base if ray.empty?
base = funcky.call(base,ray.first)
fold_l(ray.last(ray.length-1),base,funcky)
end
This uses last to return a copy of the arguments minus the first. It's also not necessary to use lambda here as you want a named function, so you may as well declare it formally. lambda is reserved for situations where you don't necessarily have a name for it.
Note that in Ruby it's generally rude to damage the arguments your method's given unless there's an understanding that it's acceptable. Most methods make copies if they need to perform alterations.
I would probably implement foldl like this – and always be careful when using recursion in languages that don't support tail call optimisation (read more)
foldl = -> (f, acc, (x,*xs)) do
if x.nil? then
acc
else
foldl.call f, (f.call acc, x), xs
end
end
add = -> (x,y) do
x + y
end
length =
foldl.curry
. (-> (acc,_) { acc + 1 })
. (0)
data = [ 1, 2, 3, 4, 5 ]
p foldl.call add, 0, data
# => 15
p length.call data
# => 5
as per #tadman's recommendation, using a normal def/end block is probably better but that's just a matter of preference – note that currying is no longer necessary with this style
def foldl f, acc, (x,*xs)
if x.nil? then
acc
else
foldl f, (f.call acc, x), xs
end
end
def add x, y
x + y
end
def length xs
foldl (-> (acc,_) { acc + 1 }), 0, xs
end
data = [ 1, 2, 3, 4, 5 ]
p foldl method(:add), 0, data
# => 15
p length data
# => 5
I understand #max, #min, #minmax. I understand <=>. But how does it work in a block within one of those functions?
That is, what is happening in the third line below? What is <=> doing in #min?
a = %w(albatross dog horse)
a.min #=> "albatross"
a.min { |a, b| a.length <=> b.length } #=> "dog"
example from http://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-min
What behavior would it have for an array of numbers?
As you've probably already seen, the documentation for the min method says:
min(n) → array
min(n) {| a,b | block } → array
Returns the object in enum with the minimum value. The first form
assumes all objects implement Comparable; the second uses the block to
return a <=> b.
This means that, in the first form, min is calling the <=> method on objects in the array and using the result to determine which element is the smallest.
In the second form, min instead calls the block with both of the elements it wants to compare, and uses the block's return value to determine which element is the smallest. Basically, it's using the block as if it were an implementation of the <=> operator. So x.min {|a,b| a <=> b } would be equivalent to x.min.
In the example given (a.min { |a, b| a.length <=> b.length } #=> "dog"), this means that instead of comparing each element to determine sort order, it's comparing the lengths of each element to make that determination. Since "dog" is the shortest string in the list, that's the value that gets returned by min. max, minmax, and sort behave similarly.
Note that the example there is a bit contrived, since you could just use min_by in that situation to achieve the same result with simpler code: a.min_by { |x| x.length }. If you want more fine-grained control though when determining sort order, using min with a block might be appropriate.
What behavior would it have for an array of numbers?
min behaves the same way regardless of what the array contains. In this case though using the block { |a, b| a.length <=> b.length } wouldn't work since numbers don't have a length method on them. Here's a better example for numbers, which sorts by smallest to biggest, but always counts odd numbers as being bigger than even numbers:
[2, 10, 9, 7, 6, 1, 5, 3, 8, 4].sort do |a, b|
if a.odd? && b.even?
1
elsif a.even? && b.odd?
-1
else
a <=> b
end
end
Result:
[2, 4, 6, 8, 10, 1, 3, 5, 7, 9]
Notice how even numbers are sorted before odd numbers in the final array? That's the result of the block we passed to sort. The behavior is similar for min, max, and minmax.
min passes two elements a and b from the array to the block and the block is expected to return -1, 0, or +1 depending on whether a is less than, equal to, or greater than b. The "spaceship" operator <=> returns these -1, 0, or +1 values.
The algorithm is easy. Given a comparison function:
cmp = -> (a, b) { a.length <=> b.length }
We start by comparing the 1st with the 2nd element:
cmp.call 'albatros', 'dog'
#=> 1
1 means 'albatros' is greater than 'dog'. We continue with the lesser value, i.e. 'dog' and compare it with the 3rd element:
cmp.call 'dog', 'horse'
#=> -1
-1 means 'dog' is less than 'horse'. There are no more elements, so 'dog' is the result.
You could also implement this algorithm in Ruby:
def my_min(ary)
ary.inject { |min, x| yield(x, min) == -1 ? x : min }
end
ary = %w(albatross dog horse)
my_min(ary) { |a, b| a.length <=> b.length }
#=> "dog"
I need to check whether the sum of any 2 elements of an array equals to the given number. This is what I came up with, but it doesn't seem to do the comparison
def sum_comparison(int_array, x)
n = int_array.length
(0..n).each do |i|
(1..n).each do |j|
if ((int_array[i].to_i + int_array[j].to_i) == x)
return true
else
return false
end
end
end
end
Your solution seems overly complicated and strongly influenced by the programming style of low-level procedural languages like C. One apparent problem is that you write
n = int_array.length
(0..n).each do |i|
# use int_array[i].to_i inside the loop
end
Now inside the each loop, you will get the numbers i = 0, 1, 2, ..., n, for example for int_array = [3,4,5] you get i = 0, 1, 2, 3. Notice that there are four elements, because you started counting at zero (this is called an off by one error). This will eventually lead to an array access at n, which is one beyond the end of the array. This will again result in a nil coming back, which is probably why you use to_i to convert that back to an integer, because otherwise you would get a TypeError: nil can't be coerced into Fixnum whend doing the addition. What you probably wanted instead was simply:
int_array.each do |i|
# use i inside the loop
end
For the example array [3,4,5] this would actually result in i = 3, 4, 5. To get the combinations of an array in a more Ruby way, you can for example use Array#combination. Likewise, you can use Array#any? to detect if any of the combinations satisfy the specified condition:
def sum_comparison(array, x)
array.combination(2).any? do |a, b|
a + b == x
end
end
When your function compare first element, it's immediately returns false. You need to return only true when iterating and return false at the end if nothing were found, to avoid this issue:
def sum_comparison(int_array, x)
n = int_array.size
(0...n).each do |i|
(1...n).each do |j|
if (int_array[i].to_i + int_array[j].to_i) == x
return true
end
end
end
false
end
To simplify this you can use permutation or combination and any? methods as #p11y suggests. To get founded elements you could use find or detect.
def sum_comparison(a, x)
a.combination(2).any? { |i, j| i + j == x }
end
a.combination(2).detect { |i, j| i + j == x }
# sum_comparison([1,2,3, 4], 6) => [2, 4]
Using an enumerator:
#!/usr/bin/env ruby
def sum_comparison(int_array, x)
enum = int_array.to_enum
loop do
n = enum.next
enum.peek_values.each do |m|
return true if (n + m) == x
end
end
false
end
puts sum_comparison([1, 2, 3, 4], 5)
Output:
true
Problem
Your method is equivalent to:
def sum_comparison(int_array, x)
return int_array[0].to_i + int_array[1].to_i == x
end
Therefore,
int_array = [1,2,4,16,32,7,5,7,8,22,28]
sum_comparison(int_array, 3) #=> true, just lucky!
sum_comparison(int_array, 6) #=> false, wrong!
Alternative
Here is a relatively efficient implemention, certainly far more efficient than using Enumerable#combination.
Code
def sum_comparison(int_array, x)
sorted = int_array.sort
smallest = sorted.first
sorted_stub = sorted.take_while { |e| e+smallest <= x }
p "sorted_stub = #{sorted_stub}"
return false if sorted_stub.size < 2
loop do
return false if sorted_stub.size < 2
v = sorted_stub.shift
found = sorted_stub.find { |e| v+e >= x }
return true if found && v+found == x
end
false
end
Examples
sum_comparison([7,16,4,12,-2,5,8], 3)
# "sorted_stub = [-2, 4, 5]"
#=> true
sum_comparison([7,16,4,12,-2,5,8], 7)
# "sorted_stub = [-2, 4, 5, 7, 8]"
#=> false
sum_comparison([7,16,4,22,18,12,2,41,5,8,17,31], 9)
# "sorted_stub = [2, 4, 5, 7]"
#=> true
Notes
The line p "sorted_stub = #{sorted_stub}" is included merely to display the array sorted_stub in the examples.
If e+smallest > x for any elements f and g in sorted for which g >= e and f < g, f+g >= e+smallest > x. Ergo, sorted_stub.last is the largest value in sorted that need be considered.
For a given value v, the line found = sorted_stub.find { |e| v+e >= x } stops the search for a second value e for which v+e = x as soon as it finds e such that v+e >= x. The next line then determines if a match has been found.
Let's say I have 4 characters, A, P, B, N. I want to be able to compare them such that:
A > P > B > N > A
How would this be accomplished in Ruby?
From your comments, it seems that you are not trying to put these elements in order, but rather define some binary relation between some of them. It's possible to do that in Ruby in many ways, depending on how you intend to use that relation later.
The simplest one is just to define ordered pairs of related elements:
MAP = [
['A', 'P'],
['P', 'B'],
['B', 'N'],
['N', 'A']
]
And then use it whenever you need to "compare" two elements.
def beats? one, other
MAP.member?([one, other])
end
beats? 'A', 'B'
# => false
beats? 'A', 'P'
# => true
beats? 'N', 'A'
# => true
PS. You can generate the map from a string using something like
MAP = 'APBNA'.chars.each_cons(2).to_a
One of the possible solutions is to create a class with, for example, character and weight or something. And implement <=> operator (method) in it.
Don't forget to include Comparable mixin into this class.
class ComparableCharacter
include Comparable
attr_accessor :character, :weight
def <=>(another)
weight <=> another.weight
end
end
a = "APBN"
h = {};(0...a.size).each{|i| h[a[i].chr] = i}
b = ['A','P','A','N', 'B','P']
b.sort_by{|t| h[t] }
Of course this can not work with your example as you have a bad ordering - you can never have A > P > A, but at least it shows you how to sort according to an order you want.
If someone can be interested, this is my proposal (ternary comparison - because comparison is not a binary operation!!!):
class RockPaperScissors
ITEMS = %W(A P B N)
def self.compare(item, other_item)
new(item).compare other_item
end
def initialize(item)
# input validations?
#item = item
end
def compare(other_item)
# input validations?
indexes_subtraction = ITEMS.index(#item) - ITEMS.index(other_item)
case indexes_subtraction
when 1, -1
- indexes_subtraction
else
indexes_subtraction <=> 0
end
end
end
require 'test/unit'
include MiniTest::Assertions
assert_equal RockPaperScissors.compare('A', 'A'), 0
assert_equal RockPaperScissors.compare('P', 'P'), 0
assert_equal RockPaperScissors.compare('B', 'B'), 0
assert_equal RockPaperScissors.compare('N', 'N'), 0
assert_equal RockPaperScissors.compare('A', 'P'), 1
assert_equal RockPaperScissors.compare('P', 'A'), -1
assert_equal RockPaperScissors.compare('P', 'B'), 1
assert_equal RockPaperScissors.compare('B', 'P'), -1
assert_equal RockPaperScissors.compare('B', 'N'), 1
assert_equal RockPaperScissors.compare('N', 'B'), -1
assert_equal RockPaperScissors.compare('N', 'A'), 1
assert_equal RockPaperScissors.compare('A', 'N'), -1
EXPLANATION
Equality: (A, A) comparison
Indexes: iA: 0; iA: 0
iA - iA = 0
A is equal to A, so we could return 0
Majority: (A, P)
Indexes: iA: 0; iP: 1
iA - iP = -1
A > P, so we must obtain 1; we can use the - function: - (-1) -> 1
Minority: (P, A)
Indexes: iP: 1; iA: 0
iP - iA = 1
P < A, so we must obtain -1; we can use the - function: - (1) -> -1
Edge case 1: (N, A)
Indexes: iN: 3, iA: 0
iN - iA = 3
N > A, so we must obtain 1; we can use the <=> function: (3 <=> 0) -> 1
Edge case 2: (A, N)
Indexes: iA: 0, iN: 3
iA - iN = -3
A < N, so we must obtain -1; we can use the <=> function: (3 <=> 0) -> 1
The rest is refactoring: 0 can be converted to 0 with the <=> function.