Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
162 changes: 159 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,160 @@
# assignment_search
Marco? Polo!
# Data Structures, searching algorithms & Knight in Chess movements

[A data structures and algorithms Ruby challenge from the Viking Code School](http://www.vikingcodeschool.com)
practice with representing data using Trees and Graphs.

* knight_tree.rb - 'MoveTree class (as in, "a tree of moves"). It should construct a tree of potential moves from a given position by using a series of Move objects. It should take two inputs -- a coordinate pair to represent the starting position and a maxDepth value which prevents the tree from continuing infinitely large.'
* knight_searcher.rb - Breadth-First Search - bfsFor method which takes a targetCoords input and kicks off a breadth-first search of the nodes in the tree - and Depth-First Search - which depth-first search for the target coordinates
* benchmark.rb - benchmark which runs a series of similar searches using each method thousands of times and compares them.

## Getting Started

If you want to quick run some the examples to see the code in action, run
```
$ ruby example.rb
```
from the project directory.

## Authors

* **Dariusz Biskupski** - *Initial work* - https://dariuszbiskupski.com


## Acknowledgments

It is part of the assignment created for [Viking Code School](https://www.vikingcodeschool.com/)



## Appendix - BFS and DFS Concepts

1. What data structure is used to implement DFS?
--stack - which stacks following nodes on the previous one so he can gets deeper in search for particular node

2. What data structure is typically used to implement BFS?
--queue - which holds the nodes in an order that allows to deal with one depth at a time and having its children saved for later

3. Which one can be done recursively? (the clue should be the data structure)
-- Depth First Search as it repetitively goes deeper and deeper until certain point is reached, when it folds back to a specific point (ie. were there are children on the node that haven't been penetrated yet - but then again can recursively go deeper). Also Recursion uses stack as data structure.

4. Which one would you use to print a list of all the nodes in a tree or graph, starting with depth 1, then depth 2, then depth 3 etc.?
--Definitely Breadth Search First - as command line can print one line a time which is adequate to one depth at a time

5. What is the difference between a tree and a graph?
--A tree has a hierarchical structure, we have a one clear source, a source has a number of children who cannot be parents of their parent; there are children who are not parents which is adequate to the end of particular branches/edges

Pseudocode the following processes with enough detail to be clear:

1. Searching a simple tree of nodes where each Node has an array of child nodes (some_node.children) using DFS.
a. record the target child to be found
b. Loop starts
ba. pick a child (of root or previous child) and check if it's the searched solution
baa. if solution is found, exit the loop, return the solution
bab. if no solution if found, add the node into the stack
bac. if no solution is found and we reached the dead end unstack the parent and examine if it has a child unexamined
baca. if solution is found, break the loop and return the solution
bacb. if no solution is found, repeat the process


2. Searching the same tree using BFS.
a. record the target child to be found
b. Loop starts
ba. add the source/root node into the queue
bb. dequeue the first node
bc. Check if it matches criteria
bca. if the source has a solution the search is ended, solution returned
bcb. if the source doesn't hold the solution
bcc. add its children to the back of the queue

3. Searching a graph (represented however you feel most comfortable -- Edge List, Adjacency List or Adjacency Matrix) using DFS.
a. record the target child to be found
b. Loop starts on Adjecency List
ba. pick a child (of root or previous child) and check if it's the searched solution
baa. if solution is found, exit the loop, return the solution
bab. if no solution if found, add the vertex into the stack, take its childred, repeat the process
bac. if no solution is found and we reached the dead end unstack the parent and examine if it has a child unexamined
baca. if solution is found, break the loop and return the solution
bacb. if no solution is found, repeat the process

4. Searching the same graph using BFS.
a. record the target vertex to be found
b. Loop starts on Adjecency List
ba. add the source vertex - unvisited into the queue
bb. dequeue the first vertex
bb. check its neighbours
bca. if the vertex wasn't visited we have a tree edge
bcb. if the vertex has the solution, the search is ended, solution returned
bcc. if the vertex doesn't have a solution, we add its neighbours to the back of the queue following its weight
bcb. ignore the one we visited





Warmup 2: Knight's Travails Pseudocode


How will you represent a particular move? Will you repeat nodes? How will you display the final output after searching? How will you prevent your tree from continuing on infinitely?

*********Your mission here is to use your search skills to find the exact sequence of moves required to get from any given square to another square on the board.**********
```
--0-1-2-3-4-5-6-7
0|X|_|_|_|X|_|_|_|
1|_|_|F|_|_|_|_|_|
2|X|_|_|T|X|_|_|_|
3|_|X|_|X|_|_|_|_|
4|_|_|_|_|_|_|_|_|
5|_|_|_|_|_|_|_|_|
6|_|_|_|_|_|_|_|_|
7|_|_|_|_|_|_|_|_|
```
from x, y
from 2, 1
to 3, 2

### Pseudocode

Cheeseboard is 8x8

some_move = Move.new(:x, :y, :depth, :children, :parent)
our_move(:x, :y)


Create Board of Cheese
1. Crete a tree of all moves leading from the current position
a. Create Adjacent matrix of moves




2. Follow the Breadth First Search sequences
a. record the target child to be found
b. Loop starts
Take the node
ba. Check if the node coordinates are on the board
bb. Check if the node was visited comparing coords with the one in adjecent matrix (x, y and nil or 1)
bba. if it wasn't, mark the adjacent matrix i special coord nil into 1
bbaa. add the root or node into the queue
bbb. if it was, discard it and go to the next item in the queue
bc. dequeue the first node
bd. Check if it is the solution
bda. if the vertex has a solution the search is ended
bdaa. solution is returned in form of new linked_list with sequence of movements
which goes from current child to the parent - loop
bdb. if the vertex is not a solution
bdba. add its children to the back of the queue and repeat the process


Constraints: within dimensions of the board ?
Constraints: was it visited?

The sequence of moves.. or a linked_list
[[[1,2],[3,4]],[[3,4],[8,9]]]


Print the best sequence of turns for the Kinght ie.
"The Kinght has to do following movements to reach its destination:
TURN 1 : from 1,2 to 3,4
TURN 2 : from 1,2 to 3,4
TURN 3 : from 1,2 to 3,4
TURN 4 : from 1,2 to 3,4
"
57 changes: 57 additions & 0 deletions benchmark.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require_relative "knight_tree"
require_relative "knight_searcher"

class BenchmarkSearching

def initialize()
@root_series = roots_creator
@target_series = target_creator
end

def breadth_vs_depth_search
dfs = test_1000_searches_dfs
bfs = test_1000_searches_bfs
puts "The total time of depth searching is #{dfs} while breadth is #{bfs}"
end

private

def roots_creator
roots = []
10.times {|combination_number| roots << [rand(8), rand(8)] }
roots
end

def target_creator
targets = []
10.times {|combination_number| targets << [rand(8), rand(8)] }
targets
end

def test_1000_searches_dfs
total_time = 0
time_start = Time.new
@root_series.each do |comb|
searcher = KnightSearcher.new( MoveTree.new([comb[0], comb[1]], 6) )
@target_series.each do |targets|
searcher.dfs_for([targets[0], targets[1]])
end
end
total_time = Time.new - time_start
end

def test_1000_searches_bfs
total_time = 0
time_start = Time.new
@root_series.each do |comb|
searcher = KnightSearcher.new( MoveTree.new([comb[0], comb[1]], 6) )
@target_series.each do |targets|
searcher.bfs_for([targets[0], targets[1]])
end
end
total_time = Time.new - time_start
end

end

BenchmarkSearching.new.breadth_vs_depth_search
102 changes: 102 additions & 0 deletions knight_searcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
require_relative "knight_tree"


class KnightSearcher

def initialize(tree)
@stack = []
@queue = []
@tree = tree.root
end

def bfs_for(target_coords)
tx = target_coords[0]
ty = target_coords[1]
enqueue(@tree)
while @queue.any?
current_node = dequeue
if current_node.x == tx && current_node.y == ty
solution_array = array_moves_from_current_node_to_root(current_node)
print_moves_sequence(solution_array)
break
else
current_node.children.each {|child| enqueue(child)} if current_node.children != nil
end
end
end

#recursive part 2
def dfs_for(target_coords, current_node = @tree)
tx = target_coords[0]
ty = target_coords[1]
current_node.children.each do |child|
if child.x == tx && child.y == ty
solution_array = array_moves_from_current_node_to_root(child)
print_moves_sequence(solution_array)
return -1
else
result = dfs_for(target_coords, child)
return -1 if result == -1
end
end
end

private

def enqueue(node)
@queue << node
end

def dequeue
@queue.shift
end

def add_to_stack(node)
@stack << node
end

def remove_from_stack
@stack.pop
end

def print_moves_sequence(array)
puts "There are #{array.length} moves to reach the target"
array.each {|coords| puts "#{coords}"}
end

def array_moves_from_current_node_to_root(current_node)
solution_array = []
while current_node
current_coords = [current_node.x, current_node.y]
solution_array.unshift(current_coords)
current_node = current_node.parents
end
solution_array
end

# # iterative solution
# def dfs_for(target_coords)
# tx = target_coords[0]
# ty = target_coords[1]
# add_to_stack(@tree)
# while @stack.any?
# current_node = remove_from_stack
# if current_node.x == tx && current_node.y == ty
# solution_array = array_moves_from_current_node_to_root(current_node)
# print_moves_sequence(solution_array)
# break
# else
# current_node.children.each {|child| add_to_stack(child)} if current_node.children != nil
# end
# end
# end




end

knight_tree = MoveTree.new([3,3], 5)
searcher = KnightSearcher.new( knight_tree )
searcher.bfs_for([1,6])
searcher.dfs_for([1,6])
55 changes: 55 additions & 0 deletions knight_tree.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
Move = Struct.new(:x, :y, :depth, :children, :parents)

class MoveTree
attr_accessor :root, :children, :parents

ALL_MOVES = [[1,2], [-1,2], [1,-2], [-1,-2], [2, 1], [-2,1], [2,-1], [-2,-1]]

def initialize(coord_array, max_depth=4)
@coord_array = coord_array
@max_depth = max_depth
@root = Move.new(coord_array[0], coord_array[1], 0, [], nil)
@total_nodes_number = 0
tree_builder
end

def inspect
root_children = @root.children
root_children.length {|idx| puts root_children[idx..idx+7] if root_children[idx] == "x" }
puts "Your tree has #{@total_nodes_number } Move nodes and a maximum depth of #{@max_depth}."
end

private

def add_child(current_node,x,y,depth)
new_node = Move.new(x, y, depth, [], current_node)
current_node.children << new_node
end

def build_child_nodes(current_node, depth)
number_of_nodes = 0
ALL_MOVES.each do |(x1,y1)|
x, y = current_node.x + x1, current_node.y + y1
if x <= 7 && x >= 0 && y <= 7 && y >= 0
add_child(current_node, x, y, depth + 1)
number_of_nodes += 1
end
end
@total_nodes_number += number_of_nodes
end

def build_generation(parent_node, depth=0)
return if depth > @max_depth
build_child_nodes(parent_node, depth)
parent_node.children.each do |child|
build_generation(child, depth+1)
end
end

def tree_builder
build_generation(@root)
self
end


end