From: Robert Klemme Date: 2004-08-17T19:11:03+09:00 Subject: Re: Q: Shifting a hash anf and array "Meino Christian Cramer" schrieb im Newsbeitrag news:20040817.105109.41634481.Meino.Cramer@gmx.de... > From: "Robert Klemme" > Subject: Re: Q: Shifting a hash anf and array > Date: Tue, 17 Aug 2004 17:21:01 +0900 > > Hi Martin, Hi Robert, > > thank you very much for your sources ! > Great! :) You're welcome! > I have an additional question below.....vvvvvv Ok, answers below. > > > > > "Martin DeMello" schrieb im Newsbeitrag > > news:u_gUc.122944$M95.4319@pd7tw1no... > > > Meino Christian Cramer wrote: > > > > > > > > Contents of the array before the trick: > > > > > > > > 1 2 3 4 5 6 7 8 9 0 index > > > > A B C D E F G H I J contents > > > > > > > > (execute *trick*) > > > > > > > > Contents of the array after the trick: > > > > > > > > 1 2 3 4 5 6 7 8 9 0 index > > > > J A B C D E F G H I contents > > > > > > array.unshift(array.pop) > > > > or array.push(array.shift) for the other direction. > > > > > > And now the shift of an hash: > > > > > > > > Contents of the hash before the trick: > > > > > > > > A B C D E F G H I J key > > > > 1 2 3 4 5 6 7 8 9 0 value > > > > > > > > (execute *trick*) > > > > > > > > Contents of the hash after the trick: > > > > > > > > A B C D E F G H I J key > > > > 0 1 2 3 4 5 6 7 8 9 value > > > > > > > > Both are very similiar. > > > > > > Not really :) Hashes are inherently unordered, so the concept of a > > > rotation is a bit trickier. This works on the assumption that > > > Hash#values returns values in positions corresponding to the keys in > > > Hash#keys... > > > > > > class Array > > > def rot1 > > > unshift(pop) > > > end > > > end > > > > > > Hash[*(hash.keys.rot1.zip(hash.values.rot1).flatten)] > > > > Hm, IMHO there is one "rot1" too much: you're rotating keys *and* values > > while you should only rotate one of them. Otherwise I figure the result > > will be the same as the original hash... :-) > > > > The other drawback of your solution is that it relys on #keys and #values > > returning elements in matching order, i.e., if you zip both you get the same > > hash. Although that might work it makes me feel a bit wary because AFAIK > > it's not a guaranteed property of Hash (and I wouldn't want to rely on it). > > This might even change over time if the Hash implementation was changed for > > any reasons. > > > > How about these one liners, that avoids the problem: > > > > h = {0=>"a", 1=>"b", 2=>"c"} > > # put rotated in h2 > > (h2={}).send(:[]=, *h.sort.inject {|(k1,v1),(k2,v2)| h2[k1]=v2; [k2, v1]}) > > I tried to understand this... > > "h = {0=>"a", 1=>"b", 2=>"c"}" generates an hash...ok > > "(h2={}).send(:[]=" generates h2, another hash and sends to its []= > (element assignment) the.... > > "*h.sort.inject {|(k1,v1),(k2,v2)| h2[k1]=v2; [k2, v1]})" > > ...?... I stumbled over the "*" at *h.sort.....What does it mean? If you execute h.sort.inject {|(k1,v1),(k2,v2)| h2[k1]=v2; [k2, v1]} you'll see the result is an array: >> h.sort.inject {|(k1,v1),(k2,v2)| h2[k1]=v2; [k2, v1]} => [2, "a"] You need unary "*" to make this an argument list for the invocation of []=. Some example usage of "*" for clarification (hopefully): >> def foo(*args) p args end => nil >> foo 1 [1] => nil >> foo [1] [[1]] => nil >> foo(1,2,3) [1, 2, 3] => nil >> foo([1,2,3]) [[1, 2, 3]] => nil > > # rotate inplace > > h.send(:[]=, *h.sort.inject {|(k1,v1),(k2,v2)| h[k1]=v2; [k2, v1]}) > > > ...same here... > > > :-) > > > > Note: you can omit the sort in the first example, but IMHO an order should > > be imposed on keys. Otherwise the rotation does not make much sense IMHO. > > Absolutely correct. I was searching for something like "an array > with indices not being integers but ordered". > > > You can omit the sort in the second example, too, which seems to work > > because the set of keys of the hash doesn't change. But this seems a bit > > hackish to me, too. You can substitute "sort" by "dup" though. > > > > Kind regards > > > > robert > > > > > > PS: Enumerable#inject is cool. ;-) > > PS: Undertstanding and writing such code is much cooler... ! :-)) #inject works like this: the block receives two values, the result of the last block execution and the current iteration element. The result of inject is the value of the last block evaluation. At first invocation the value of the last execution is either the value provided as argument to inject if there is one or otherwise the first element of the iteration and the current element is the second element: >> %w{a b c d}.inject("foo") {|*args| p args; args.join('|')} ["foo", "a"] ["foo|a", "b"] ["foo|a|b", "c"] ["foo|a|b|c", "d"] => "foo|a|b|c|d" >> %w{a b c d}.inject {|*args| p args; args.join('|')} ["a", "b"] ["a|b", "c"] ["a|b|c", "d"] => "a|b|c|d" You can do all sorts of cool things with inject, in fact you can implement all Enumerable methods based on inject - although that's not always the most elegant or efficient method. Counting is probably the easiest: module Enumerable def count inject(0) {|cnt,| cnt + 1} end end >> %w{a b c d}.count => 4 But other algorithms work, too: module Enumerable def find_elem inject([]) {|sel, item| sel << item if yield item; sel} end end >> %w{a b c d}.find_elem {|e| /^a/ =~ e} => ["a"] Another feature I used was bracketing of block arguments to assign keys and values to individual variables. During a Hash iteration with inject, the current element is a two element array with key and value: >> {1=>"a",2=>"b",3=>"c"}.inject {|*args| p args} [[1, "a"], [2, "b"]] [nil, [3, "c"]] => nil By using brackets you can control assignment of array elemtens to variables: >> test = lambda {|(a,b),c,*d| p a, b, c, d} => # >> test.call [1,2],3,4,5,6,7,8 1 2 3 [4, 5, 6, 7, 8] => nil >> test = lambda {|a,(b,*c),*d| p a, b, c, d} => # >> test.call 1,[2,3,4],5,6,7,8,9 1 2 [3, 4] [5, 6, 7, 8, 9] => nil In my code the first value is carried through the whole iteration while the key of the last value is always the last key, which is assigned the current value. I hope that made things a bit clearer. :-) > Ruby.use! Definitely! Kind regards robert